First PrinciplesGENERALCONTESTEDILLUSTRATIVE

The Simplest Thing That Could Work

"Could work" is the half of the slogan people drop. The simplest thing that could work meets the real requirement — failure handling included — with the fewest parts; the simplest thing that does not is a demo. The move is finding the first, and knowing what evidence would make it insufficient.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

You have the real requirement. What is the simplest thing that could actually meet it — and how do you keep "simplest" from sliding into "incomplete"?

The situation

The why ladder said the email must be sent asynchronously. The simplest thing, someone says, is to fire the send in a background thread after the response and forget about it. It is three lines. It works on your machine every time.

The reflex

Ship the three lines. It is the simplest thing; the slogan says so; adding a table and a worker is exactly the over-engineering you have been warned about. It feels like discipline.

Why it stalls

The three lines meet the requirement "checkout does not wait" and miss the requirement "the email is sent eventually". A restart between the response and the send loses the email, silently, and the customer who never got a confirmation is the first to know.

What the reflex produces — and fails to produce
  • The three lines meet the requirement "checkout does not wait" and miss the requirement "the email is sent eventually". A restart between the response and the send loses the email, silently, and the customer who never got a confirmation is the first to know.
  • "Simplest" was measured in lines rather than in parts that could be wrong. The three lines have one part — a thread — and no way to tell when it failed; the table and worker have more parts, and each can be observed.
  • The requirement was quietly shortened to fit the implementation. "Sent eventually, at least once, failure visible" became "sent, usually", and nobody wrote the change down.
  • When the lost emails are noticed, the fix is bolted on — a retry loop inside the thread, then logging, then a restart-recovery hack — and the result is more complex than the table would have been, with none of its guarantees.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Start from the requirement with its failure clauses, not from the happy path. "Send the email asynchronously" is a happy path; "the email is sent at least once even if the process restarts, and a failure to send is visible" is the requirement. The simplest thing has to meet the second.
  • Count parts that can be wrong, not lines. A thread that can die silently is one part with one invisible failure. A table row and a worker are more lines and two observable parts: the row is either there or not, and the worker either marks it done or does not. Simplicity is about how many ways the thing can be wrong and whether you would know.
  • Find the fewest parts that meet every clause, and write what each clause is met by. Durability: the row is written in the order's transaction. Eventually: the worker polls. At least once: the row is marked done only after the send returns. Visible: rows older than a limit raise an alert. Nothing else is added.
  • Write the evidence that would make this insufficient. Polling contention at some measured volume; a second consumer; a need for replay. Then the simplest thing is a decision with an expiry, not a permanent architecture, and "could work" is re-evaluated when the evidence arrives (Growing From the MVP).

Three candidates against four clauses

The requirement with its clauses, and each candidate scored on whether it meets them and on how many ways it can be wrong without anyone noticing. The caveat matters more than usual here: the numbers are a way of writing the argument down, not a way of settling it.

Sending the confirmation email
OptionSimplicityReliabilityCostMaintainabilityNote
Thread after responseThree lines; one part; its failure is invisible; meets one clause of four.
Outbox row + polling workerTwo observable parts; meets all four clauses; polling has a measured ceiling.
Message brokerMeets all four; adds a system to run; earns it when a second consumer or replay appears.

caveat Simplicity is scored by parts that can be wrong invisibly, which is a judgment rather than a count; another engineer could score the thread a 2. Reliability scores assume the clauses as written — for a requirement without the durability clause, the thread's reliability is fine. The matrix argues; it does not decide.

The simplest thing, as a slice

Candidate B through the layers it touches, and what a passing slice does and does not establish. The doesNotProve list is the expiry written the other way round: the things this slice leaves open are where the evidence for insufficiency would appear.

Confirmation email via outbox
Send the order confirmation asynchronously, at least once, visibly
  1. Checkout handlerInserts the order and an outbox row in one transaction; responds.
  2. DatabaseHolds the row with status, attempts and created-at.
  3. WorkerPolls pending rows, sends, marks done on success, increments attempts on failure.
  4. AlertFires when a row exceeds the attempt limit or age limit.
proves
A restart between response and send loses nothing; a provider failure is retried and, if persistent, visible; the customer never waits on email.
does not prove
That the same email is never sent twice — a send that succeeds and a mark-done that fails will resend, so at-least-once is what this is; that polling scales past the volume you tested; that a second consumer could share the table without contention. Each is an unknown with a probe, not a reason to build the broker today.

Which candidate meets which clause

The check that keeps "simplest" honest, in pseudocode: the requirement as a list of clauses, and each candidate mapped to the parts that satisfy them. A candidate with a blank is not simpler; it is incomplete.

Clause check
1requirement "confirmation email":
2 C1 checkout responds without waiting on email
3 C2 email is sent at least once
4 C3 a process restart does not lose it
5 C4 a persistent failure to send is visible
6
7candidate "thread after response":
8 C1 <- thread C2 <- (none) C3 <- (none) C4 <- (none)
9 => does not "could work"; matches a requirement with only C1
10
11candidate "outbox row + worker":
12 C1 <- respond after insert
13 C2 <- worker retries until send returns ok
14 C3 <- row is in the order's transaction
15 C4 <- alert on attempts > limit or age > limit
16 => simplest thing that could work
17 expiry: second consumer of order events; measured polling contention

