---
title: "07 — Durable Objects: Stateful Single-Actor Coordination"
uid: durable-objects
tags: ["durable-objects", "coordination", "stateful", "consistency", "cloudflare", "roadmap:cloudflare", "transactions"]
excerpt: "A Durable Object is a single-actor, strongly consistent instance — exactly one per key, worldwide — where all concurrent clients rendezvous. That property is the whole point."
date: 2026-08-13T03:28:18+0000
source: https://www.aveshina.my.id/en/blog/durable-objects
---

"Stateful Workers" was my Durable Objects summary, and it missed the part that makes them unlike anything else. The model that pinned it: **a Durable Object is a single-actor, strongly consistent, stateful instance — there is exactly one of a given object in the world, and all concurrent clients rendezvous at it.** [1] That single-actor property is the whole point, and it's what makes Durable Objects unlike anything else in the storage lineup.

The framing that landed is the contrast with KV. KV is eventually consistent and globally replicated — great for read-heavy data, bad for coordination. Durable Objects are the opposite: there's _one_ authoritative instance for a given ID, all writes serialize through it, and the state is consistent because there's no second copy to disagree with [1][2]. This is the layer for problems that genuinely cannot be solved without a single coordinator: a real-time document edit, a room with a participant count, an auction where two bids must not collide.

```figure
<svg viewBox="0 0 740 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Durable Objects single-actor model. Multiple clients on the left all route to the same single Durable Object instance on the right, identified by an ID. The object holds consistent state. A label notes: exactly one instance of a given ID exists worldwide.">
  <defs>
    <marker id="obarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- clients -->
    <rect x="30" y="50" width="90" height="36" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="75" y="72" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">client A</text>
    <rect x="30" y="120" width="90" height="36" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="75" y="142" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">client B</text>
    <rect x="30" y="190" width="90" height="36" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="75" y="212" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">client C</text>

    <!-- routing -->
    <path d="M120,68 L300,130" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#obarrow)"/>
    <path d="M120,138 L300,138" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#obarrow)"/>
    <path d="M120,208 L300,146" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#obarrow)"/>

    <text x="210" y="100" font-size="9" fill="#64748b" text-anchor="middle" font-style="italic">all route by ID</text>

    <!-- the single object -->
    <rect x="305" y="100" width="180" height="80" rx="10" fill="#dcfce7" stroke="#16a34a" stroke-width="2"/>
    <text x="395" y="124" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">Durable Object</text>
    <text x="395" y="140" font-size="10" fill="#052e16" text-anchor="middle">id: "room-42"</text>
    <text x="395" y="158" font-size="9" fill="#052e16" text-anchor="middle">one instance · consistent state</text>
    <text x="395" y="172" font-size="9" fill="#052e16" text-anchor="middle">in-memory + persistent storage</text>

    <!-- KV contrast -->
    <rect x="540" y="100" width="160" height="80" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5" stroke-dasharray="4,3"/>
    <text x="620" y="124" font-size="10" font-weight="700" fill="#500724" text-anchor="middle">KV (for contrast)</text>
    <text x="620" y="140" font-size="9" fill="#500724" text-anchor="middle">many replicas</text>
    <text x="620" y="156" font-size="9" fill="#500724" text-anchor="middle">eventually consistent</text>
    <text x="620" y="172" font-size="9" fill="#500724" text-anchor="middle">no coordination</text>

    <!-- bottom -->
    <text x="370" y="230" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">writes serialize through the one object — no two clients can collide</text>
    <text x="370" y="252" font-size="10" font-style="italic" fill="#64748b" text-anchor="middle">the right tool when "exactly one coordinator" is the actual requirement</text>
  </g>
</svg>
```

## State management

Each Durable Object instance owns its own state, and that state survives across requests [2][3]. The object has two layers of storage:

- **In-memory state.** While the object is active (handling a request or holding a WebSocket open), JavaScript variables in the object persist between calls. This is genuine server state — a counter, a list of connected clients, a parsed config.
- **Transactional storage.** A key-value-style storage API (blockConcurrencyWhile, transaction, put/get/delete) backed by Cloudflare's storage. This is the durable part — it survives even if the object is evicted from memory and re-instantiated later.

The combination is the powerful part. The object can hold live state for active clients (the WebSocket connections in a room, the current cursor positions) and persist anything that must survive a restart (the room's message history, the canonical document state). Workers themselves are stateless; Durable Objects are where state that _must_ be consistent and live goes.

## Coordination

The single-actor property is what makes coordination possible [4]. Because there's exactly one instance of a given object ID in the world, multiple clients interacting with that ID are all talking to the same JavaScript object. That object can:

- Accept WebSocket connections from multiple clients and broadcast messages between them (a chat room, a collaborative editor).
- Serialize conflicting operations — two clients trying to update the same counter both pass through the object, which applies them in order.
- Act as the single source of truth for a bounded piece of state — the participants in a game, the current high bid in an auction, the lock on a resource.

Without Durable Objects, this category of problem requires an external coordination system (Redis, a database with row locks, a dedicated server). With them, the coordination primitive is built into the platform, addressed by an ID I choose.

## Persistence

Persistence is automatic, but the granularity is mine to design [5][6]. The transactional storage API lets the object read and write durable key-value pairs, and the persistence model guarantees:

- **Durability.** Once put returns, the value is durable — it survives object eviction, edge failures, and reboots.
- **In-memory caching.** The object can cache hot state in memory for the duration of its active lifetime, falling back to storage on cold starts.
- **Block-and-serialize semantics.** Operations like blockConcurrencyWhile let the object perform initialization or migration without racing with concurrent requests.

The discipline this enforces is worth embracing: anything the object can't afford to lose goes into transactional storage on every meaningful change. In-memory state is a cache; storage is the truth.

## Transactional operations

For multi-step state changes, Durable Objects offer explicit transactions [7]:

```
await ctx.storage.transaction(async (txn) => {
  await txn.put("balance:a", newA);
  await txn.put("balance:b", newB);
});
```

Either both writes succeed, or neither does. If anything throws, the transaction rolls back and the state is left consistent. This is the answer to "transfer 5 from account A to account B without ever leaving a state where the money is in neither" — the classic transactional problem, now available at the edge, scoped to a single object.

The constraint that shapes when transactions matter: they're scoped to _one_ object. A transaction can atomically update multiple keys within a single Durable Object's storage; it cannot atomically update two different objects. Cross-object atomicity isn't a primitive — it has to be designed around (sagas, compensating writes, or accepting the eventual-consistency trade).

## Where Durable Objects fit (and don't)

The decision rule I use:

- **Use Durable Objects** for: real-time coordination (chat, collab, presence), per-resource counters that must be exact, single-writer resources, anything where two concurrent operations genuinely must not collide, WebSocket fan-out.
- **Don't use Durable Objects** for: read-heavy global data (that's KV), structured queryable data (that's D1), bulk blob storage (that's R2). Durable Objects are the specialized tool for coordination, not a general-purpose store.

