ExperimentsGENERALILLUSTRATIVESTAGE-SPECIFIC

A Prototype Answers a Question

"Can the browser upload a five-gigabyte file straight to object storage without going through our server?" is a question. A prototype that does only that — and nothing around it — answers it in an afternoon. A prototype built to "see how the upload feature would work" answers nothing and takes a week.

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 are about to build a prototype. What question is it answering, and what is the smallest thing that answers it?

The situation

The store needs sellers to upload product videos, and some are huge. You are not sure whether the browser can send a file that size directly to object storage, or whether it has to pass through your backend, and the two designs are completely different. You have started a branch called upload-prototype, and it already has a form, a progress bar and a database table.

The reflex

Build a small version of the feature. The form, the progress bar, a row in the database, and — somewhere in there — the upload itself. It feels like the right shape: a prototype should look like the thing, so that people can see it working.

Why it stalls

The week goes on the parts that were never in doubt. The form and the progress bar are known technology; they take days and prove nothing, and the direct-to-storage upload — the only uncertain part — is still a TODO on Thursday.

What the reflex produces — and fails to produce
  • The week goes on the parts that were never in doubt. The form and the progress bar are known technology; they take days and prove nothing, and the direct-to-storage upload — the only uncertain part — is still a TODO on Thursday.
  • The prototype "works" for a small test file and the question is quietly assumed answered. Whether a five-gigabyte file survives — multipart, resumability, the browser's memory, the signed URL's expiry — was never tried, because the demo file was small enough to be convenient.
  • The branch looks like the feature, so it is treated as the feature. The database table designed for the prototype becomes the real one, and the polish that was skipped is now debt rather than a deliberate omission.
  • When someone asks "so can we do direct upload?", the honest answer is "probably" — the same answer as before the week.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

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

  • Before writing anything, write the question the prototype exists to answer, in one sentence, with a yes or no in it. "Can a browser upload a five-gigabyte file directly to object storage, with our server only issuing a signed URL, and can the upload resume after a dropped connection?" If the sentence has an "and how would it look", it is two questions, and the second is not a prototype question.
  • Then build *only* what the question needs and treat everything else as forbidden. No form — a file input and a button. No progress bar — a console line. No database — a log statement. The prototype is an instrument; every part that is not measuring something is weight.
  • Run it against the case the question names, not a convenient one. If the question says five gigabytes, the test file is five gigabytes; if it says resume, the network is cut in the middle. A prototype run against an easy case has answered an easier question.
  • When it answers, write the answer next to the question, together with what the prototype did *not* establish — cost at volume, behaviour on mobile, what the server does when the signed URL leaks — and then either delete it or keep it as documentation. A prototype that becomes the feature has lost the property that made it fast (Prototype vs Production).

The question, sharpened until it has an afternoon

The prototype starts with the question, and the question usually starts vague. The ladder shows the same uncertainty at three levels; only the last one can be answered by something built in an afternoon, and the reason is that it names the hard case and the boundary.

The upload question
vagueCan we do large video uploads?
betterCan the browser upload directly to object storage instead of through our server?
bestCan a browser upload a five-gigabyte file directly to object storage using URLs that our server signs, and resume after a dropped connection — so that our server never carries video bytes?

