PerformanceGENERALCONTESTEDLIFETIME-SPECIFIC

Premature Optimization, Reclaimed

The quote is about small efficiencies and it is routinely used to dismiss all performance thinking. Structural cost decisions are not premature; micro-tuning without measurement is.

The requirement, the obvious build, and why it breaks

Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.

The question

When is thinking about performance premature, and when is "that is premature optimisation" being used to end a conversation that should be had?

The requirement

In a design review, someone asks how many queries the new report page will issue. The answer is "we should not worry about that yet — premature optimisation is the root of all evil", and the review moves on.

The obvious build

Do not think about performance until you have measured. It is the most-repeated engineering advice there is, it comes from Knuth, and following it prevents an enormous amount of wasted effort on code that turns out not to matter.

Why it breaks

It is excellent advice about implementation and silent about structure, and the slogan does not carry the distinction — so it gets applied to both, and the half it is wrong about is the expensive half.

How it breaks as requirements change
  • It is excellent advice about implementation and silent about structure, and the slogan does not carry the distinction — so it gets applied to both, and the half it is wrong about is the expensive half.
  • It cannot be followed for decisions made before there is anything to measure. "Measure first" is not actionable when the question is which interface to publish, and the default answer becomes whatever is quickest to type.
  • It ends conversations. Once invoked, the burden of proof lands on the person asking a design question, and they have no number, because the system does not exist yet.
  • The full quote says the opposite of the way it is used: it explicitly reserves a critical fraction where optimisation is exactly right, and it is followed by an argument *for* measurement, not against thinking (the reading that survived is Observability & Performance's: measure first).
RequirementConstraintsInvariantsResponsibilitiesBoundariesInterfacesStateDependenciesFailureImplementationTestsFeedbackEvolution

What limits the solution, and what must never stop being true

This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.

Constraints
  • Both people in that exchange are experienced and both are partly right, which is why the argument never resolves.
  • There is no production data, so the question genuinely cannot be settled by measurement today.
  • The report interface will have callers within a week, after which changing its shape is a multi-file change (The Cost of Change).
Invariants
  • A decision is only deferrable if deferring it is actually cheaper than making it — that is the test, and it is testable in advance.
  • A claim about performance without a measurement stays a claim, including when it is a claim that something is fast enough.

Who owns what, and where the seams fall

Responsibilities decide boundaries; boundaries decide what an interface has to say.

Responsibilities
  • Whoever invokes the slogan owns saying which category the decision is in — a local implementation detail, or a shape that binds callers.
  • Whoever proposes an optimisation owns naming what it makes cheaper, in the same way structural proposals must (The Cost of Change).
  • The design review owns asking the counting questions — how many round trips, what bounds this — because those are answerable by reading and cost nothing.
Boundaries
  • The line is reversibility, not effort: if being wrong is fixed inside one function, defer it; if being wrong is fixed by changing every caller, decide it (Designing for Cost).
  • The second line is measurability: a decision that can be settled by measurement later should be; a decision that forecloses the alternative cannot be settled later at all.
  • Neither line is about how much the code will be executed, which is the axis people usually argue on and the one they cannot know yet.

The sentence, with the parts that get dropped

The line comes from Knuth's 1974 paper "Structured Programming with go to Statements", which is worth knowing because it means the surrounding argument is available and it is not the one the slogan implies. The paper is, among other things, an argument for looking at generated code and for measuring.

Two qualifiers do all the work and both are usually dropped. "Small efficiencies" says what is being warned against. "That critical 3%" says there is a fraction where the opposite advice applies, and that finding it is a skill rather than a vice.

  • The warning is about *small* efficiencies — instruction counts, loop tricks, the things a profiler is designed to find.
  • Nothing in it addresses interface shape, data model, or how many times a boundary is crossed, because those are not efficiencies, they are structure.
  • The advice it leads to is "measure", which is a call to gather evidence, not a licence to skip the question (Observability & Performance gives that its full treatment).
  • Used as a conversation-ender it inverts the burden of proof onto the person who asked a structural question and has no number available (Tone, Disagreement and Receiving Review).
"We should forget about small efficiencies, say about 97% of the time:
 premature optimization is the root of all evil. Yet we should not pass up
 our opportunities in that critical 3%."
                        — Knuth, "Structured Programming with go to Statements", 1974

what it says                          how it is used
-------------------------------       --------------------------------
small efficiencies                    any performance consideration
97% of the time                       always
not pass up the critical 3%           (dropped)
followed by: so measure               so do not think about it

Two decisions the slogan treats identically

Here are two things a reviewer might flag on the same pull request. Under the slogan they get the same answer. Under the reversibility test they get opposite answers, and the reason is nothing to do with how hot the code is.

The report must cover twelve months instead of thirty days
The change

Product extends the reporting window, so the same code now processes an order of magnitude more rows per request.

Hand-tuned aggregation loop over an unbounded read
ReportServiceReportRepositoryReportControllerExportJobDashboardPanel
testsreport_service_testrepository_testcontroller_testexport_test
5 modules · 4 test files

The loop was optimised early — a preallocated buffer, no intermediate arrays — and that work is untouched by this change and did nothing for it. The read had no bound, so the fix is to add one, and the return type is an array that four callers already consume.

Bounded, aggregate-in-store read behind an interface that takes a window
ReportRepository
testsrepository_test
1 module · 1 test file

The window was already a parameter and the aggregation already happened where the rows live, so extending it is one query change. Nothing about the loop mattered either way — which is the point: the structural decision was the one that paid, and it was made without a single measurement.

what it cost The bounded design was more code on day one, and it committed to aggregating in the store — which is now hard to change if the aggregation needs logic the query language cannot express. It also cost the team an argument in review, on a page that at the time had eleven users. That argument is the actual price of this lesson, and it is worth being clear that you pay it every time, including the times you turn out not to have needed the structure.

Answering the objection in one question

SIMPLIFIEDReal reviews mix categories in one change — a bounded interface with a hand-tuned body — and the useful move is to split the pull request rather than to give the whole thing one verdict; the five-option framing is a teaching device, not a triage process.

When the slogan appears in a review, the productive move is not to argue about Knuth. It is to ask what fixing this later would touch, which is a concrete question about the codebase that both people can answer together.

The options below are the answers you get, and each one has a clear disposition. Note that three of the five say "defer" — this test is not a licence to optimise, it is a way to find the small number of decisions that genuinely cannot wait.

"That is premature optimisation." Is it?

If this turns out to be wrong, what does the fix touch — and can measurement even reach it?

One function body, measurable later

when A loop, a serialiser, a hash choice, an allocation.

cost Defer, and say so plainly: this is the case the slogan is about and acting now is a pure loss (Allocation and Copies).

One module, interface unchanged

when Aggregation strategy, an internal cache, a batching detail.

cost Defer, and keep the seam that makes it changeable. The design work is the seam, not the optimisation (Encapsulation Radius).

Every caller of an interface

when Whether a read is bounded, whether an operation is set-shaped, whether a signature can express a limit.

cost Decide now. It is not an optimisation, it is an interface, and interfaces are the thing this domain says to design deliberately (Cost-Aware Interfaces).

The data model or the write path

when Denormalisation, precomputed rollups, append-only history, tenancy as a column.

cost Decide now, and usually decide against the optimisation while keeping the option open — this is the category where retrofitting means migrating stored data (Data Migration).

Nobody knows, because the path may not exist

when A feature that might be built, a scale that might arrive.

cost Defer, and write down the trigger that would reopen it. An unnamed future is the definition of speculative (Revisit Triggers).

How to build it

Most important first.

  • Separate the two questions explicitly in review: "is this shape right" and "is this code fast". The first is a design question with no measurement available; the second should not be asked yet.
  • Ask the counting questions, which are free: how many round trips, what bounds the work, how much data crosses each boundary. None of these require a profiler or a guess (N+1 as a Design Problem).
  • Refuse micro-optimisation without a measurement, firmly. That is the part of the slogan that is entirely correct and worth defending (Observability & Performance owns doing it properly).
  • Where a structural decision is contested and expensive either way, write it down with a revisit trigger instead of arguing it to conclusion (Revisit Triggers).
  • Prefer the design that keeps the measurement possible later: an interface that can express a bound can be tuned; one that cannot has to be replaced (Reversible and Irreversible Decisions).
  • Stop quoting the sentence. Say which of the two things you mean — it takes the same number of words and does not import an argument from 1974 into a question about a repository method.

What the next change costs

The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.

Cost of the next change
  • Deferring a micro-optimisation: costs nothing. The code is local, the tests protect it, and a profiler will point at it if it matters.
  • Deferring a structural decision: costs a multi-caller migration if it turns out wrong, done later, usually under pressure, and usually in the same week as the incident that revealed it (Change Amplification).
  • Making a structural decision too early and wrongly: costs an abstraction that has to be threaded through, and it is the cost the slogan correctly warns about — this lesson does not claim the risk is one-sided.
  • The asymmetry, which is the whole argument: the two branches are not equally recoverable, and "wait and see" implicitly assumes they are.
What the recommended approach costs
  • Reclaiming the quote licenses more up-front performance discussion, and some of that discussion will be wasted on paths that never get hot. That waste is real and it is the price of the position.
  • The reversibility test is a judgement call that reasonable engineers apply differently, so it does not end disagreements — it relocates them to a more productive question.
  • Insisting on the distinction makes design review slightly more adversarial, and on a small, fast-moving team that friction can cost more than the structures it saves (Tone, Disagreement and Receiving Review).

What can go wrong

Failure modes
  • The reclaimed version becomes its own slogan and is used to justify exactly the speculative optimisation the original warned about — now with a respectable-sounding argument about structure (Speculative Generality).
  • A team decides everything is structural and designs for scale it never reaches, which is the failure the slogan exists to prevent and it is a real one.
  • The counting questions become a checklist and get answered "fine" without anyone counting (A Review Checklist Worth Reading).
  • Someone optimises the structure and neglects to measure afterwards, so nobody learns whether the shape mattered — and the next argument has no more evidence than this one did.
Dependencies, and their direction
  • The distinction depends on knowing how many callers an interface will have, which is a guess — but a much better-informed guess than a guess about hot paths.
  • Deferring decisions depends on having tests that make later change safe. Without them, "we can change it later" is an assertion nobody has checked (Refactoring Without Tests).
  • Treating structure as decidable now depends on the requirement being roughly known; for a genuine spike, the slogan is right about everything (Design for the Known, Name What You Assumed).
Misreads
  • "So Knuth was wrong." He was not; he was writing about small efficiencies in code, said so explicitly, and reserved a critical fraction where optimisation is right. The misuse is entirely downstream of dropping the qualifiers.
  • "This means design for scale up front." It does not. Almost every scale-oriented structure a small team builds is wasted, and this lesson's test — reversibility — rules most of them out (YAGNI, With Its Bill Attached).
  • "Structural means important." Structural means it binds callers. A structural decision on a page nobody loads is still cheap to get wrong, and effort should follow consequence (When Design Does Not Pay).
  • "We can always add caching later." Caching later is a consistency decision, not a performance patch, and the systems where it is easiest to add are the ones whose read paths were already bounded and owned (Consistency Boundaries).
Smells this explains
  • speculative-generality
  • shotgun-surgery

Testing it, and how it ages

What to test, and at which boundary
  • Structural properties belong in the normal suite: query counts, memory bounds, round-trip counts. They are deterministic and they catch the regression that matters (What a Unit Is).
  • Micro-optimisations belong in a benchmark harness with a baseline, and a change with no baseline is not an optimisation, it is a rewrite with an opinion (Observability & Performance owns the harness).
  • Test that the design still permits the deferred decision — that a bound can be added without changing the return type, for instance. That is the assertion "we can do it later" is actually making.
How this design ages
  • The slogan will keep being quoted, so the useful skill is answering it in one sentence: "this is not a small efficiency, it is an interface shape, and it binds callers".
  • As a system matures, the balance genuinely shifts toward measurement, because there is data and the structures are already committed. The reclaimed reading matters most in the first year and least in the fifth.
  • Teams that keep both halves eventually stop having the argument at all, because "how many round trips" becomes a routine review question rather than a challenge to someone's judgement (Review as Design Feedback — and Why It Arrives Too Late).

Where this applies

This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.

  • GENERALThe reversibility test — one function versus every caller — is about the structure of change and applies in any language, at any scale, to decisions that have nothing to do with performance as well.
  • CONTESTEDThe strongest opposing view, stated properly: the slogan earns its status because engineers are demonstrably bad at predicting where cost will land, and every licence to think about performance early is used mostly to build the wrong thing. On this view even the "structural" carve-out is dangerous, because almost any decision can be argued to bind callers, and the discipline of building the simplest thing and fixing what measurement finds produces better systems and better engineers than the discipline of forecasting. The honest answer is that this is largely right, and that its exceptions are the specific decisions measurement cannot reverse — which is a small list, and anyone using this lesson to justify a long one has misread it.
  • LIFETIME-SPECIFICFor a prototype or a campaign with a known end date, the slogan is correct without qualification, because there is no "next change" for the structure to be expensive for; the reclaimed reading only pays where the code has to keep absorbing requirements.

Where the depth lives

This domain teaches the codebase-level structure and hands the rest off.

Databasequery-tuning
Domains that do not exist yet
  • Testing & Reliability Engineering — the claim "we can change it later" is a claim about the safety net, and it is only true where a test suite would notice the behaviour change; without one, every deferred decision is deferred to a riskier moment than the team thinks.