The Case for Composable Notations (3 of 5) State Isn’t The Enemy
2026-03-30
TL;DR
FP didn’t eliminate state. It hid it. The field spent decades learning to avoid expressing state rather than learning to express it well. Those are opposite skills.
L;R
Programming courses teach state avoidance as a virtue. The word “state” has acquired a faint smell — something to be minimized, hidden, apologized for. Stateless is clean. Stateful is dangerous. Pure functions are safe. Mutation is where bugs live.
This is a notation bias dressed up as wisdom.
What state actually is.
State is the current condition of a system at a point in time. Every useful program has it. A program that accepts input, produces output, and changes nothing in between is not a program — it is a mathematical function. Useful for certain problems. Not a general model of computation.
CPUs mutate registers. Memory is written and rewritten. Device drivers track hardware conditions. User interfaces respond to sequences of events. Network protocols maintain connection state across time. Embedded controllers react to signals from the physical world. Every one of these domains is stateful by nature. The state does not go away because the notation ignores it.
Where FP put the state.
FP pushed state into the operating system. The call stack is state. The heap is state. The scheduler’s bookkeeping — which process is running, which is waiting, what it was doing when it was interrupted — is state. This is not a small amount of state. It is the complete runtime state of every active process, maintained by the OS on the programmer’s behalf.
The programmer did not make it disappear. The programmer handed it to the OS and agreed not to look at it directly. The OS shapes that state according to its own model — threads, processes, protected memory regions — and the programmer inherits that shape whether or not it fits the problem.
When it fits, the arrangement is invisible and cheap. When it doesn’t fit — when the problem has a state structure the OS model doesn’t express cleanly — the programmer pays. The payment is indirection, workarounds, and a persistent translation layer between the problem’s natural structure and the structure the runtime imposes.
The vocabulary we didn’t develop.
State machines are one way to name state explicitly: here are the states a system can be in, here are the conditions that cause transitions between them. The notation is direct. The structure is visible. The programmer controls it.
Harel’s Statecharts extended state machines to handle hierarchy and concurrency — evidence that the notation can be developed further. They are not the final word. They are one existence proof that expressing state clearly is possible and has been explored.
There may be better approaches. We don’t know, because the field largely stopped looking. Instead of developing richer notations for state expression, the field developed richer techniques for state avoidance. These are opposite research programmes. One asks: how do we express state clearly? The other asks: how do we avoid having to?
The avoidance programme won. State machines are taught, if at all, as a niche technique for embedded systems or compiler design — not as a general tool for expressing the stateful behaviour that most programs actually contain. Most programmers implement what are effectively state machines as switch statements inside loops, without the notation to see that is what they are doing.
The cost of the avoidance programme.
When state is hidden, debugging means reconstructing it. When state lives in the OS, you observe it indirectly — through stack traces, memory dumps, log files, debugger watches. The tools for reconstructing hidden state are sophisticated because the need for them is constant.
When state is expressed directly, you can see it. You can name the states your system can be in. You can describe the transitions explicitly. You can ask, at any point: what state is this system in, and why? The question is answerable without reconstruction.
Hidden state is not safe state. It is state that is harder to reason about, harder to test, and harder to modify. The sense of safety that FP’s state restrictions provide is real but narrow. It applies to the class of problems where the OS’s model of state happens to fit. For everything else, the safety is an illusion maintained at the cost of visibility.
What ease of expression requires here.
The transistor argument applies directly. We do not describe programs in terms of transistors because the notation is too far from the problem. We do not describe concurrent, reactive, long-lived systems in terms of pure functions for the same reason — the notation is too far from the problem. The distance between notation and problem is not a style preference. It is where bugs live, where complexity accumulates, and where programmer hours disappear.
Expressing state well means having notations in which states are named, transitions are explicit, and the current condition of the system is visible. We have glimpses of what that looks like. We have not pursued it seriously, because the dominant notation taught us that state was the enemy.
It isn’t. Invisible state is.
Next: Ease of Expression Is the Whole Point.
See Also
Email: ptcomputingsimplicity@gmail.com
Substack: paultarvydas.substack.com
Videos: https://www.youtube.com/@programmingsimplicity2980
Discord: https://discord.gg/65YZUh6Jpq
Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas
Twitter: @paul_tarvydas
Bluesky: @paultarvydas.bsky.social
Mastodon: @paultarvydas
(earlier) Blog: guitarvydas.github.io
References: https://guitarvydas.github.io/2024/01/06/References.html

