Why Use State Machines?

A state machine describes an application as a small set of states it can be in, and the events that move it from one state to another. I build applications this way; here is why.

States: What the Application Is Doing Now

Events: Cause a Transition to the Next State

Guards: Make Sure the Transition Is Allowed

Actions: Do the Work

More Benefits

Thinking About the Application Differently

Some will say: Imperative code is deterministic as well, right?

Yes. Technically, all standard imperative code is completely deterministic under the hood. A computer chip will always execute the line of code you wrote exactly the same way every time.

But there is a catch.

Imperative code is micro-deterministic (at the line-by-line level), but it quickly becomes macro-chaotic (at the application level). A state machine solves this by making that determinism explicit, visible, and manageable.

Here is the difference between how imperative code hides its determinism and how a state machine makes it obvious:

1. Imperative Code Hides the Map

In an imperative application, the “state” of your app is smeared across dozens of different variables scattered throughout your code (is_loading, user_authorized, current_page_index, api_error_count).

2. A State Machine Externalizes the Map

A state machine takes all those hidden variables and condenses them into a single, unified blueprint—the state map.

Instead of asking, “What happens if is_loading is true, but user_authorized is false, and the network drops?” you simply look at the map and see if there is a line connecting the Loading state to the NetworkError state.

The Analogy: A Jungle vs. A Train Track

Think of the difference like this:

The state machine engine forces your application to run on tracks. Imperative code is still doing the work inside the actions, but the map decides when those actions are allowed to run.

In imperative code, it is not just that the state variables are scattered throughout the code. The added problem is that the history of how you got there is completely hidden and it is complex.

Branching History

When an imperative application crashes, looking at the variables at the exact moment of the crash only tells you where you are. It doesn’t tell you the path you took to get there. So we add logging. It is complex because the application is a complex flow and it’s easy to leave out logging details that are needed.

State maps make runtime history much easier to log because the flow is already structured. A useful replay log needs the following items for each advance through the map.

With the map plus this ordered trace, a tool can visually replay the application history: highlight each state, animate each transition, show guards and actions, and display context/payload changes. Debugging becomes following the declared rails instead of reconstructing branching history from scattered imperative code.

The Three Great Advantages of State Machines

1. Spatial Reasonability (The Power of “Seeing”)

Imperative code is temporal—it exists as lines of execution moving forward through time. You can only understand it by reading from top to bottom and simulating the clock cycles in your head.

A declarative map is spatial. It maps your entire application out like a physical territory. Because it is static data, you can look at the whole system at once. A human can trace paths with a finger and visually verify: “Yes, there is a track connecting the loading screen to the error screen, so that flow is covered.”

2. Eliminating the “Execution Gap”

In an imperative application, there is always a gap between what the developer thinks the code does and what the computer actually executes. Hidden edge cases live in that gap.

With a state map, the data structure is the execution runtime. There is no gap. If a state transition line isn’t explicitly defined in your map the engine cannot execute it. You can confidently reason about the application visually because the visualization and the runtime logic are the exact same thing.

3. Anyone Can Read It

Think of your declarative map like an architectural blueprint for a house:

By shifting your application architecture into a map, you have stopped writing walking directions and drawn a blueprint.

The New Bottleneck

A lot of headlines are out there these days saying things like “AI made writing code cheap, but verifying it correct/safe is now the bottleneck”. AIs make these state maps and applications extremely well.

In my experience using this framework with AIs to make applications, the verification process has changed.

This framework moves a huge amount of verification out of ad hoc code review and into architecture.

The AI is not being asked to invent arbitrary program flow in raw Python. It is being asked to fill in constrained slots:

That makes AI-generated work much easier to trust because the system constrains the shape of possible mistakes.

The verification burden does not disappear. It changes form:

That is a much better environment for AI because AI is weakest when it must maintain hidden global consistency across scattered imperative code. This framework gives it rails: the linter, database schema, state machine topology, typed context, and generated contracts all narrow the blast radius.

This system is designed so verification is structural, automatic, and local: structural because it checks code shape and boundaries; automatic because many checks run mechanically from the declared structure; and local because each behavior is checked at the map row, contract, type, or layer boundary that owns it, instead of by tracing the whole program.