Cloudflare

Durable Objects vs a database: choosing where state lives

Durable Objects are single-threaded, strongly consistent, and scoped to one entity. That makes them right for coordination and real-time state, and wrong for cross-entity queries. Here is how to decide which to reach for.

August 9, 2026 8 min read CloudflareDurable Objectsarchitecturestateengineering

You are building something on Cloudflare Workers and you hit the question every stateful app eventually reaches: where does the state live? A Worker is stateless by design — it spins up, handles a request, and forgets everything. The moment you need to remember something between requests, you have a choice to make, and on Cloudflare the two main answers are a Durable Object or a database like D1.

They are not interchangeable. Picking the wrong one shows up months later as a bottleneck you cannot tune away or a query you cannot write. Here is how the two actually differ, and how to tell which a given piece of state wants.

What a Durable Object is

A Durable Object is a small, addressable unit of compute with its own private storage. Three properties define it:

  • Single instance, globally. For a given object ID there is exactly one instance running at a time, in one location. Every request for that ID routes to the same instance. There is no cluster of replicas to keep in sync, because there is only one.
  • Single-threaded. That one instance processes requests one at a time. Two requests to the same object cannot interleave and corrupt shared state, which removes an entire category of race conditions without a lock in sight.
  • Strongly consistent, co-located storage. Each object has transactional storage sitting right next to its code. A write is immediately visible to the next read on that object. There is no eventual-consistency window to reason about.

The mental model that helps most: a Durable Object is an actor. It owns one entity — one chat room, one document, one user session, one game match — and it is the single source of truth for that entity’s state and the coordination point for everyone touching it.

Where a Durable Object wins

Reach for one when the work is about a single entity and the hard part is coordination or freshness, not querying.

Coordination. Anything where multiple clients or requests must agree on one answer. A booking system where two people try to grab the last slot. A rate limiter that must count accurately under a burst. A leader election. The single-threaded model means “check then write” is safe without distributed locks, because nothing else runs on that object while you are mid-operation.

Real-time and WebSockets. A collaborative editor, a chat room, a live dashboard, a multiplayer match. The object holds the connections and the shared state in memory, fans out messages to everyone connected, and stays warm between events. WebSocket hibernation lets it drop the in-memory cost while idle and wake on the next message, so long-lived connections do not cost you compute the whole time.

Per-entity state that is read and written constantly. A shopping cart, a session, a document’s live contents. State that belongs to one thing and is touched far more often than it is queried across all things. Keeping it in memory next to strongly consistent storage is faster and simpler than a round trip to a shared database on every keystroke.

Where a database is the right answer

A Durable Object is scoped to one entity by design, and that is exactly its weakness for anything cross-cutting.

Queries across many entities. “Show me every order over $500 from last month.” “Which users have not logged in for 90 days?” You cannot answer that by talking to one object, because the data is spread across thousands of them. A relational database exists precisely to index, filter, join, and aggregate across a whole dataset. If your access pattern is “search,” you want a database.

Reporting and analytics. Anything with GROUP BY, ad-hoc filters, or a dashboard that slices the data a different way each time. Objects are terrible at this; SQL is built for it.

Relationships and integrity across records. Foreign keys, joins, and transactions that must span multiple entities at once belong in a database. A Durable Object transaction is atomic within that one object, not across objects.

The durable record of truth. Even in a heavily object-based design, you often want a database as the canonical store — the thing you can back up, migrate, and query — while objects handle the live, per-entity coordination on top.

The pattern that resolves most cases: use both

The framing “Durable Object or database” is usually a false choice. Mature designs use each for what it is good at.

A common shape: a Durable Object per active entity handles the live coordination — the WebSocket connections, the in-flight edits, the fast reads and writes — and periodically flushes a durable snapshot into D1 or another database, which then serves all the cross-entity queries, reporting, and history. The object is the hot path; the database is the system of record and the query surface.

Think of it as a division of labour. Objects answer “what is happening to this one thing right now, and who else is touching it?” Databases answer “what is true across everything, and what happened over time?”

Pitfalls to plan for

  • The hot object. Because one object is single-threaded, all traffic for one ID funnels through one instance. That is a feature for correctness and a trap for throughput. If a single object would take a firehose of traffic — a global counter every request must increment — it becomes a bottleneck. Shard it: split one logical counter across N objects and sum them, trading exact real-time totals for scale.
  • No cross-object transactions. You cannot atomically update two objects in one transaction. If your invariant spans entities, that invariant probably belongs in a database, or needs a coordinating object that owns both.
  • Naming and routing. Every object is reached by an ID, and you have to decide what that ID is — a user ID, a room name, a document slug. Get the granularity wrong and you either create millions of tiny objects or a few giant ones. Choose the boundary that matches how the data is actually accessed.
  • Migrations and versioning. Object storage schemas evolve too. Because objects wake lazily, a schema change has to tolerate old and new shapes until every object has been touched and upgraded. Plan for a read-time migration rather than a single big rewrite.

The one-line test

Ask what the state is about. If it is about one entity and the difficulty is coordination or freshness — one room, one document, one match, one limiter — a Durable Object fits. If the difficulty is searching, joining, or reporting across many entities, a database fits. Most real systems have both kinds of difficulty, so most real systems use both.

If you are weighing where to put state in something you are building, that architecture call is exactly the kind of decision we like to get right early — it is the sort of work described on our Cloudflare and custom software pages. Send us two paragraphs about what you are building and where the state lives today, and we will reply in writing within one business day.

— Newsletter

Get the writing by email.

An occasional note from the team — case studies, new free tools, engineering essays. Never daily.

Three fields, no tracking. Privacy policy.