Hero image for "Erlang Didn't Solve Distributed Systems. It Refused to Pretend They Were Something Else."

Erlang Didn't Solve Distributed Systems. It Refused to Pretend They Were Something Else.


Lesson 12: When the Language Is the Architecture


Most distributed systems frameworks start with a lie: that you can make a network of machines behave like a single machine, if you just add enough abstraction. Erlang started from the opposite premise. Networks fail. Processes die. Messages get lost. The question isn't how to hide that — it's how to build something useful anyway.

That design decision, made at Ericsson in the 1980s for telephone switching hardware, turns out to be one of the most instructive in programming language history.

The Problem Was Telecom, But the Lesson Is Universal

Ericsson needed software to run telephone exchanges — systems handling hundreds of thousands of simultaneous calls, each stateful, each independent, with zero tolerance for downtime. You can't take a phone switch offline for a restart. You can't let one bad call corrupt another. And you absolutely cannot have a garbage collector pause the whole system while someone's 911 call is in progress.

The constraints were brutal and specific. And they produced a language with an equally specific answer.

The BEAM virtual machine, the runtime Erlang runs on, was built around lightweight processes — not OS threads, not coroutines bolted onto a single-threaded event loop, but genuinely isolated units of computation. Each process has its own private heap, its own stack, its own garbage collector, its own mailbox. A fresh process costs roughly 2.6KB. You can run a million of them on a single machine without breaking a sweat.

That last detail matters more than it sounds. When your unit of isolation is cheap enough to be disposable, your entire architecture changes.

The Actor Model as a Design Constraint, Not a Feature

Carl Hewitt conceived the Actor Model at MIT in 1973 as a way to reason about concurrent computation. The core idea: every actor is a self-contained unit that holds private state and communicates exclusively through asynchronous message passing. No shared memory. No direct function calls across process boundaries. If you want something from another actor, you send it a letter and wait.

Erlang didn't invent this model, but it did something more interesting: it made the model inescapable. You can't reach into another process's memory in Erlang because there's no mechanism to do so. The isolation isn't a convention you're supposed to follow — it's a property the runtime enforces. This gives you three guarantees that are genuinely hard to achieve otherwise: no data races between concurrent processes, natural fault isolation (one process crashing doesn't corrupt another's state), and a supervision model that can restart failed components without touching the rest of the system.

I'd argue this is the key insight that separates Erlang from most concurrency frameworks: the constraints aren't limitations you work around, they're the architecture. When every process communicates only through messages, you've already described how your distributed system will behave when the network between two nodes goes down — because a remote message and a local message look identical to the sender.

What This Teaches Every Programmer

Here's the uncomfortable part. Most of us writing distributed systems in Python, TypeScript, or Java are rebuilding these primitives by hand — actor libraries, message buses, supervisor trees, worker pools, durable execution layers — all stacked on top of runtimes that were never designed for this shape of work. We're adding the constraints back in after the fact, with our own subtle bugs in cancellation and backpressure.

Erlang's lesson isn't "use Erlang." It's that the right constraints, baked into the language and runtime from the start, can make an entire class of bugs structurally impossible. You don't need discipline to avoid shared mutable state if the language gives you no way to create it.

This is the same insight that drives Rust's ownership model, Haskell's purity, and Elm's enforced immutability. The language isn't just a tool for expressing solutions — it's a set of commitments about what kinds of problems you're willing to have.

Erlang committed to: processes die, networks fail, and your system should keep running anyway. Every feature follows from that.

Your Next Step

Before next week's issue, spend twenty minutes with one distributed system you've worked on — a job queue, a microservice mesh, a websocket server — and ask: where does this system pretend failure won't happen? Where does it assume a remote call will succeed, a process will stay alive, a message will arrive exactly once?

You don't need to rewrite anything in Erlang. You need to see where your architecture is built on optimism rather than design. That's where Erlang's philosophy has the most to teach you.