← all articles

The Most Dangerous State in Software Is Almost Done

Abstract workflow nearing a stable endpoint

Software teams are comfortable talking about success and failure. A request succeeded. A job failed. A deployment passed. Those are neat words, but a surprising number of the hardest production problems live in the state between them: almost done.

Almost done is when a person presses Save, sees a reassuring message, and then closes their laptop before the work has reached durable storage. It is when a mobile app queues an action, the connection disappears, and the user cannot tell whether to try again. It is when a background worker has accepted a job but has not performed the irreversible part. It is also when two systems both believe the other one owns the next step.

Nothing has obviously crashed. That is precisely why this state is dangerous.

Apparent success is not a contract

Many interfaces treat the first positive signal as success: a button becomes disabled, a toast appears, a spinner stops. Often that signal means only that the client has handed work to another component. The work may still be waiting in memory, queued locally, travelling over a network, competing for a lock, or scheduled for a later process.

There is nothing wrong with asynchronous work. The mistake is allowing the interface, the API, and the operational process to use the same word — “complete” — for different milestones.

I find it useful to name the milestones explicitly. An action can be accepted, recorded, processed, and confirmed. Each is valuable, but they are not interchangeable. If the user needs a receipt, an audit trail, or a safe next action, the system must say which milestone has actually been reached.

That small discipline changes design conversations. Instead of asking, “Did it work?”, a team can ask, “What can the user safely rely on right now?”

Design for the return trip

The most revealing test is not the happy path. It is the moment immediately after an action begins: the app is killed, the browser reloads, a worker restarts, or the network changes. Can the system recover without guessing?

Good systems make the return trip boring. They assign an operation a durable identity before attempting work. They make retry behaviour deliberate. They can distinguish a request that was never received from one that completed just before the response was lost. Most importantly, they give people a way to inspect the current state rather than forcing them to repeat an action and hope for the best.

Idempotency is often described as an API technique, but it is really a user-experience promise. When a person taps a control twice because the first attempt is uncertain, the system should not punish them for behaving rationally. A stable operation identifier and a clear status record turn a nervous repeat action into a safe recovery path.

Make uncertainty visible, but useful

Hiding uncertainty does not remove it; it moves it into support queues, spreadsheets, and improvised team knowledge. A better interface can be honest without being alarming. “Saved locally and waiting to send” is more useful than a generic success message. “We are checking the outcome” is better than inviting a duplicate submission. “Completed at this time” is meaningful when it represents a real, durable event.

This also means designing the states around decisions. If an item is pending, what can the user do? Wait, retry, cancel, continue working, or ask for help? If there is no sensible answer, the state is probably an implementation detail that has leaked into the product.

Observe the gaps

Teams commonly measure errors and latency, yet the most important reliability signal may be a gap: accepted but never confirmed, queued longer than expected, or retried more than once. These are the near misses that tell you whether the system can be trusted when conditions are imperfect.

The practical takeaway is simple. Treat completion as a sequence of promises, not a single event. Give every important action a durable identity, define the recovery path before the happy path ships, and make the current level of certainty visible to the person who needs it.

“Almost done” will always exist in distributed software. It does not have to be where confidence goes to die.

Matthew Ratcliffe, software developer and architect, Ballarat
Senior Software Engineer & Architect

20+ years across the technology stack — from greenfield builds to brownfield rescues. Based in Ballarat, VIC, focused on AI, healthcare and high-risk data systems. Full resume →

Share your thoughts