Skip to main content

The Engineering Method: A Step-by-Step Process for Solving Challenging Problems

Getting stuck before you even begin to work on an engineering problem is more common than you think. Use this method to help you break a problem down, find a path toward a solution, and avoid mistakes.

Formation is an interview prep platform for software engineers founded by former Meta and Nextdoor software engineers who have one goal: to help mid-level and senior engineers from all backgrounds land life-changing roles.

Have you ever felt that flash of panic when you're handed a problem you've never seen before — on a deadline, with everyone watching? Maybe it's your manager dropping something urgent and mission-critical on your desk. Maybe you're in an interview for your dream job, sitting across from the one person standing between you and the offer. Most of us have been there.

In my years as a tech lead and engineering manager at companies like Microsoft and Meta (back when it was still Facebook), I noticed something: the people who shine in these moments usually aren't the smartest in the room, and they don't always have the most experience. They're the ones who lean on effective strategies for working through problems they've never seen. Junior engineers who do this well grow fast. Senior engineers who've sharpened these instincts become the people their teams rely on — and the ones best equipped to mentor everyone else.

So how do people make steady progress on something genuinely new and hard? What do you do when you truly have no idea where to start?

At Formation, we practice something we call the Engineering Method: a simple, repeatable loop for breaking a problem down, finding a path to a solution, and avoiding the mistakes that cost you. Let me be upfront — we didn't invent this. The shape of it is timeless. It's the same empirical loop behind the scientific method (observe, hypothesize, test) and John Boyd's OODA loop (observe, orient, decide, act). What we did was make each step concrete and give it a name, so it's something you can actually practice, review, and lean on when the pressure is on. We use it for software engineering, but the same steps travel well into plenty of other fields.

Step 1: Explore the problem

Before you write a single line, get crystal clear on what you're solving and what you have to honor while solving it. This is the step people are most tempted to rush, and it's the one where rushing hurts the most. Get the problem wrong here and every clever optimization later just compounds the mistake.

A few things worth nailing down:

  • The problem statement: what's actually happening versus what should be happening?
  • Success criteria: what does "done" or "fixed" mean, in measurable terms?
  • Constraints: performance, latency, cost, correctness, compliance, and — always — time.
  • Scope: what's explicitly in, and just as important, what's out.

One trap to watch for: make sure you're chasing the root cause, not just the loudest symptom.