The cost model also shapes the decision — Durable Objects bill per request and per duration, so a workload that fans out to millions of independent objects has a different cost profile than one with a handful of long-lived objects. The pattern is to choose object IDs that naturally shard the workload (one object per room, per document, per user session) rather than one giant object for everything.

## How I use this

The shape I keep coming back to: identify the resource that needs single-actor coordination, use its natural ID as the Durable Object ID, hold live client connections and in-memory state on the object, and persist anything durable to transactional storage on every meaningful change. When I'm tempted to reach for Durable Objects, I first check whether the requirement is actually strong consistency — if eventual consistency would do, KV is simpler and cheaper. Durable Objects earn their complexity only when "exactly one coordinator" is the real requirement.

## References

[1] Cloudflare, "Cloudflare Durable Objects — Cloudflare Docs," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/durable-objects/](https://developers.cloudflare.com/durable-objects/)

[2] Cloudflare, "What are Durable Objects?," Cloudflare Documentation, 2024. [Online]. Available: [https://developers.cloudflare.com/durable-objects/what-are-durable-objects/](https://developers.cloudflare.com/durable-objects/what-are-durable-objects/)

[3] Cloudflare, "Durable Object state · Cloudflare Durable Objects," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/durable-objects/api/state/](https://developers.cloudflare.com/durable-objects/api/state/)

[4] Cloudflare, "Durable Objects," Cloudflare Developer Platform, 2024. [Online]. Available: [https://www.cloudflare.com/developer-platform/products/durable-objects/](https://www.cloudflare.com/developer-platform/products/durable-objects/)

[5] Cloudflare, "Access Durable Objects storage — Cloudflare Docs," Cloudflare Docs. [Online]. Available: [https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/)

[6] Cloudflare, "In-memory state in a Durable Object," Cloudflare Docs. [Online]. Available: [https://developers.cloudflare.com/durable-objects/reference/in-memory-state/](https://developers.cloudflare.com/durable-objects/reference/in-memory-state/)

[7] Cloudflare, "Durable Object storage · Cloudflare Durable Objects," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/durable-objects/api/storage-api/](https://developers.cloudflare.com/durable-objects/api/storage-api/)

```quiz
Q: What makes a Durable Object different from every other storage option on Workers?
- It's the only one that supports SQL queries
- There is exactly one instance of a given object ID worldwide — it's a single-actor coordinator
- It's the only one that replicates globally for low-latency reads
correct: 1
explain: The single-actor property is the whole point. All clients addressing the same ID rendezvous at one JavaScript object, so writes serialize and state is consistent because there's no second copy to disagree with.

Q: Two clients both try to increment a counter stored on a Durable Object simultaneously. What happens?
- One update may be lost — eventual consistency applies
- The object serializes them; both increments apply in order, none lost
- The second client gets an error and must retry
correct: 1
explain: Because there's one object, concurrent operations pass through it one at a time. The counter ends up correctly incremented by two — this is coordination that eventually-consistent stores can't do.

Q: An object sets a JavaScript variable in memory, then the object is evicted and re-instantiated. What's true of that variable?
- It's automatically restored from storage
- It's gone — in-memory state is a cache; only what was written to transactional storage survives
- The object refuses to re-instantiate until the variable is restored
correct: 1
explain: In-memory state lives only while the object is active. Anything that must survive eviction has to be written to transactional storage explicitly. Treat memory as a cache, storage as the truth.

Q: What scope does a Durable Object transaction have?
- It can atomically update keys across multiple different Durable Objects
- It can atomically update multiple keys within one object's storage — not across objects
- It can atomically update KV, D1, and the object's storage together
correct: 1
explain: Transactions are scoped to a single object. Cross-object atomicity isn't a primitive — it must be designed around with sagas or compensating writes, or the workload has to be sharded so all relevant state lives on one object.

Q: When should you NOT reach for Durable Objects?
- For a real-time collaborative editor that needs single-writer coordination
- For read-heavy global configuration data that tolerates brief staleness
- For per-resource presence tracking across WebSockets
correct: 1
explain: Read-heavy, eventually-consistent data is KV's job — simpler and cheaper. Durable Objects earn their complexity only when strong coordination is the actual requirement.
```
