Sarthak Garg

Simplicity as a leadership stance

Complexity wins by default. Make simplicity a team norm.

·8 min read·

"Keep it simple" is the first design advice most engineers hear, and as advice to one engineer making one choice, it works: when two designs solve the same problem, pick the one with fewer parts. The advice is old enough to have an acronym, KISS, keep it simple, silly.

The version of the advice I get the most use from separates simple from easy, because the two are constantly confused. Simple describes the design: few parts, each doing one job, so that changing one thing does not require holding the whole system in your head. Easy describes you: how familiar the design is, how quickly you could start. A framework the whole team already knows is easy but not necessarily simple, and a design with one fewer service is simpler but less familiar. The distinction matters in design conversations because "I prefer this one" usually means "this one is easy for me", and the better question is which design will be simpler to change a year from now.

Both pieces of advice speak to an engineer at the moment of a single choice. Neither explains the team where nobody chose complexity and the system got complicated anyway, one reasonable change at a time. That team is the situation a leader has to deal with.

The first useful thing a leader can do is name what is happening: complexity is the default outcome, and it arrives without anyone doing anything wrong. Two forces push it there.

The first force is incentive: in a promotion discussion, the work that gets named is the architecture someone designed and the platform someone launched; the service a team decided not to build never comes up, because there is nothing to show for it. Adding structure is visible work, avoiding structure is invisible work, and people understandably do the visible kind.

The second force is iteration: complexity arrives one change at a time, and each change is reasonable on its own: a flag for one customer, a special case that protects a launch date. I have approved many of them myself. No single change looks like the problem, so review after review passes, and the total is a system nobody would have designed on purpose.

Naming this matters because it takes the blame off the engineers: a good team under these two forces still drifts toward complexity if its leader stays neutral. And once the team can see why the drift happens, a leader can counter it deliberately at the places where complexity enters the system: the problem statement, the design review, and code review.

Shrink the problem before design starts

A problem never arrives at a team in a simple form; it arrives as a pile of requests and edge cases from several stakeholders, with the real business need buried under things somebody once mentioned in a meeting. Whatever statement of the problem the team receives is the one they will design against, and by the time that design reaches review, its structure is set; asking for a simpler version at that point means asking someone to take apart finished work, which costs goodwill and rarely succeeds. So the useful moment is earlier, and I treat it as my job to condense the pile into a simpler problem statement before the team starts designing, and to hand over that statement rather than the raw pile.

There is an old rule of thumb for this, Occam's razor: when several explanations fit the facts, prefer the one that assumes the least. The same rule applies to problem statements: among the versions that cover what the business needs, pick the one with the fewest assumptions. Cutting a request out of the problem statement is the same act as saying no on a roadmap, at a smaller scale.

The cut will sometimes be wrong, because nothing about condensing tells you which requests were noise and which were real. So cut visibly: a request leaves the problem statement by name, with a note about where it went, and a wrongly cut requirement can come back while the design is still easy to change.

Separate essential from accidental complexity

Sooner or later a complex design gets defended with the sentence "our domain is complex," and the defense deserves a serious answer, because it is half right. I lead teams building an accounting product; complexity keeps arriving as we build, quarter after quarter, over the same set of modules, and a real part of it belongs to accounting itself and cannot be designed away. I answer the defense with the old distinction between essential and accidental complexity. Essential complexity comes from the problem: the rules of accounting the product must honor no matter how the code is structured. Accidental complexity comes from our own choices: the extra service, the configuration flag nobody asked for. The domain is entitled to every bit of the first kind and none of the second.

The strongest objection to a simplicity push lands right here: essential complexity cannot be removed, only moved around, and a team that oversimplifies a genuinely complex domain builds a system that breaks on real cases. Both halves are true, but a hard domain also becomes a place for accidental complexity to hide, because an extra layer is easy to wave through when everything in the codebase is assumed to be hard. The most common form of this is relabeling: structure that shipped years ago and that everyone has learned to work around gets treated as essential, because the product is hard to imagine without it, but age does not turn accidental complexity into essential complexity. Holding that line in a review, against people who may know the code better than you do, while staying genuinely open to being wrong, is a discipline of its own.

Require evidence before building ahead

"How will this scale" is one of the better questions asked in design reviews, and on modules a team revisits every quarter, thinking ahead pays off: a module designed to be extended beats one designed as a dead end. But the question has no natural stopping point: asked without a constraint, it justifies building for futures nobody has committed to: an abstraction over a second data source that does not exist yet, a configuration surface for customers nobody has met. We have overbuilt this way, on the same modules where thinking ahead helped us, and we carried the extra structure for years afterward.

So I ask for evidence: a future you build for needs a name and a date, meaning a customer who has signed, or a trend in the numbers you can point to. When nobody can produce either, we ship the simple version and accept that we may need to rework it later, which is debt taken on purpose and usually cheaper than structure built for a future that never comes. The rule can harden into refusing every future, and then the quarter a real one arrives, the team is rebuilding in a hurry. Asking for evidence filters out guesses; a real future passes easily, because someone can point at the customer or the chart.

Hold the line in code review

Generated code changed the economics of all of this. On my teams the pull requests have gotten larger, the comments narrate every block, the code works, and it quietly duplicates things we already have, solving solved problems again instead of reusing what exists. None of that is an argument for banning the tools. Structure used to cost the effort of typing it; now it is close to free to produce, and more of it shows up at review every week than reviewers can push back on when the norm against it lives only in their heads.

Code review is the one place all of that new code still passes through a person, so that is where the norm has to be said out loud. In my reviews it takes the form of specific asks:

  • What would this look like with one fewer moving part?
  • This block re-implements a module we already have; reuse it.
  • These comments narrate the code; remove them.

The asks are about what merges rather than about how the author produced it, so a generated change that arrives simple merges without comment, and a hand-written change that adds a speculative layer gets the same pushback as a generated one. Reading generated code and running review when code is cheap to produce are subjects of their own.

This practice decays into style policing if I am not careful: comment format and naming are easy to flag, while structural duplication takes real reading to find, so a hurried reviewer flags the easy things and the expensive problems merge. The asks worth making stay structural: parts and duplication before anything cosmetic. This is also the newest of the four practices and the one I am least settled on; the asks above are what I use today, and I expect the list to change.

A stance like this can fail in two directions. One is giving up: if complexity wins by default, resisting it can feel pointless, but a drift with structural causes is exactly the kind that only stops when someone counters it deliberately. The other is overcorrecting: treating simplicity as an aesthetic and cutting complexity the domain genuinely needs, which leaves a brittle system and a team that hides difficulty rather than raising it. Between those two, the stance stays small and concrete: hold the line at the problem statement, at the design review, and at code review. Each is a specific meeting or a specific document, and the last of the three is the busiest one today.