The blanks are the whole lesson. The thread was never simpler; it was answering a smaller question.

How to do it

Most important first.

  • Write the requirement with every clause the failure modeling produced. Underline the clauses; the simplest thing must satisfy each.
  • For each candidate, list the parts that can be wrong and, for each, whether you would know. Prefer the candidate with the fewest invisible failures, not the fewest lines.
  • Write next to the chosen candidate which part meets which clause. An unmet clause means it does not "could work".
  • Write the evidence that would make it insufficient and where you would look for it.
  • Make the slogan precise before using it: "the simplest thing that could work" means the fewest parts that meet the whole requirement, failure clauses included — never the fewest lines that pass the demo.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The email. Requirement, with clauses: checkout responds without waiting; the email is sent at least once; a restart does not lose it; a failure to send is visible. Candidate A, the thread: meets the first clause; fails the next three, invisibly. Candidate B, a row in the order's transaction and a polling worker: meets all four, with the alert covering the last. Candidate C, a message broker: meets all four and adds a system to run. B is the simplest thing that could work. Evidence that would make it insufficient: a second consumer of order events, or measured polling contention.
  • The URL shortener, as a contrast. Requirement: a short code maps to a URL; the mapping survives a restart; the same long URL may get different codes. Simplest thing that could work: a table with a unique code column and a random code generator that retries on collision. The candidate people reach for — a distributed id generator — meets the requirement and adds a coordination problem the requirement never asked for. Evidence that would make the table insufficient: measured collision retries or write throughput beyond one database.
  • The three-line thread, revisited honestly: it is the simplest thing that could work for a requirement without the durability clause — a "we are online" ping that nobody would miss. The move did not reject it; it matched it to the requirement it actually meets.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • Every clause of the requirement is next to the part that meets it, and there is no part without a clause.
  • You can list the ways the chosen thing can be wrong and, for each, how you would find out.
  • The candidate with fewer lines was rejected for a reason you can state in terms of an invisible failure.
  • The expiry is written: the evidence that would make this insufficient, and where it will show up.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?What are all the clauses of this requirement, including the failure ones — and does the candidate meet each?
  • ?How many parts of this can be wrong, and for each, would I know?
  • ?Which part meets which clause, and is any part meeting no clause?
  • ?What evidence would make this insufficient, and where would it appear first?

What can go wrong

How the move itself fails
  • Simplest by lines. The thread wins because it is shorter, and the missing clauses are discovered in production. Count parts and invisible failures.
  • Simplest by familiarity. The broker wins because the team knows it, and "simplest" is redefined as "least new to us". That is a legitimate cost — it belongs in the ledger as operating cost — but it is not simplicity.
  • The requirement is trimmed to fit the candidate. "Sent eventually" becomes "sent, usually" without anyone saying so. Keep the clauses written and check the candidate against the list, not against memory.
  • No expiry is written, so the simplest thing becomes the permanent thing and is defended past the evidence that should have replaced it. Simplicity that ignores the measurement is the mirror image of complexity that precedes it.
What the move costs
  • The simplest thing that could work is more work than the simplest thing that runs. The table and worker are an afternoon; the thread was three minutes. The afternoon buys the three clauses the thread dropped.
  • Writing the expiry commits you to watching for it, and when it arrives, to a migration you could have avoided by building the bigger thing first — if you had known. Usually you did not.
  • Counting invisible failures is a judgment, and two engineers will count differently. The disagreement is useful; it is where the requirement gets clarified.
Misreads
  • "Simplest means fewest lines." Fewest parts that can be wrong without you knowing. Three lines with one silent failure are not simpler than thirty with none.
  • "This contradicts MVP thinking." An MVP simplifies scope, not correctness within the scope. The email in V1 might be optional — that is scope. If it is in scope, it is sent at least once — that is the requirement (MVP vs Bad Prototype).
  • "The simplest thing is always a database table." It often is for a small system, because the database is already there and already durable. It is not for a requirement the database cannot meet — fan-out to many consumers, replay — and the ladder finds those.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALClauses, parts, invisible failures and an expiry apply to any implementation choice, from a background job to a data structure.
  • CONTESTEDThe strongest opposing view: the simplest thing that could work should be judged only against today's requirement, and writing an "expiry" is exactly the speculative design the slogan warns against — you cannot know which evidence will arrive, and planning for it biases the design toward the bigger thing. Build the table; when it fails, you will know more than any expiry could have predicted. The reply is that the expiry is not a design, it is a sentence about where to look; the disagreement is about whether that sentence changes what gets built.
  • ILLUSTRATIVEThe three-line thread, the afternoon and the URL shortener's collision retries are invented to show the shape of the comparison; measure the real system before calling anything insufficient.

Where the depth lives

This domain asks the question and hands the answer off by name.