Data, functions and state
The three ingredients of every program, and why most bugs live in the third.
What you will learn
- 01Every program is data plus transformations
- 02Data: model what you know
- 03Functions: input to output
- 04State: data that changes
- 05Putting the three together
- 06Where this starts to hurt
Every program is data plus transformations
Strip away frameworks and languages and a program is made of three things. Data is what the program knows. A function turns some data into other data. State is data that changes over time. Low-level design is the craft of arranging these three so that a program stays understandable as it grows.
Our running example for this whole part is a parking lot. A car enters, receives a ticket, and later leaves and pays. Even this small story uses all three ingredients.
Swipe sideways to pan · drag a box to move it
Data: model what you know
Start by writing down the facts the program must hold. In code that is a type: a named shape for a piece of data.
type Ticket = { id: string; plate: string; enteredAt: number };
Good data modeling is half the design. Keep the data honest: store facts, not conclusions that can be recomputed. We store when the car entered, not how long it has been parked, because the second changes every minute and the first never does.
Choose the structure by how you will use it. A list is easy to build but finding one ticket means checking each, which takes time proportional to the list. A map from ticket id to ticket finds one in roughly constant time. Pick the shape that matches the question you ask most.
Functions: input to output
A function takes inputs and returns an output. The best functions do one thing, have a name that says what it does, and depend only on what you pass in.
function fee(minutes: number): number {
const hours = Math.ceil(minutes / 60);
return hours * 20;
}
Given 90 minutes this always returns 40, today or next year, on any machine. You can test it with a single line and reason about it without reading anything else. Keep that quality as long as you can. It is the most valuable property a function has.
Swipe sideways to pan · drag a box to move it
State: data that changes
A parking lot must remember which cars are inside right now. That memory changes every time a car enters or leaves, so it is state.
State is where most bugs live, because it gives a program a past. The same call can behave differently depending on what happened earlier, and two parts of the program can disagree about what is true. Three habits keep state manageable.
- Keep as little of it as you can, and never store what you can recompute.
- Keep each piece in one place, so there is one answer to "what is true now".
- Make every change explicit, through a few named operations, not scattered edits.
Swipe sideways to pan · drag a box to move it
Putting the three together
Here is a tiny but complete lot using only what we have so far.
const lot = new Map<string, Ticket>();
function enter(plate: string, now: number): Ticket {
const t = { id: crypto.randomUUID(), plate, enteredAt: now };
lot.set(t.id, t);
return t;
}
function leave(id: string, now: number): number {
const t = lot.get(id);
if (!t) throw new Error("unknown ticket");
lot.delete(id);
return fee((now - t.enteredAt) / 60000);
}
Notice the split. Ticket is data. fee is a pure function with no memory. lot is state, changed only by enter and leave. This is a good start, and it already works.
Where this starts to hurt
Nothing stops other code from touching lot directly. A bug elsewhere could delete a ticket, insert a bad one, or read it while it is half changed. As the program grows, every part that can reach the state becomes a suspect when something goes wrong.
Swipe sideways to pan · drag a box to move it
This pressure is why the next lessons exist. We will separate what changes from what does not, put walls around state, and expose only small, safe operations. Before moving on, ask of any piece of code you write: which of these three ingredients is it, and who else can see the state it touches?