Choosing Between Cloudflare Workers KV and Durable Objects for Stateful Workers
When a Cloudflare Worker needs mutable state, pick the right storage: KV for global, eventually consistent caching; Durable Objects for strong consistency, low‑latency per‑instance state. This guide compares options, explains trade‑offs, and shows a concrete example with validation steps.
15 Nov 2025, 00:39 UTC

The Decision
Cloudflare Workers can run anywhere in the network, but the platform itself is stateless. When your application needs mutable data—counters, session data, or real‑time collaboration—you must pick a storage service that fits your consistency, latency, and cost requirements. The two built‑in options are Workers KV and Durable Objects.
Constraints to Consider
- Consistency Needs: Do you need immediate read‑after‑write guarantees or can you tolerate eventual consistency?
- Latency Sensitivity: How close to the user must the data be accessed?
- Throughput & Scale: Do you expect millions of writes per second or just occasional updates?
- Cost Model: Are you billed per read/write or per compute‑second?
- Operational Overhead: Do you want a simple key/value API or a full object lifecycle?
KV vs Durable Objects
| Feature | Workers KV | Durable Objects |
|---|---|---|
| Consistency | Eventual (writes may propagate up to a few seconds) | Strong, immediate (within the same object instance) |
| Latency (local read) | 10–30 ms (global edge) | 5–15 ms (within the same zone) |
| Throughput | High – partitioned across many nodes; limited by per‑key rate limits (≈1 kWPS per key) | Limited to one request per second per instance; can scale by creating many instances |
| Cost | Per read/write operation; free up to certain quota, then $0.0004/1 M reads, $0.0004/1 M writes | Per compute‑second; free tier 100 k compute‑seconds/month, then $0.0004/compute‑second |
| Use Case Fit | Read‑heavy caches, feature flags, static content pointers | Mutable counters, WebSocket sessions, collaborative documents, per‑user state |
Trade‑offs
- Eventual vs Strong: With KV, a write may not be visible to a subsequent read immediately, which can cause race conditions in counters. Durable Objects guarantee that a write is visible to the same instance right away.
- Routing Overhead: Durable Objects require the worker to target a specific object instance via
env.MY_OBJ.get(id). If many requests target the same instance, contention occurs, raising latency. - Scalability Pattern: KV scales automatically by sharding keys. Durable Objects need manual sharding—creating multiple objects for high write volumes.
- Cost Predictability: KV costs are tied to operation counts, making it easier to forecast. Durable Objects cost depends on active compute time, which can spike during heavy WebSocket traffic.
- Operational Simplicity: KV is a simple key/value API; Durable Objects expose a full JavaScript class with lifecycle hooks, which adds complexity.
Concrete Example: Counter with Durable Objects
Below is a minimal counter that increments a value stored in a Durable Object. The counter demonstrates strong consistency and per‑instance state isolation.
export class Counter {
constructor(state) { this.state = state; }
async increment() {
// Retrieve current value, defaulting to 0
let val = (await this.state.storage.get('v')) ?? 0;
val++;
await this.state.storage.put('v', val);
return val;
}
}
Worker code that calls the counter:
export default {
async fetch(request, env) {
const id = env.COUNTER.idFromName('global-counter');
const obj = env.COUNTER.get(id);
const newVal = await obj.increment();
return new Response(`Counter value: ${newVal}`);
}
};
Validation Checklist
- Local Test: Run
wrangler devand send concurrentcurlrequests to the worker. Verify that each response shows an incrementing number without gaps. - Consistency Check: After a write, immediately read the same key in a separate request. The value should match the latest write.
- Latency Metrics: In the Cloudflare Dashboard, view the “Requests” tab for the worker and the “Durable Object” binding. Look for
Average latencyaround 5–15 ms under light load. - Throughput Test: Use a simple load generator (e.g.,
heyorwrk) to hit 500 req/s. Observe that latency spikes after ~1 req/s per instance—indicating the per‑instance limit. - Cost Estimation: Estimate compute‑seconds by multiplying the number of concurrent requests by the average duration (e.g., 5 ms) and compare against the free tier.
- Failure Mode: Simulate a network partition by temporarily disabling the worker’s connection to the Durable Object. Observe that reads still return the last known value, confirming strong consistency.
When to Prefer KV
- Data is largely read‑heavy with infrequent writes.
- Eventual consistency is acceptable (e.g., feature flags, cache entries).
- You need global availability with minimal routing logic.
- Cost per operation is predictable and lower for high read volumes.
When to Prefer Durable Objects
- Immediate read‑after‑write guarantees are required (e.g., counters, session data).
- State is tightly coupled to a specific user or session (e.g., WebSocket rooms).
- You need to run custom logic on state changes (e.g., validation, side‑effects).
- Throughput per key is low enough that the per‑instance limit is not a bottleneck.
Summary
Pick Workers KV for global, read‑heavy, eventually consistent data. Pick Durable Objects when you need per‑instance strong consistency, low latency, and custom state logic. The decision hinges on consistency, latency, throughput, and cost. Use the validation checklist above to confirm that your chosen solution meets the application’s requirements before production rollout.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.