MVP vs Bad Prototype
An MVP simplifies scope and keeps correctness where the idea lives; a bad prototype simplifies correctness and keeps scope. The first tests an idea; the second tests whether customers notice.
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.
My V1 is small — but is it small in the right places, or have I cut the parts that make the result mean anything?
We shipped a lean V1 in a hurry: products, cart, checkout, payment. The payment handler trusts whatever the browser sends as the total, passwords are stored as they were typed, and a double-click on Pay makes two orders. Someone said it was fine because it is an MVP. I am not sure that word covers this.
Ship it and fix it after. Real users are the whole point of an MVP, and every hour spent on hardening before launch is an hour of feedback delayed. "Security doesn't matter, it's an MVP" is said with a straight face, because the alternative sounds like gold-plating.
The feedback arrives, and it is about the bugs. Users who were double-charged do not tell you whether they liked the product; they tell you they were double-charged. The signal the MVP existed to collect is buried under the noise it created.
- The feedback arrives, and it is about the bugs. Users who were double-charged do not tell you whether they liked the product; they tell you they were double-charged. The signal the MVP existed to collect is buried under the noise it created.
- The shortcuts are in the parts hardest to change later. A trusted client-side total is a line of code today and a fraud incident with a refund queue behind it later; plaintext passwords are a breach notification. These are not deferred features; they are deferred disasters, and the deferral is invisible in the demo.
- The team learns that "MVP" means "the rules are off", and the next V1 cuts deeper. Nothing in the word says which cuts were scope and which were correctness, so the distinction has to be taught by the first incident.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Sort every shortcut into one of two bins before it is taken. *Scope* shortcuts remove a capability the path does not need — no coupons, one currency, filter-by-name. *Correctness* shortcuts keep the capability and make it wrong sometimes — a total the server does not check, an order that can be created twice, a password anyone with the database can read. MVPs take the first kind freely; "it is an MVP" is not a reason for the second kind, and the slogan is false exactly when it is used that way.
- Ask of every correctness shortcut: what does the idea look like if this fails in front of the user we are testing on? A double charge does not test the idea; it ends the test. That is the difference between a simplification that preserves the proof and one that destroys it (What Cannot Be Simplified).
- Where correctness is expensive to build, the MVP move is to *buy or borrow* it, not to skip it: a hosted payment page instead of a card form, a managed identity provider instead of a password table, the database's own constraint instead of hand-written checking. Scope stays small and the invariant stays true (The Build-vs-Buy Questions).
- A prototype that is deliberately wrong is a fine tool — a fake payment step, hard-coded products, a UI over no backend — as long as it never meets a real user with real money. The failure is not the prototype; it is calling it an MVP and launching it (Prototype vs Production).
Two shortcuts that look the same in the demo
Both versions below are "small". One removed something the path did not need; the other kept the capability and removed the part that made it true. The engineering reason for preferring the second is not tidiness — it is that the first one cannot be used to learn anything.
Checkout takes the total from the request body, creates an order on every click, and stores the card details "for now". Scope is full: coupons, wishlist and a search box are in.
Checkout recomputes the total from stored OrderItem prices, uses an idempotency key so a repeated click returns the same order, and hands the card to the provider's hosted page. Coupons, wishlist and search are not in.
The idea being tested is "people will pay us for these products". The first version cannot produce that evidence — a double charge or a manipulated total ends the test — while the second produces it with less code, because the code it lacks was never on the path.
How the correctness shortcuts fail
The table is the argument against "fix it after": each row is a shortcut that looks like a scope cut and is not, with the failure it produces in front of the exact user the MVP was built to observe.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Server trusts the client's total | Orders paid for less than their contents; a fraud pattern appears in the first week | Price is an input rather than a fact the server owns | Recompute from stored prices; the client total is for display only |
| Double-click on Pay | Two orders, sometimes two charges, one angry email | Checkout is not idempotent | Idempotency key per checkout attempt; a repeat finds the existing order |
| Passwords stored as typed | Nothing, until the database is read by someone who should not | Identity was treated as scope | Managed sign-in or a proper hash; better, no password table in V1 |
| Messages held in memory | Every conversation vanishes on deploy | Persistence was deferred as if it were a feature | A table, on day one; it is the smallest persistence there is |
| Stock never decremented | Orders for products that do not exist; refunds | The invariant "sold ≤ stocked" was never written | Decrement in the order transaction with a check constraint |
The slice that keeps its correctness
A vertical slice of checkout shows how thin the MVP can be while the invariants stay in. The layers are few; what matters is where the truth lives at each one. And the slice is honest about what it does not establish, which is most of what a launch will teach.
- PageShows the cart and a Pay button; sends the cart id and an idempotency key, never the total.
- APIPOST /checkout looks up the cart, rejects an empty or stale one, and returns the existing order if the key was seen before.
- LogicBuilds OrderItems from current prices, computes the total, decrements stock, all in one transaction.
- ProviderHosted payment page in test mode; our backend learns the outcome from the provider's callback, not from the browser.
- DatabaseOrder with status; OrderItem with the price paid; a stock check constraint.
How to do it
Most important first.
- List every shortcut you intend to take, and label each S (scope) or C (correctness). If you cannot label it, it is C.
- For each C, name the invariant it breaks — "an order is paid at most once", "a total is computed from stored prices" — and either restore it, buy it, or move the shortcut into a prototype that no real user touches (What Must Never Break).
- Write the test for each invariant you keep before writing the feature; in an MVP these are the only tests that must exist (Invariants as Tests).
- Where money, identity or irrecoverable data is involved, use the provider or the database feature that already gets it right, and spend your own time on the idea.
- Put the prototype and the MVP in different places. A throwaway that fakes payment lives in a branch or a folder named for what it is, not behind a feature flag in production (A Prototype Answers a Question).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- The store's shortcuts, sorted. S: one currency, fixed shipping, no coupons, filter-by-name, no order editing. C: total trusted from the client, orders created on every click, passwords in plaintext, stock never decremented. The first list is the MVP. The second list is the reason it will fail its own test.
- Fixing the C list without growing scope: the server recomputes the total from OrderItem prices it stored itself; checkout carries an idempotency key so a repeated click finds the existing order; identity goes through the payment provider's hosted page and a managed sign-in, so no password table exists in V1 at all; stock is decremented in the same transaction that creates the order, with a check constraint that it never goes below zero.
- The chat app version. S: no attachments, no read receipts, no typing indicator, no search. C: messages stored only in memory ("we will add the database later") — which is a data-loss shortcut, not a scope one, and turns the first server restart into the end of the test.
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every shortcut has a letter next to it, and the C column is empty or consists of things a provider now does.
- The MVP has a short list of tests, and every one of them is an invariant rather than a feature.
- The first real user's feedback is about the product, because nothing about the money or the account went wrong.
- There is a prototype somewhere that is deliberately wrong, and it is labelled and unreachable from production.
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.
- ?Is this shortcut removing a capability, or keeping a capability and making it wrong sometimes?
- ?If this shortcut fails in front of the user we are testing on, does the test still tell us anything?
- ?Which invariant does this shortcut break, and can I buy or borrow the thing that keeps it instead of skipping it?
- ?Which of my artefacts is a prototype that must never meet a real user, and is it labelled and separated?
What can go wrong
- Every shortcut becomes a C, and the MVP grows a security review, a threat model and a retry framework before the first product is listed. The bin exists to protect the invariants the idea depends on; a review-page typo is scope.
- Correctness is bought and then not understood. A hosted payment page still needs your backend to learn, from the provider and not from the browser, that payment happened; delegating the card form does not delegate that question.
- The prototype and the MVP merge. A "temporary" fake payment step behind a flag ships when the flag is misconfigured, and the deliberately wrong thing meets the customer.
- Keeping correctness costs the MVP time on the parts that are least visible in a demo, and stakeholders judge demos.
- Buying correctness (hosted checkout, managed identity) trades a code shortcut for a dependency that can fail and a provider whose model you must still understand.
- A separate prototype means building some things twice: once to learn, once to keep.
- "So MVPs need production-grade everything." No — they need the invariants the *idea* rests on. A store can launch without rate limiting; it cannot launch charging the wrong amount.
- "Using a provider means correctness is handled." The provider handles its part. Whether your order becomes paid exactly once, and who tells it so, is still your invariant.
- "A prototype is a bad MVP." A prototype is a different tool with a different question — "can this be done?" — and it is bad only when it is launched.
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.
- GENERALThe scope/correctness distinction holds for every system; what counts as the load-bearing invariant changes with the domain.
- DOMAIN-SPECIFICFor a store the untouchable invariants are money, identity and stock. For an internal dashboard with read-only data, almost every shortcut is scope, and "fix it after" is often right. For a health or finance product, the C list includes audit and retention from the first day.
- ILLUSTRATIVEThe lean V1 and its shortcuts are invented; any numbers are for the shape of the argument.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The manifesto's delegation cards at /manifesto/delegating are the checklist for "buy the correctness": each card says what a provider handles and what remains yours.