Learn Durable Objects › Lesson 1 of 4

The coordination problem

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.

Workers are stateless by design

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.

The moment statelessness breaks down

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:

  1. Worker A reads: "..."
  2. Worker B reads: "..."  (same value, A hasn’t written yet)
  3. Worker A writes: "...Hello"
  4. Worker B writes: "...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 standard fix: transactions

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.

The harder problem: real-time coordination

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.

The key insight

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:

  1. Global uniqueness. A DO identified as "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.
  2. Single-threaded execution. Within that instance, code runs one thing at a time. No two requests can interleave their execution. The race condition above is structurally impossible inside a DO.

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.

Why this doesn’t feel like magic once you know it

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.


Quiz

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?

Primary source for this lesson Durable Objects: Easy, Fast, Correct — Choose three — Kenton Varda, Cloudflare Blog (2021). The original design rationale post. Sections “Background: Durable Objects are Single-Threaded” and “An Example” directly correspond to this lesson.
Lesson 2: Anatomy of a Durable Object →
Questions? Ask your teacher. Type anything that’s unclear, and the agent will answer before moving to the next lesson. Examples: “Why can’t Workers just use a distributed lock?” “What happens if two Workers request the same DO at the same millisecond?” “Is a DO per user ever the right answer?”