Hero image for "Erlang's Fault Tolerance Wasn't Borrowed — It Was Independently Discovered"

Erlang's Fault Tolerance Wasn't Borrowed — It Was Independently Discovered


Lesson 25: What Erlang's origin story reveals about the difference between designing for failure and designing around it


The actor model is commonly described as "Erlang's concurrency thing," as if Joe Armstrong and his colleagues at Ericsson reached for a known formalism and implemented it. The actual history is stranger and more instructive. Erlang arrived at actors independently — not by implementing Carl Hewitt's 1973 paper, but by solving a completely different problem and ending up in the same place. That convergence is worth sitting with, because it tells you something about why the design works that the formal lineage alone doesn't.


The Problem That Forced the Design

Joe Armstrong, Robert Virding, and Mike Williams began building Erlang at Ericsson in 1986. The problem they were solving was brutally concrete: telecom switching software cannot go down. Not "should be highly available." Cannot go down. A telephone exchange that crashes takes real calls with it, and in 1986 that meant real consequences for real people. The existing languages and runtimes of the era gave them no good tools for this. Shared mutable state meant that one failing component could corrupt the memory of every other component. Synchronous communication meant that a slow or crashed peer could block the entire system. Neither property was acceptable.

Their solution — lightweight, isolated processes that share no memory and communicate only by passing messages — converges conceptually with what Hewitt had formalized thirteen years earlier. But Erlang's designers weren't implementing a theory. They were escaping a trap. The isolation wasn't an academic preference for clean semantics; it was the only way to ensure that a crashed process couldn't take anything else with it.

This distinction matters. When you understand that Erlang's process isolation came from a requirement ("this component must be able to die without killing its neighbors") rather than from a theoretical commitment to message-passing purity, the rest of the design snaps into focus. Francesco Cesarini, who began working with Erlang at Ericsson's Computer Science Laboratory under Armstrong himself, has described this trajectory — a niche telecom tool shaped by operational necessity, not language theory.


What "Let It Crash" Actually Means

I've written about Erlang's "let it crash" philosophy before, but the actor model's lineage gives it a sharper frame. The key insight from Hewitt's original 1973 formalism — developed for AI reasoning, not telecom — was that an actor is defined by exactly three capabilities: it can create new actors, send messages to actors it knows about, and designate how it will behave the next time it receives a message. Nothing else is primitive. Crucially, actors never share mutable state, and message delivery is asynchronous — a sender continues immediately without waiting for acknowledgment.

Erlang's runtime takes those properties and adds something Hewitt's paper didn't need: a supervision hierarchy. When a process crashes, its supervisor decides what to do — restart it, restart a group of related processes, or escalate the failure upward. This pattern, drawn from Erlang's OTP (Open Telecom Platform), has since migrated into modern multi-agent AI systems, where the same logic applies: kill the erratic component, spawn a fresh instance, and let the user experience continuity rather than failure. The supervisor doesn't debug the failing agent — it kills it and starts clean, recovering in sub-300ms cycles.

The design philosophy this encodes is worth naming plainly: failure is a first-class event, not an edge case. Most systems treat crashes as exceptional. Erlang treats them as expected inputs to a supervision strategy. That's a different theory of what a runtime is for.


What Survives the Trip to Other Languages

As structured concurrency researcher Shah Bhat notes, Erlang didn't invent actors any more than Go invented channels — both are engineering ideas that trace back to the 1970s and 80s. But the trip from language primitive to library changes what you actually get. In Erlang, the actor is the only unit of computation. There is no way to write Erlang code that shares state between processes, because the language doesn't give you the tools to do it. The isolation guarantee is enforced by the runtime, not by convention.

When Akka ports actors to the JVM, or Kotlin wraps them in coroutines, the isolation becomes advisory. Nothing stops you from closing over a shared mutable reference in an actor's message handler. The model is available, but the guarantee has become the programmer's responsibility. That's the core of what changes at each step in the lineage — not the vocabulary, but who enforces the contract.

Elixir inherits Erlang's full runtime guarantees while running on the same BEAM virtual machine, which is why teams already operating Erlang/OTP infrastructure can adopt it without giving up the properties that matter. The supervision trees, the process isolation, the fault-tolerance model — those aren't Elixir features bolted on top. They're the runtime underneath. Bhat's comparative analysis of structured concurrency across languages makes the same point from the other direction: once you understand the original model, most of the "gotchas" you hit in any actor-based system stop looking like bugs and start looking like predictable consequences of design choices made decades ago.


The lesson from Erlang's independent discovery of the actor model is that good design constraints tend to converge. When you're forced to solve "a component must be able to die without corrupting its neighbors," you end up with isolation and message passing whether or not you've read the relevant 1973 paper. The theory and the practice arrived at the same place from opposite directions. That's not a coincidence — it's evidence that the design was right.