Planning before you write code#

← Back to Week 2: Foundations II and first project

From one sentence to a feature list, then a cut#

Nobody hands you a spec in real life. Someone says "I want an app to keep track of my recipes," or "I want a smart contract that does X," and the actual first job, before any code, is turning that sentence into a written list of concrete features.

For a recipe app, that list might be: add a recipe, view all recipes, view one recipe, edit a recipe, delete a recipe, tag recipes by cuisine, scale a recipe's quantities up or down, plan a week of meals from saved recipes. Notice how fast that list grows. A one-sentence idea almost always expands into more features than you could reasonably build first, and the actual skill isn't generating that list (most people can brainstorm features), it's cutting it.

Split the list into two: what you're building now, and what can wait. For the recipe app, add/view/delete is a real, usable app on its own; edit, tags, scaling, and meal planning are all real improvements that can come later, once the core thing exists and works. This isn't a permanent decision, it's a first cut, made on purpose, written down, instead of quietly trying to build everything at once and running out of time before any of it is finished.

You already did a version of this without being told: exercises/06_recipe_box_persistence.py only ever asked you to implement GET /recipes and POST /recipes, list and create, nothing else. That wasn't an arbitrary limitation. It's the same "now" list a real planning pass would have produced, decided for you in advance so the exercise could stay focused on persistence specifically.

The data model comes before the code#

Once you know roughly what you're building, the next decision, still before any code, is: what does the one central thing in your app actually look like as data?

For the recipe app, that's: what is a recipe? Look at what the exercise actually gave you:

{"id": 1, "title": "Scrambled eggs", "ingredients": ["eggs", "butter", "salt"]}

id, assigned automatically. title, a string. ingredients, a list of strings. That shape wasn't arbitrary either, and it wasn't obvious until someone decided it. Every route you write afterward is just an operation on that shape: GET /recipes returns a list of these, POST /recipes creates one, a future PUT would update one's fields. The data model comes first because it's the thing everything else is built on top of, not something you discover by accident while writing your fifth route.

This is worth treating as a genuine decision, not a formality. Should ingredients be a flat list of strings, or a list of objects with an amount and a unit ({"item": "eggs", "amount": 2})? Either is defensible; a flat list is simpler and enough for "keep a recipe box," a structured list is closer to what "scale a recipe's quantities" (one of the later features on the full list) would eventually need. Deciding this on purpose, and being able to say why, is the actual skill. Getting it wrong isn't a disaster either, a data model can change later, but changing it later costs more than getting it closer to right the first time, because by then routes, persistence, and maybe a frontend are all already built assuming the old shape.

What has to persist, and where#

Before reaching for a specific framework or database, decide: does this data need to survive at all, and if so, where does it actually need to live?

A few questions settle it. Does this need to be usable from more than one device? Does the data need to survive a restart, or is it fine if it resets? Will more than one person ever use it? A single-user, single-device recipe box could, in principle, be nothing more than a local script writing to a file on your own machine, no server at all. The moment you want it on your phone and your laptop both, or want to share it with someone else, you need something that isn't just a file sitting on one computer, which is the actual reason a real app reaches for a server plus a shared, central place to store data.

notes/03-persistence.md covered the JSON-file-versus-database tradeoff already: a JSON file is genuinely fine at small scale, and a database earns its complexity once you outgrow that. That's a persistence-mechanism decision. This note is about the decision one level up, in front of it: whether you need a server and shared storage at all, before you ever get to picking which kind.

Build the thinnest complete slice first#

Once you know the feature list, the data model, and where things persist, the temptation is to write every route first, then build a frontend, then wire them together and hope it all lines up. Resist that. Build one feature, completely, start to finish, before starting the next one.

For the recipe box: get POST /recipes (create) and GET /recipes (view the list) both working, together, with real persistence, before writing anything for delete. That's a genuinely complete, if small, usable slice, not two disconnected halves of a bigger thing. Then add delete on top of something that already works, instead of building three unfinished routes in parallel and debugging all of them at once when you finally try to run the app.

This is, again, exactly what the recipe box exercise already handed you: create and view, wired all the way through to a working file on disk, nothing half-built. That's not a coincidence, it's the same instinct this section is naming directly, just decided for you in advance so you could feel what a complete slice looks like before being asked to choose one yourself.

Two cases that need more than this framework#

Everything above is genuinely general, it applies to a recipe box, a task app, or almost anything else with the same shape. Two specific kinds of projects need real additions on top of it, worth knowing about even though neither is this week's job to teach in full.

If your data model ever includes money (a price, a balance, a cost field), treat it as a special case starting at the data-model step itself: never store currency as a plain float. Floating-point math isn't exact, and small rounding errors that look invisible on one value compound into a real, wrong number after enough operations. Store money as an integer number of cents, or use Python's Decimal, not float, from the very first field you define.

If what you're planning is a smart contract, not a web app, the same four steps still apply, feature list, data model, what persists (on-chain state, in this case), build the thinnest slice first, but you need one more thing before writing any code: once a contract is deployed, you can't patch it the way you can redeploy a Flask app. That raises the cost of a planning mistake considerably. If you're on the blockchain track, add the common failure modes note and the idempotency and retries note to your planning checklist itself, before you write a line, not as a review pass afterward.

Where this goes next#

The notes/task API you're about to build in project/ was designed exactly this way: a feature list (five endpoints), a data model ({"id": 1, "text": "Buy milk"}), a persistence decision (a JSON file, same reasoning as notes/03), and a build order (persistence and the first route first, guided; the rest, in order, after). This time, that planning was done for you, and the spec was handed to you already finished. From here on, when you start a new project, in this program or anywhere else, doing that planning yourself, on purpose, before opening an editor, is the actual habit this note exists to start.

TL;DR#

This page covers what to do before opening an editor: turning a one-sentence idea into a feature list, a data model, a persistence decision, and a build order.

  • Turn a vague idea ("I want an app for X") into a written feature list, then split it into what you're building now versus what can wait. Writing both lists down is the actual skill, not just having opinions about them.
  • Decide the data model before any code: what fields does the one central "thing" in your app actually have. Every route or function you'll write is just an operation on that shape, so the shape comes first.
  • Decide what has to persist, and where, before picking a framework or a database. Not every idea needs a server at all.
  • Build the smallest complete slice, one feature working start to finish, before adding the next one. Not every route, then every screen.
Idea: "I want an app to keep track of my recipes."

Feature list:            add, view, edit, delete a recipe
Now:                      add, view, delete
Later:                    edit, tags, scale a recipe's quantities

Data model:               {"id": int, "title": str, "ingredients": [str]}

Persistence:              needs to survive a restart -> a file (or later,
                           a database), not just a Python list in memory

Build order:              1. add + view, working end to end
                           2. delete
                           3. (later) edit, tags, scaling

Everything so far this week has handed you the plan already made: the endpoints, the data shape, even the starter file's function names. That's deliberate, you needed to learn the pieces (routes, persistence, error handling) before you could reasonably be asked to decide how they fit together. This note is the piece that's been missing: how you'd have arrived at that plan yourself, if nobody had handed it to you.