A funny thing happened on my way to being a staff engineer.
In the early years of my tech career, I found great success and satisfaction from the tight loop of planning and executing software projects. As an individual contributor, I succeeded at my projects by understanding their requirements, making a plan of what code or system would solve them, and executing on that plan until the project was finished. And over time, feedback from my peers and success on my project reinforced this method—a good plan would result in strong execution and a successful outcome.
But when I became a staff engineer and started working on larger projects, this approach stopped working. I doubled down, spending more time on requirements and design up front, but found my classic approach where I planned ahead and avoided major mistakes wasn’t cutting it.
What I was encountering was a new class of decisions, impossible to get right no matter how much I prepared in advance. Even more alarmingly, it wasn't just getting harder to be correct, the costs of being wrong were increasing too. Decisions got harder to undo mid-flight, and I found myself wasting multiple engineer-months if I advocated for poor designs.
The very habits that had made me successful before—careful planning up front, faithful execution—were actively sabotaging my own success. It was these sorts of impossible decisions I needed a new strategy for solving.
What made these decisions different?
In a fit of frustration, I reflected. What had changed in these new decisions that broke my old approach? A few traits stood out.
Ambiguity ruled them. Business goals felt muddy, or applied to the project in non-obvious ways. Guidance on which infrastructure platforms to use might be nuanced, or actively changing. And product timelines were fuzzy, with unclear expectations on when to deliver. Together these meant my decisions could not rely on the solid up front assumptions.
Time horizons were also stretching, becoming much longer than the well-defined projects I’d cut my teeth on. When projects only took a month or so, the odds of major changes were minimal. As new, long-term efforts shaped up to take years of migration and sustained effort, the likelihood of engineers quitting, infrastructure emergencies interrupting, or priorities shifting became nearly guaranteed.
And finally my decisions were getting more “trapdoor” and painful to undo. Major decisions now often involved aspects that were expensive to reverse—like selecting a programming language for a new service to be written in. Any mistakes would come back to haunt me, in particularly painful fashion.
All this forced me to take a new tack: not "don't be wrong", but instead "how can I adjust course gracefully"? I needed to adapt to changes instead of assuming they wouldn’t happen.
What I learned is a superpower for impossible decisions: making plans that bend and adapt with the changing world around you. Inspired by Lean methodologies, I built a habit of forming rough, directional plans, testing them with small bets, and using quick production feedback to reorient myself and start the cycle again.
Forming a plan
No matter how we dress it up, we still cannot avoid making a plan up front. But we can refocus our planning effort with a new set of goals. We’ll break from our traditional assumptions by trusting that:
Any plan we come up with will likely change. We’ll instead build our entire process (especially the time we spend planning) around that.
We will doubly prioritize "getting to market" with our ideas. In this “impossible” domain, we need to balance planning with getting live feedback.
We can also fight the natural entropy of a shifting project landscape by rigorously writing things down. Constraints on what technology we use, motivation for the project as a whole, assumptions about what we should be optimizing for are all fair game. It's shockingly easy for a team of well-intentioned, competent developers to all quietly have incompatible beliefs about a project, especially in distributed, remote teams. Build a habit of regularly writing small, atomic, public decisions as you make them—the name of an API, the choice of which service to write code in, or what behavior should go in your first milestone.
To encourage project momentum and unstick challenging decisions, name a DRI (directly responsible individual). We want a single person who can push for consistent forward progress—remember we're trying hard to not over optimize for up front decision making. A named DRI guarantees we have at least one owner who can focus on building project momentum and ensure we get feedback in production early.
In such a complex decision landscape, you must be vigilant about fighting the tendency to descend into endless analysis paralysis. It's easy for intellectually interesting decisions between two equivalently good options to stretch out indefinitely, long past the point of diminishing returns.
Instead, where precise decisions feel too difficult, we can disagree and commit to a solution space. Remember our goal is not to exit initial planning with a complete solution worked out across every detail. Identify the "highest order bit", or the most crucial decisions (what programming language for this service? Am I building an online synchronous system, or an offline, batch oriented one?) and focus on having compelling reasons to believe you're on track there. These will also likely be the most painful and expensive to reverse.
In the end, our planning step should:
Document (publicly!) our problem space and any constraints, assumptions, or goals.
Name and empower a DRI.
Select a directional approach, focusing on answering the highest-order-bit problems with confidence and identifying a solution space we want to pursue.
Placing small bets
Enough planning—how do we execute? I love framing our approach as placing small bets. It brings front and center the fact that every choice we make is a risk, some will fail and not pay off, and we need to be thoughtful about how much we bet (in developer time and choice reversibility) on each task we pursue. A great read on this topic is “Thinking in Bets” by Annie Duke.
Our bets aren’t just any size though, I’ve emphasized “small.” Why? Small bets are inherently "simple" and concrete. They strip all the ambiguity and uncertainty of our broader problem space away and give us something we can execute on.
We also can deliver on small bets quickly—I like < 4 weeks. Shipping regularly is your team's heartbeat and we want to transform a messy problem into concrete execution. Get something in production, because, remember, we don't trust anything but real end to end feedback.
I also suggest these bets should always be “small” in delivery time, but don’t have to be “small” in scope. Some of the most important early decisions are often around architecture (datastore choice, picking a web framework). Use this same approach to get fast feedback on core design choices, building a prototype to feel out your hunch that MySQL will work better than Dynamo.
And as a bonus, we can parallelize our small bets simultaneously. Remember they're each uncertain—by analogy, we don't want to pick a stock, we want to invest across the market. When we see one of our bets work out, we can invest more effort in that direction and double down. Long term projects are iterative games, and we can use that to our advantage.
In practice, I like bets that exercise cheap, end-to-end spikes through projects. You might have heard this approach described as walking skeletons, spikes, or tracer bullets. Depending on what information would be valuable, you might even have one of your small bets be a throwaway prototype purely used to learn more about the problem space and what will or won't work.
So we select our small bets, and we're rigorous about keeping them that way, especially early. The simplicity of the bets give us needed concreteness so we can build, while framing them as bets reinforces the risk that they may not pan out, and we should hedge effort accordingly.
Using the outcome of our bets as feedback for future planning
Deng Xiaoping led China through economic reforms in the 1970s and 1980s, altering economic systems on an enormous scale. His approach was described with the phrase "crossing a river by feeling the stones", drawing a metaphor about crossing a dangerous river by taking incremental steps, observing where best to go next, and repeating until you reach the far shore.
This is the crux of our method: considering our small bets and using their results as feedback for our larger plan. We're playing an iterated game of many smaller plans, overlapping in time. This framing allows us to learn surprising information from our small bets, or adapt to a novel shift in the technical landscape we're working in, while still maintaining a coherent overall plan.
Our goal, as the project progresses through successive rounds of planning/experimenting/correcting, is that we find feedback on our bets is resulting in smaller and smaller modifications to our overall plan. This winnowing down builds confidence, reducing ambiguity and giving us momentum toward our final design.
As we get a better understanding of where complexity lies in our project, we can identify which aspects are simple and which deserve appropriate complexity. Good projects tend to either use boring technology and execute complex project plans, or rigorously keep a simple and obvious plan when introducing novel technology. We may not know which type of project we're running until a few early cycles through this feedback loop.
Learning to prefer Roofshots to Moonshots
What I appreciate about this strategy is it allows me to take on projects larger and grander than I feel like I can accomplish up front.
Tech has a habit of referring to sufficiently big efforts as "moonshots"—a slightly optimistic take on our own importance! This has a pernicious result, since we tend to talk about a moonshot as an enormous effort, often led by a charismatic leader, resulting in a big success or failure. It can even lead to really excellent engineers self-disqualifying, as the personalities who volunteer to lead such a glorious effort often also bias toward self-aggrandizing overconfidence.
But if you look into the actual history of the NASA moon program, it's really ahistorical to frame that as one of these big bang projects. In fact, NASA led a series of iterated programs (Mercury, Gemini, and finally Apollo), each lasting roughly five to 10 years and comprising multiple independent missions. Mercury began by asking "can we put a human into space", Gemini proved they could operate, maneuver, and walk in space, and finally Apollo accomplished the literal moonshot, 11 years after the first Mercury mission had launched.
I take heart that an iterated approach, with consistent goals over 11 years was able to accomplish all this. And it should encourage you that impossible goals can be achieved in this steady, incremental way. This concept even has a cute name—roofshots! Take a look at the Roofshot Manifesto and think about how you can break your next goal into a long series of incremental wins. You’ll still hit the moon, but have steady and incremental progress to de-risk your progress all along the way.
This approach to impossible decisions isn't magic, but it is repeatable. It's astonishing how many goals fall to the steady application of months or years of effort in a consistent direction, assuming you're capable and aware of adjustments you need to make along the way.
My advice for a younger version of myself, in moments where I felt something was too hard, or too unknown for me to tackle would have been this process:
Find a big, complex problem that you want to solve.
Live and breathe the problem until you see incremental next steps forward.
Try these steps, but keep it short. See how they went, and adjust as needed.
Repeat!
This is one of my favorite all-time lessons I've learned. The very act of attempting something teaches you how to succeed at it, and sometimes the best way to solve an impossible decision is to reject the idea you need to know the answer up front. Give it a shot (preferably with a few, small bets), pay attention to what is and isn't working, and repeat. It will help you approach the impossible safely, wisely, and hopefully successfully.