why The best form names the size (which forces multipart into view), the mechanism (signed URLs, which decides the server's role), and the failure case (resume, which is the part that breaks in the field). It can be answered with one endpoint, one page and one cut cable. The vague form can only be answered with a feature.

The instrument, as a slice

The prototype is a vertical slice with most of the layers deliberately hollow. What each layer does is the minimum the question needs; what the slice proves is the answer; what it does not prove is the list that the production design starts from. The doesNotProve is not a footnote — it is half the deliverable.

Direct-to-storage upload, as an instrument
Browser uploads a large file to object storage with a server-signed URL and resumes after interruption
  1. BrowserA file input and a script that splits the file into parts and uploads each to its signed URL; on restart, lists uploaded parts and sends the rest. No form, no styling, console output only.
  2. Our serverOne endpoint: start a multipart upload and return signed URLs for the parts; one endpoint: complete it. No auth, no product, no database.
  3. Object storageThe real provider, a real bucket, the real multipart API — because the question is about it.
  4. The testA five-gigabyte random file, the network cut at the halfway point, the script restarted.
proves
A browser can complete a large direct upload with our server only signing, and can resume from the provider's list of completed parts. Multipart is required above the provider's single-request limit, which the simple design had not anticipated.
does not prove
Cost at the expected monthly volume; behaviour on mobile browsers and their memory limits; what happens to abandoned parts (a lifecycle rule is needed); what a leaked signed URL allows; whether the upload can be tied to a product record safely when the server never sees the bytes.

Before and after the afternoon

The board shows what the prototype changed. Before it, one unknown; after it, one answer and three sharper unknowns — each already a question with an experiment. That is what a prototype is for: it does not eliminate uncertainty, it converts one vague unknown into several specific ones, most of which turn out to be searches rather than builds.

The upload feature, after the prototype
known
  • Direct upload with server-signed URLs works, with multipart above the single-request limit and resume via the provider's part listing.
  • Boundary decision: the server signs and completes; it never carries video bytes.
assumed
  • ~Sellers upload from desktop browsers in V1. Written down because the mobile case was not tried.
unknown → question → experiment
  1. ? What does it cost?

    becomes At the expected number of uploads and the expected average size, what is the monthly storage and transfer cost, and does the provider charge for abandoned multipart parts?

    experiment The provider's pricing page and one spreadsheet — a search, not a prototype.

  2. ? Abandoned uploads.

    becomes If a seller starts an upload and never completes it, where do the parts live, for how long, and who deletes them?

    experiment Start an upload, abandon it, read the bucket the next day; then find whether a lifecycle rule can expire incomplete multipart uploads automatically.

  3. ? Is a signed URL safe to hand to the browser?

    becomes What can someone who obtains a signed part URL do with it — overwrite, read, upload to another key — and for how long?

    experiment Take a signed URL from the prototype, try each misuse from a separate machine, and read the provider's documentation on what the signature covers.

One of three new unknowns needs anything built. The rest are reading. The prototype's answer made the reading targeted.

How to do it

Most important first.

  • Write the question first, as a sentence with a verifiable answer. If you cannot, you do not yet know what you are uncertain about; go back to the unknowns board (Unknown to Specific Question).
  • List what the question needs and cross out everything else. The crossed-out list is as important as the kept one; it is the thing you will be tempted to add on day two.
  • Name the hardest case the question implies and make it the test input. Big file, slow network, the provider's smallest instance.
  • Time-box it. A question this narrow has an afternoon's answer; if the afternoon ends without one, the question was wider than it looked, and the next step is to split it, not to extend the box (Spikes).
  • Record the answer and what it does not prove, in the notebook, before closing the branch (The Engineering Notebook).
  • Ask "which design does this answer commit us to?" — a yes to direct upload means the server issues URLs and never sees bytes, and that is a boundary decision worth writing down as such (Where Does My System End?).

Worked on a concrete problem

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

  • Question: "Can the browser send a five-gigabyte file straight to object storage using a URL our server signs, and resume after the connection drops?" What it needs: one endpoint that signs a URL, one page with a file input and a script that uploads in parts, and a way to cut the network. What it does not need: authentication, a form, a progress bar, a database, the product it belongs to.
  • Afternoon: the signed-URL endpoint takes an hour. The browser side takes longer than expected, because a single request for the whole file fails — the provider wants multipart for anything large, and the script has to split the file and upload parts with their own signed URLs. That discovery is the prototype earning its afternoon: the "simple" design had a hidden requirement.
  • The test: a five-gigabyte file of random bytes, network cut at the halfway point, script restarted. The parts already uploaded are listed from the provider, the rest are sent, the upload completes. Answer: yes, with multipart and a part-listing step. Recorded. Does not prove: what this costs per month at the expected volume, whether it works on a phone, what happens when a seller never finishes and the parts sit there forever.
  • The branch is deleted. The notebook has the question, the answer, the three things it did not prove, and a boundary decision: our server signs and never carries video bytes. The real feature is designed from that sentence, not from the prototype's code.

How you know it worked

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

  • The prototype has a question written above it, and someone who read only the question and the answer would know what was learned.
  • The prototype is embarrassingly small — no form, no style, no persistence — and finished in a fraction of the time the feature would take.
  • It was run against the hard case, and the run either surprised you or confirmed something you could not have confirmed by reading.
  • The answer is recorded with what it does not prove, and the branch is gone or marked as documentation.

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 is the one question this prototype answers, stated so that its answer is yes or no?
  • ?What is the smallest thing that could produce that answer, and what am I tempted to add that would not?
  • ?What is the hardest case the question implies, and am I running against it?
  • ?When it answers, what will it still not have proved?

What can go wrong

How the move itself fails
  • The question is too wide. "Can we do video uploads?" needs a product, not a prototype; the afternoon ends with a partial feature and no answer. Split until each question has an afternoon-shaped answer.
  • The prototype is run against a convenient case and its answer is generalised. The small file uploads; the five-gigabyte one was never tried; the design is committed and the multipart requirement is found in production.
  • The prototype is kept. It becomes the feature because it exists, and everything skipped — validation, auth, cleanup of abandoned parts — becomes a bug rather than a known omission.
  • Every uncertainty gets a prototype, including ones a page of documentation would settle. The move is for questions whose answer depends on trying; "does the provider support multipart?" is a search, and "does multipart resume work from this browser with our signing?" is the prototype.
What the move costs
  • A question-shaped prototype is ugly and demonstrates nothing to a stakeholder who wanted to see the feature. Sometimes a second, throwaway demo is the price of being allowed to build the instrument.
  • Running against the hard case is slower and more awkward than the convenient one — a five-gigabyte test file, a way to cut the network — and that setup is part of the afternoon.
  • Deleting a working prototype feels wasteful, and the pressure to keep it is real; the cost of keeping it is paid later and invisibly.
Misreads
  • "Prototypes should be throwaway code, so quality doesn't matter." Quality of the *instrument* matters — a prototype that leaks memory answers the wrong question about the browser. What does not matter is everything the question does not need.
  • "One prototype per feature." One prototype per uncertain question. The upload feature had one real uncertainty; a feature with three has three tiny prototypes, not one medium one.
  • "The prototype proved direct upload works." It proved direct upload works for one file, one browser, one network, one afternoon. The doesNotProve list is the honest scope, and the production design has to cover it.

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.

  • GENERALA prototype as an instrument for one question applies to any uncertainty that can be tried — a library's behaviour, a provider's limit, a browser capability, a query's plan; the discipline of crossing out everything the question does not need is the same in each.
  • ILLUSTRATIVEThe five-gigabyte video, the afternoon, the hour for the signing endpoint and the halfway network cut are invented to show the shape of the move; no provider's actual limits are being described.
  • STAGE-SPECIFICOn a greenfield feature the prototype is built in isolation from nothing; in an existing system the instrument is built *beside* the system — a script against the real bucket, a page outside the app — because embedding it in the app drags the app's concerns into the question.

Where the depth lives

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