A feature request never arrives as a specification. It arrives as a sentence — "customers should be able to share reports" — and everything that makes it succeed or fail is still unsaid: who the customer actually is, what "share" means legally, how many of them will do it at once, and what happens to the system underneath when they do. The most consequential engineering I do happens in that gap, before any code exists, because that is the only moment when every decision is still free to change.

This article is the process I actually follow: what I do in the first meetings, the questions I refuse to skip, and how thinking about scale on day zero has kept the systems I ship boring under load — in the best sense of the word.

Line chart: the cost of changing a decision rises steeply across four moments — a five-minute conversation in the meeting, through design and build, up to an incident and a war room in production.
The same change, priced at four moments. The meeting is the discount window.

The first meeting is engineering#

Before implementing anything, I sit down with the people the feature belongs to — the product manager, the business side, whoever talks to the customer. Not to collect a ticket, but to interrogate the intent: what the customer is really trying to do, which parts are essential and which are one person’s guess at a solution. Half the value of that conversation is what I bring to it as an engineer. When a requested flow has a problem — an edge case that will confuse users, a compliance wrinkle, a dependency that makes it fragile — I say so there, and I usually arrive with an alternative: "the way you described it costs us X and breaks in case Y; this variant gets the same outcome without either."

The other half is surfacing technical limits early. Every stack has walls — rate limits on a third-party API, a query pattern the database will hate, a real-time requirement the current transport cannot honor. Naming those walls in the first meeting turns them into design inputs everyone plans around. Hiding them (or not knowing them yet) means the team discovers the wall mid-sprint, when the roadmap has already promised the feature to someone. I have never regretted stating a constraint too early; I have watched projects burn weeks because one was stated too late.

Questions I refuse to start without#

  • Who is the actual user of this, and what are they trying to accomplish — not what UI are they asking for?
  • What does success look like in a number we can check later? (Adoption, time saved, tickets avoided — anything falsifiable.)
  • What is the expected load at launch, and what is the honest ambition at 12–24 months — users, requests, data volume? A guess is fine; no answer is not.
  • Which parts of this are essential, and which are negotiable if they turn out to be expensive?
  • What existing behavior must not change while we build this?
  • What is the worst thing that happens if this ships a month later — and the worst thing if it ships wrong?
  • Which rare-but-real situations can hit this flow — and what should happen in each one? Decided with the stakeholders, out loud, before code.

The scale question deserves its own defense, because it is the one most often waved away as premature. I am not asking so I can build for a million users on day one — I am asking because a handful of early decisions are one-way doors, and which side of the door you want to be on depends entirely on where the curve is going.

Design on the growth curve, build for today#

Once there are rough numbers for the future, I sort every design decision into two piles. The first pile is the one-way doors: choices that are nearly free to make correctly now and brutally expensive to reverse once data and traffic exist. The second pile is everything else — and everything else should be built as simply as possible, because simplicity today is what funds the growth tomorrow.

Two panels: one-way doors to decide in week zero (tenancy model, stateless vs. session-bound, sync vs. async boundaries, ID and partition keys, hot vs. cold data split) versus reversible two-way doors added later (microservices, caches, read replicas, sharding).
Where the design effort goes — and what deliberately waits for the numbers.
DecisionCost in week 0Cost to retrofit at scale
Tenancy model (how customer data is isolated)A design discussion and a key on every tableRe-partitioning every table and every query while tenants are live
Stateless vs. session-bound servicesExternalize state from day one; scaling out stays a replica-count changeSticky sessions everywhere; horizontal scaling needs a re-architecture first
Sync vs. async boundaries (what happens on a queue)Choosing which calls may fail independentlyUntangling a call chain where every hop can take the whole flow down
Identifier and partition-key schemeA naming conventionA migration touching every stored row and every integrated client
Hot vs. cold data splitTwo storage paths and a rule for which is whichMoving history out of the operational database it is now drowning

Notice what is not in that table: microservices, caches, read replicas, sharding. Those are two-way doors — you can add them when the numbers demand it, precisely because the week-0 decisions left room. That is the discipline that separates designing for scale from over-building for it: I want the door open, not the mansion built.

Cloud-native is a cost strategy#

The reason I default to cloud-native shapes — containerized, stateless, orchestrated, config-driven — is not fashion. It is that they make the infrastructure bill track the user curve instead of leading it. A system built this way starts small and cheap: a couple of replicas, a modest database tier. When users arrive, capacity is a scaling event — more replicas, a bigger tier, an extra partition — executed without downtime and, crucially, without re-implementation. The alternative path is familiar and expensive: a system that was cheap to write but can only grow by being partially rewritten, with a migration window, a freeze, and a team doing archaeology on its own code.

Chart comparing two capacity strategies against a rising user curve: a flat dashed line of capacity bought up front that sits idle until users arrive, versus cloud-native capacity that steps up alongside the user curve.
The bill follows the curve: capacity added in steps as users arrive, instead of paid for up front.

This is also where the early stakeholder conversation pays for itself twice. Because the growth numbers were on the table in week 0, nobody has to buy capacity for a future that may not come — and nobody has to explain why the launch-day architecture collapsed under a success everyone claimed to expect. Systems designed this way tend to have quiet production lives: load grows, replica counts change, and the pager stays silent. Boring, at scale, is the achievement.

The question I struggle to answer#

People love to ask engineers about the biggest challenge they solved in the middle of a project. My honest answer usually disappoints: on most systems I have run, there wasn’t a dramatic mid-project story to tell — and that is the point. The dramatic challenge is usually the week-0 work, done early enough that it never gets to be dramatic. The scaling wall was designed around before launch. The third-party limit was known before the sprint was planned. The awkward edge case — the refund that arrives after the account is closed, the device that reconnects with a stale token — was walked through with product and support people in a meeting, where deciding its behavior cost five minutes. The same question discovered in production costs an incident, a war room, and a customer apology.

This is not luck, and it is not caution for its own sake. It is a deliberate trade: I spend uncomfortable amounts of time in conversations that produce no code, so that the code, once started, mostly proceeds without surprises. Firefighting makes better stories. Fire prevention makes better systems.

What I actually do, in order#

  • Meet the stakeholders before estimating anything. Understand the customer problem, not the requested solution.
  • Offer alternatives where the requested design has a cost the requester cannot see — with the trade-off stated, not just "no".
  • Put every known technical constraint on the table in that meeting, even the embarrassing ones.
  • Get growth numbers for 12–24 months in writing, even as ranges. Ranges beat silence.
  • Sort decisions into one-way doors and everything else; spend design effort only on the doors.
  • Build the simplest version that respects the doors — stateless, containerized, config-driven — and instrument it so the real curve is visible.
  • Scale when the numbers say so, as an operation, not as a project.
Features fail in the meeting room long before they fail in production — spend your judgement where changing your mind is still free.