15 minutes · No code required · Mental model only
Listen while you read — AI-generated podcast for this lesson
Before you can understand what a Durable Object is, you need to feel the problem it solves. This lesson is about that problem. We’ll call it the coordination problem.
A Cloudflare Worker is stateless. Every request spins up an isolated execution context, runs your code, and exits. There is no memory between requests. This is the right default: it means Workers fan out horizontally across Cloudflare’s global network with no coordination overhead. Thousands of concurrent requests, each handled independently.
That statelessness is a feature until you need two requests to agree on something.
Imagine a collaborative document editor. Two users open the same document. User A appends “Hello”. User B appends “World”. Each request hits a Worker. Each Worker reads the current document from a database, appends the new text, and writes it back.
Here’s what can happen:
"...""..." (same value, A hasn’t written yet)"...Hello""...World" (clobbers A’s write)The final document contains only “World”. Hello is gone.
This is a read-modify-write race condition. It happens because two Workers read the same value before either one writes. Neither Worker knows the other exists.
This class of bug is especially cruel: it only surfaces under load, and it’s non-deterministic. Everything looks fine in development. It only breaks when real users hit the same resource at the same time.
The traditional answer is database transactions. Wrap the read-modify-write in a transaction. The database serializes concurrent operations. Whoever commits first wins; the loser retries.
This works, but it has costs:
Transactions solve correctness. They don’t solve speed.
Now make it harder. Not just two writes to the same document — two users in the same chat room. Each connected via a WebSocket. When User A sends a message, Worker A needs to broadcast it to User B’s Worker.
But Worker B’s connection lives in a different isolate, on a different machine. There’s no shared memory. There’s no message bus built into Workers. You could publish to a queue and have each Worker poll it, but now you’ve introduced another system, latency, and complexity.
What you actually want is: one place where all clients for this room meet. A single address. Any Worker anywhere in the world that knows the room name can reach it. That address has memory. That address can broadcast to all connected clients.
That address is a Durable Object.
This idea has a 50-year history. The pattern a DO implements is called the Actor model, invented by Carl Hewitt at MIT in 1973. An actor is a globally-addressable, single-threaded entity that processes messages one at a time and holds its own private state. Erlang (1986), Microsoft Orleans (2014), and Akka have been running this model in production for decades — WhatsApp, Discord, Xbox Live, and Halo all use variants of it. Cloudflare’s contribution is not the idea; it’s the implementation: actors provisioned globally on edge hardware, with co-located SQLite storage and a routing layer baked into the runtime. When DOs feel like magic, it helps to know the magic has a name, a bibliography, and competitors.
A Durable Object is the inverse of a stateless Worker. Instead of many independent Workers handling each request, a Durable Object is one instance, handling all requests for a given identity.
Two properties make this work:
"room-42" exists exactly
once in the world. Every Worker that asks for "room-42" gets routed to the same
instance, regardless of where in the world the Workers are running.
The magic explained. When a DO “just handles concurrency for you”, this is what’s happening: the runtime routes all requests for a given ID to the same single-threaded instance. There is no locking algorithm, no retry logic, no transactions (for in-memory state). There is just one thread. Concurrency is serialized by the runtime before it reaches your code.
Your existing mental model was mostly right. You said: “a bunch of WebSocket-powered, database-backed individualized threads attached to individual users or sessions.”
The amendment: it’s not a thread per user. It’s a thread per coordination unit. A room. A document. A game session. An order. Whatever the thing is that multiple clients need to agree on. One DO per thing-being-coordinated.
The “cognitive surrender” feeling comes from using DOs without understanding why one instance handles everything. Now you know why: it’s the only structural way to eliminate the race condition without a database transaction on every operation.
Select the best answer. No partial credit — commit to one.
Two Workers read the same counter value from a database, each increment it, and each write it back. What is this bug called?
What is the core structural property that makes a Durable Object correct for coordination without database transactions?
A chat app has 10,000 rooms. Each room has up to 50 active users. What is the right DO topology?