Durable Objects 101: Sequential State on the Edge
You're building an app where multiple things try to happen at the same time. A thousand users incrementing a counter. Multiple people editing the same document. Requests coming in faster than you can process them.
This causes race conditions. Updates step on each other. Data gets lost or corrupted.
Durable Objects fix this by making sure only one thing happens at a time.
##What is a Durable Object?
Think of it like a worker at a store counter. Customers line up. The worker helps one person, then the next, then the next. Never two at the same time.
A Durable Object does the same thing with data. It sits in one place and handles requests one by one. When request #1 finishes, request #2 starts. No overlap.
The "durable" part means it remembers things. Even if the server crashes, your data comes back.
##Why you'd want this
Imagine a leaderboard. A thousand people try to increment their score simultaneously. With a normal database, some updates might get lost. Increments step on each other.
With a Durable Object, every increment queues up. Score goes up by 1. Then by 1 again. Then by 1 again. Nothing gets lost because only one thing happens at a time.
##Real examples where this works
Counters and leaderboards: Thousands of users incrementing scores. Each increment goes through one Durable Object per user. No missed updates.
Shared documents: Multiple people editing a document. One Durable Object per document. Edits queue up. Everyone sees the same version because there's only one version.
Rate limiting: A Durable Object per user tracks API calls. First request: count goes to 1. Second request: count goes to 2. When count hits 100, you block. No cheating the system.
Voting systems: A thousand people voting at once. One Durable Object counts votes. Vote 1 counts. Vote 2 counts. Vote 3 counts. Nothing gets lost.
Queues: Tasks that need to happen in order. One Durable Object processes them one at a time. No task gets skipped.
##Where this doesn't work
If you need to process a million things per second, one Durable Object can't handle it. It processes things in order, so speed is limited.
Also, you can't store all your data in Durable Objects. They're not a database replacement. They're for specific pieces of state that need to stay consistent.
##How it actually works
A request comes in. A Worker (temporary code) receives it. The Worker sends it to a Durable Object. The object processes it, updates its memory, and sends back an answer. The Worker returns it to the client. The Worker shuts down. The Durable Object stays awake, ready for the next request.
typescriptimport { DurableObject } from "cloudflare:workers"; export class Leaderboard extends DurableObject { scores: Map<string, number> = new Map(); constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); ctx.blockConcurrencyWhile(async () => { const savedScores = await ctx.storage.get<Map<string, number>>("scores"); this.scores = savedScores || new Map(); }); } async fetch(request: Request) { const action = await request.json(); if (action.type === "addScore") { const userId = action.userId; const currentScore = this.scores.get(userId) || 0; const newScore = currentScore + action.points; this.scores.set(userId, newScore); await this.ctx.storage.put("scores", this.scores); return new Response(JSON.stringify({ userId, score: newScore, timestamp: Date.now() })); } if (action.type === "getScore") { const score = this.scores.get(action.userId) || 0; return new Response(JSON.stringify({ score })); } return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400 }); } } export default { async fetch(request: Request, env: Env) { const leaderboardId = env.LEADERBOARD.idFromName("global"); const leaderboard = env.LEADERBOARD.get(leaderboardId); return leaderboard.fetch(request); } };
One Durable Object per leaderboard. All score updates queue up through that object. When user A increments by 10 and user B increments by 5, both happen. No updates get lost. Both see their new scores immediately
##The tradeoff
You get consistency. Nothing breaks. State stays valid. But you give up speed. Things happen in order, not all at once.
If you need both speed and consistency, Durable Objects won't work. If you can accept things happening in order so long as nothing breaks, they're perfect.
##When to use them
You're building something where order matters and breaking is bad. A counter. A voting system. A shared document. A rate limiter. A queue.
Don't use them for everything. Use them for the parts where consistency is more important than speed.