Hacktakes · Edition 5
Hacktakes · Edition 5 · July 8, 2026

State Space Drag and the Myth of the Lazy Developer

Declining software reliability stems from the mathematical reality of exponential state space drag, not a moral failing of lazy developers.

By Leo Marchetti

Sparked by Notes on Software Quality · discussion

It's just sad how developers today have completely lost their sense of craftsmanship.
It's just sad how developers today have completely lost their sense of craftsmanship.

When Anthony Hobday published his Notes on Software Quality recently, the subsequent Hacker News comment discussion immediately devolved into a very specific, very familiar type of industry fatalism. The prevailing consensus among the commenters was basically that modern developers are fundamentally lazy, management is uniquely apathetic, and nobody takes pride in their craft anymore. To hear the internet tell it, the decay of software reliability is purely a moral failing. We just need to try harder, care more, and refuse to ship until things are perfect. It is the sort of software craftsmanship rhetoric that sounds incredible on a podcast but rarely survives contact with a modern production environment.

I prefer to start from Gerald Weinberg's classic definition, where quality is simply value to some person who matters. If we accept that premise, the "lazy developer" hypothesis requires us to believe that millions of engineers simultaneously decided they no longer wanted to deliver value to their users. I find that highly suspect. Moralizing about code quality feels deeply satisfying in a forum thread, but you are bringing a cultural lecture to a fistfight with raw math. A system that constantly breaks under pressure is just the natural byproduct of a codebase slamming into a wall of combinatorics.

To see why the moral argument collapses, let's look at a brutally simple scenario: an e-commerce checkout flow. We have a shopping cart, a shipping calculator, and a basic status flag. Let's write a tiny TLA+ specification to model exactly how a user might interact with this.

---- MODULE Checkout ----
EXTENDS Integers
VARIABLES cart_total, shipping_calculated, status

Init == 
    /\ cart_total \in 0..100
    /\ shipping_calculated = FALSE
    /\ status = "shopping"

CalculateShipping == 
    /\ status = "shopping"
    /\ shipping_calculated' = TRUE
    /\ status' = "ready_to_pay"
    /\ UNCHANGED cart_total
====

If we fire up the TLC model checker and run this naive spec, everything works perfectly. We haven't defined anything bugnuts crazy here. The CalculateShipping action just looks at our shopping state, flips a boolean, and moves us to the next step while leaving the cart total UNCHANGED. It’s as basic as an online store gets.

Model checking completed. No error has been found.
State space observation:
24 states generated, 12 distinct states found, 0 states left on queue.

Twelve states. You can hold twelve discrete states in your head while you sip your morning coffee without breaking a sweat. The hypothetical developer who wrote this code absolutely "cares" about quality. They understand every possible path the user can take through the application because the bounds of the system map perfectly to human working memory.

Then Product asks for a discount code feature.

We just need to add a single boolean flag to track whether a promo code has been applied, and let the user toggle it before calculating shipping. We update our model's variables and add an action to apply the code.

VARIABLES cart_total, shipping_calculated, status, promo_applied

Init == 
    /\ cart_total \in 0..100
    /\ shipping_calculated = FALSE
    /\ promo_applied = FALSE
    /\ status = "shopping"

ApplyPromo ==
    /\ status = "shopping"
    /\ promo_applied = FALSE
    /\ promo_applied' = TRUE
    /\ UNCHANGED <<cart_total, shipping_calculated, status>>

We added one tiny boolean feature. We didn't change the underlying architecture or introduce any wild asynchronous background jobs. We just gave the user a single new button to click. Let's run TLC again and see how our perfectly disciplined developer fares.

Error: Deadlock reached.
State space observation:
18,432 states generated, 14,208 distinct states found, 0 states left on queue.

Sorry, you’re doomed.

We went from 12 states to over 14,000. Our model deadlocked because adding a promo code suddenly created unintended interactions with the cart total zero-bound, the shipping calculator timing, and the checkout status transitions that we didn't explicitly restrict.

What happens if a user calculates shipping, then applies the promo code, dropping the cart total below the free-shipping threshold? What if they apply the promo code twice? What if the promo code applies a flat $10 discount to a $5 cart—does our system suddenly handle negative integers, or does it dropkick the payment gateway? The model checker forces us to confront the fact that our single boolean flag didn't just add one new path; it multiplied across every existing variable state.

This is a classic state space explosion. The failure to anticipate edge cases here stems directly from the physical limits of human cognition trying to track exponential branches, rather than any sudden decay in developer morals.

There is a parallel to this in aerodynamics. If you look at the NASA drag equation, you see that as an object moves faster through a fluid, the resistance it encounters scales non-linearly.[^1]

[^1]: (Yes, I know drag increases with the square of velocity while the power required to overcome it cubes. We are focusing on the general principle of non-linear scaling here regardless of the specific fluid dynamic parameters. Please do not email me about the Reynolds number.)

I think it helps to model our software problem as <dfn>State Space Drag</dfn>.

[Imagine a simple line-vs-curve graph here: linear feature additions plotted on the x-axis, with the exponential state space growth rocketing up the y-axis]

When you add features linearly, the potential interactions between those features grow factorially. Just as overcoming aerodynamic drag requires exponential engine power for linear speed increases, keeping track of feature interactions requires exponential cognitive power for linear feature additions. The first five features are basically free. You can write them quickly, test them thoroughly, and deploy them safely. The fiftieth feature, however, requires you to mentally simulate a state graph the size of a small moon. Even the most fastidious, detail-obsessed engineer in the world cannot hold that much context at once. Eventually, the drag coefficient overwhelms the engine.

When users complain that software feels buggier today than it did twenty years ago, they are observing a real phenomenon. Codebases are vastly larger, third-party integrations are practically mandatory, and asynchronous microservices have largely replaced localized monoliths. Pointing the finger at lazy engineers misdiagnoses a mathematical reality as a moral defect. The modern developer's finite cognitive power is simply being rapidly consumed by State Space Drag. You cannot shame someone into holding 14,000 discrete transactional states in their working memory, no matter how many times you lecture them about craftsmanship.

Before the pitchforks come out, let me clarify that I am not a manager, and I could be completely wrong about the cultural side of this. Maybe rigorous TDD culture and moral imperatives do move the needle at the margin. But whatever cultural revolution you try to spark, you still eventually have to pay the combinatoric piper. Math doesn't care how much you care.

← Back to Edition 5