Why Distributed Systems Fail in Ways Unit Tests Never Catch
A distributed system can pass every single unit test, every integration test, and a full staging environment soak test, and still fail in production in a way none of that testing ever came close to catching.

A distributed system can pass every single unit test, every integration test, and a full staging environment soak test, and still fail in production in a way none of that testing ever came close to catching. This is not a sign the testing was done poorly. It reflects something genuinely structural about distributed systems, the failures that actually matter most emerge specifically from interactions between components under real, unpredictable timing and real, partial failure conditions that a controlled test environment essentially never reproduces faithfully by its very nature.
Unit tests validate logic, not timing, and timing is where the real danger lives
A unit test runs a single function or a single service in careful, controlled isolation, verifying that given a specific input, it produces the specific expected output every single time. This genuinely, usefully validates logic correctness. It says essentially nothing about what happens when that same exact function runs concurrently across several nodes, with real network latency varying unpredictably between calls, and a downstream dependency responding slowly in a way that is neither a clean, clear success nor a clean, clear failure but an genuinely ambiguous state sitting uncomfortably in between the two.
Race conditions, the specific class of bug depending entirely on the exact relative timing between two or more independent events, are notoriously difficult to reproduce reliably in any kind of deterministic test environment precisely because the actual bug depends on timing variance that a controlled, repeatable test environment is specifically, deliberately built to eliminate and remove in the first place.
Partial failure is the actual default state in any sufficiently large distributed system
A single-machine application genuinely fails as a cohesive, singular whole, the process crashes, or it does not. A distributed system fails partially and incompletely as the genuine, ordinary default condition, one node down while others remain healthy, a network partition isolating some genuine subset of nodes from the rest, a dependency responding slowly but not failing outright in any clean, clearly detectable way. This connects to designing explicitly for partial failure as the expected normal case, not a rare edge case, a system only genuinely tested against full, clean failure or full, clean success will handle the actual, far more common partial-failure middle ground considerably worse than its passing test suite would otherwise honestly suggest to anyone reviewing those green checkmarks.
Why chaos engineering exists as a genuinely distinct, necessary practice
Chaos engineering, deliberately injecting real failures into a genuinely running system specifically to observe how it actually behaves, exists precisely because traditional testing cannot adequately cover this entire category of distributed failure mode on its own. Deliberately killing a random node, deliberately injecting artificial network latency, deliberately simulating a slow, genuinely degraded dependency rather than a clean, binary failure, surfaces real, genuine weaknesses that no amount of additional unit or integration testing alone would ever reliably reveal, because those specific tests were never actually designed to probe for this particular, distinct category of distributed failure in the first place.
This is not a replacement for unit and integration testing, which remain genuinely necessary and valuable for verifying logical correctness. It is a genuinely distinct, complementary practice addressing a fundamentally different, equally real category of failure that conventional testing approaches were simply never actually built to catch reliably on their own.
Where I disagree with how distributed systems get taught
Most software engineering education treats distributed systems failure modes as an advanced, optional topic, covered briefly if covered at all, after students have already spent considerably more time mastering single-machine programming and conventional testing practices first. I think this ordering genuinely fails students who will go on to actually build and maintain real distributed systems throughout their careers, since partial failure and genuine timing-dependent bugs are not edge cases to learn eventually, they are the actual central, defining challenge of the entire field once a system crosses more than a single machine boundary.
A curriculum that introduced partial failure thinking earlier, alongside conventional testing rather than considerably after it, would produce engineers genuinely better prepared for what actually goes wrong in real production distributed systems, rather than engineers confidently trusting a fully green, fully passing test suite that was never actually designed to catch the specific failure modes that will eventually, inevitably take their own real production system down.
A more complete way to think about testing a distributed system
Keep unit and integration tests for what they genuinely, reliably do well, verifying logical correctness in careful isolation from the rest of the system. Add deliberate chaos testing specifically for partial failure and genuine timing-dependent behavior that conventional testing structurally cannot reach or reliably reproduce. And treat a fully passing, fully green test suite as real, meaningful evidence of logical correctness, not as genuine proof the system will actually behave well under the real, messy, partial-failure conditions production will eventually and inevitably throw directly at it.
More from the blog
Systems
Reading GHC's +RTS -s Report Before You Tune the GC
Every few weeks someone in a Haskell chat posts the same message: their service uses too much memory or pauses at odd moments,...
Systems
Why Systems Programmers Are Choosing Haskell for New Projects
Systems programming has long been dominated by C, C++, and more recently Rust.
Open Source
Choosing an Open Source License for a Haskell Library
Open the license field of a hundred random packages on Hackage and you will see the same two answers again and again:...