Ask clarifying questions. Good ones tend to fall into two buckets — questions that gather requirements so you understand exactly what the goal is (and isn't), and questions that hunt for the subtle edge cases and exceptions hiding in the problem.

Come up with your own happy cases. A "happy case" is a normal input — not one designed to probe the edges. (You'll also hear "happy path" or "golden path.") What does the feature, algorithm, or system need to do in the ordinary case, and what does that case look like? Try to generate two or three examples that illustrate the requirements. These are what reveal the patterns that point you toward an approach.

Then push on the limits with edge cases. Resist going straight for the easy ones like "what if it's null" — useful for implementation, but rarely the thing that shapes your algorithm. Reach instead for the logical edge cases that are specific to this problem. Listing them now pays off later, because some designs handle certain edge cases far more gracefully than others, and that'll help you choose.

For algorithmic problems, the usual suspects:

  • Negative numbers
  • Empty cases (an empty array or string)
  • Out-of-bounds access
  • Cycles in linked lists
  • The literal edges of an array or matrix, which often have their own quirks
💡
Looking to land your next role? Join Formation to get unlimited personalized practice, mentorship, and mock interviews.

Step 2: Brainstorm solutions

Now map the solution space — and resist the urge to defend your first idea. The goal here is breadth: a short list of plausible approaches, usually two or three (up to five for something genuinely gnarly), each with its tradeoffs laid out.

Start simple. Try the obvious thing first. If nothing jumps out, run through the major solution archetypes for the kind of problem you're facing — for algorithms, that's stacks, queues, BFS, DFS, dynamic programming, and friends; for systems, things like map/reduce, key/value stores, and work queues.

A few moves that help you generate options:

  • Compare to patterns you already know — similar problems, architectures, or algorithms you've seen before.
  • Change the axis — latency versus throughput, consistency versus availability, build versus buy.
  • Include the ugly options — a brute-force or throwaway approach is worth writing down, because it often reveals a cleaner final design.

Don't throw away the ideas that don't pan out. Say you're trying depth-first search and, while testing it on an example, you realize it doesn't quite work. The way it fails might be exactly what tips you off that breadth-first search does. The dead ends frequently light the path to the right answer.

Work through a few examples by hand. Take the inputs you came up with while exploring and solve them manually, watching for decisions you can generalize. If you can't generalize, tweak the input a little and look again until the pattern shows itself.

And reason about time and space complexity for each candidate. It's one important axis for comparison — often, though not always, you'll want the solution with the best complexity.

Step 3: Plan your approach


A huge part of engineering is making decisions — choosing between options and owning the choice. So pick one. Weigh the advantages and disadvantages out loud, and if more than one solution is still on the table, think hard about which one actually makes the most sense to build given your real constraints: time, cost, risk, and the team around you.

There's usually no single "best" answer, so think in tradeoffs and talk through your reasoning. Complexity matters, but it's only one input. Weigh difficulty of implementation too. Simple solutions are often the right call — especially when the clock is running.

You've settled on an approach. Time to build it, right? Not quite. Even for simple problems, a little planning goes a long way. For something small, the plan might just be splitting out a couple of helper functions. Jumping straight in from the top tends to breed confusion and bugs; starting from small, testable building blocks tends not to.

For something bigger, a good plan of record covers:

  • Milestones — increments you can actually validate end to end.
  • Interfaces and invariants — what has to stay true the whole way through.
  • A testing strategy — unit, integration, load, whatever the problem calls for.
  • Observability — the logs, metrics, and traces that tie back to your success criteria.
  • Rollout and rollback — feature flags, staged deploys, and a clear way to recover fast if something goes sideways.

For an algorithm problem, the "plan" might just be a tight bit of pseudocode plus a note of the invariant you have to maintain and the edge cases you'll test. The point is the same at every scale: decide deliberately, then write down enough that execution is mostly mechanical.

Step 4: Implement it


With a clear solution and a plan in hand, the coding goes far more smoothly. The harder the problem, the more it pays to keep these steps explicit — splitting them out feels slow, but it heads off the expensive mistakes and usually wins on net.

A few principles worth holding to:

  • Ship in slices. Smaller increments mean you validate sooner and a bug has fewer places to hide.
  • Keep it reviewable. Small changes, clear history, obvious invariants.
  • Stay operationally literate. Add the dashboards, alerts, and runbooks as you build, not after.
  • Stay adaptable, but don't thrash. When new information genuinely invalidates an assumption, adjust the plan — but change course on evidence, not nerves.

One more thing: implementation should follow a plan, never stand in for one.

And if a problem looks simple enough to just start coding — go ahead, but know you haven't actually skipped the earlier steps. You did them ahead of time, in all the past work and practice that built your instincts.

Step 5: Verify it


You've written a solution. You're done. Right? No.

As an engineer, you're always on the hook for the quality of your work, and verification is where you earn your confidence — and catch the regressions before your users do. The good news: you've already done most of the prep. A lot of your test cases came straight out of Explore and Brainstorm.

Start with the happy cases. For a sort, that's a plain unsorted array like [3, 5, 4, 1, 2]. Pull a few of the important cases you flagged earlier.

Then hit the edge cases — the ones you already listed. Once the code is written, you'll often turn up a few implementation-level edge cases too, and this is the moment to handle genuinely malformed input. A nice trick for coverage: make sure every line of code runs at least once.

It's worth being honest about what testing can and can't do. It can show that bugs are present; it can never prove they're absent — that's an old observation of Dijkstra's, and it still holds. Thorough verification doesn't give you a proof, but it does give you real, earned confidence. Or, as the saying goes: in God we trust; everyone else brings data.

And here's where the loop comes back around. When verification turns up a problem, don't patch blindly — diagnose it and step back to the right place:

  • A design flaw sends you back to Plan.
  • A small, localized defect sends you back to Implement.
  • A wrong problem statement or the wrong success criteria sends you all the way back to Explore.

Stepping backward isn't a sign you failed. It's the method doing exactly what it's for — as long as you're moving on evidence.

Example: Seeing the loop on one problem

Let me make this concrete with a classic interview prompt: return the k most frequent elements in a stream of integers.

Explore. Clarify before you code. Is the input a bounded array or an unbounded stream? Do values repeat heavily, or are they nearly unique? How big is k relative to n? Does the answer need to be exact, or is an approximation fine at scale? Say the interviewer tells you "assume it could be a long stream" — there's your memory constraint, surfaced early.

Brainstorm. Map a few approaches instead of grabbing the first:

  • Count, then fully sort — trivial to write, but O(n log n) and sorts more than you need.
  • Count plus a min-heap of size kO(n log k), and the heap stays bounded.
  • Count plus a bucket sort by frequency — O(n) time, at the cost of more memory.
  • A Count-Min Sketch — sublinear memory if you're truly streaming, but the answer is approximate.

Plan. Given "long stream, exact answer expected," the min-heap is your plan of record: it keeps the heap bounded to k and avoids sorting the whole frequency map. Note the invariant (the heap never exceeds size k) and the cases you'll test (empty input, k at least the distinct count, ties), with a fallback in your back pocket — switch to the sketch if memory turns out to be the binding constraint.

Implement. Build in slices — frequency map first, then the bounded heap, then tie handling — each piece runnable on its own so a bug stays local.

Verify. Run those edge cases, reason about complexity out loud (O(n log k) time, O(n) space for the map), and dry-run an example by hand. If the dry run shows your heap comparison is inverted, that's a localized defect — back to Implement. And if the interviewer now says "actually, approximate is fine and memory's tight," your success criteria just changed — back to Explore and Plan, and you reach for the sketch.

The payoff was never any single answer. It's that every step left a visible artifact someone else could follow.

Wrapping Up

None of this is about being the smartest person in the room. It's about having a reliable way to move when you're staring at something unfamiliar and the clock is ticking. Explore, brainstorm, plan, implement, verify — and don't be afraid to loop back when the evidence tells you to. The first few times, the steps will feel slow and deliberate. That's fine. With practice they fold into instinct, and the panic that used to show up with every new problem starts to feel a lot more like the opening move of a process you already know how to run.

Feeling panic set in? Take a deep breath, then follow the Engineering Method.

Looking for structure in your job hunt? Apply below to close your most urgent interviewing skill gaps and interview with confidence.