Most bugs in commerce aren't math bugs — they're state bugs. An order gets refunded after it shipped. A "delivered" order goes back to "preparing." A cancel button works on an order that's already out for delivery. Every one of those is the same mistake: treating an order like a row you edit, instead of a machine that moves through states. Building Saturdays hammered that lesson in.
An order is a lifecycle, not a record
A row in a table can hold any value you write to it. That's the problem. Nothing about a plain status column stops you from setting a delivered order back to pending, or refunding an order that never got paid. The database will happily let you write nonsense, because a column doesn't know that some transitions are legal and most aren't.
An order has a life: placed → accepted → preparing → out for delivery → delivered, with cancelled and refunded branching off at specific points and nowhere else. That shape is the real business logic. If it lives only in your head, every developer who touches the code re-derives it — and gets it slightly wrong.
Make the illegal states unreachable
The fix is to stop asking "what's the new status?" and start asking "is this transition allowed from where we are?" You model the order as a finite state machine: each state declares the moves it permits, and the code refuses everything else by default.
-
Guarded transitions. You can only move
out_for_delivery → delivered, neverdelivered → preparing. The rule lives in one place, not scattered across every handler that happens to touch an order. -
Side effects hang off transitions, not fields. "Send the receipt" fires on the transition into
delivered, exactly once — not every time some code re-saves the row. This is how you stop double-notifications and phantom refunds. - The impossible becomes unrepresentable. A cancel request on a delivered order isn't a bug you patch later; it's a move the machine simply doesn't offer.
The takeaway
The instinct is to model the world as data you mutate. But anything with a lifecycle — an order, a subscription, a support ticket, a deploy — is better modeled as a state machine, because the value isn't the current state, it's the set of legal moves between states. Encode that, and a whole category of "how did the data get into this shape?" bugs stops being possible.
Saturdays taught me to look for the state machine hiding inside every workflow. The full build — the checkout, the payment path, the order flow — is on the project page.
👉 See it: www.divyakush.com/projects/saturdays
Divyakush Punjabi — Full-Stack & AI Systems Engineer
🌐 https://www.divyakush.com · 💼 LinkedIn · 💻 GitHub
Top comments (0)