Back to Blog
CloudflareServerlessDurable ObjectsArchitectureEdge Computing

Durable Objects: The Missing Piece of Serverless Architecture

7 min read
Share:

Durable Objects
Durable Objects

Serverless promised us freedom. No servers to manage. No scaling to worry about. Just write your function and deploy.

And for simple CRUD operations, it delivered.

But the moment you need state—the moment you need to coordinate concurrent users editing the same document, manage a WebSocket connection that lives for hours, or schedule a task specific to a single user—serverless falls apart.

You end up spinning up a Redis cluster, a WebSocket server, or a full VM just to hold a little state. And just like that, you're back to managing infrastructure.

Cloudflare Durable Objects fix this. And once you understand them, you'll wonder how you ever built real-time applications without them.

The Problem: Stateless by Design

Traditional serverless functions (Lambda, Workers, Cloud Functions) are stateless by design. Each invocation starts from scratch. No memory of what happened before.

If 100 requests come in for "workspace-123", they might be handled by 100 different function instances across 100 different data centers. Each one is isolated and ephemeral.

This is great for scaling, terrible for coordination.

Want two users to edit the same document in real-time? You need a centralized coordination point. Traditionally, that meant:

  • A Redis Pub/Sub cluster
  • A dedicated WebSocket server
  • A database with optimistic locking

All of which defeat the purpose of going serverless in the first place.

What Durable Objects Actually Are

A Durable Object is a globally unique, single-threaded, stateful instance that lives on Cloudflare's edge network.

The key insight: One ID = One Instance. Globally.

When you create a Durable Object with an ID like workspace-123, Cloudflare guarantees that only one instance of that object exists anywhere in the world. Every request targeting that ID is routed to the same instance, no matter where the request originates.

It's like having a dedicated micro-server per user, per room, or per document—except you only pay when it's active, and it scales to zero when idle.

// In your Worker (the entry point)
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Get the unique ID for this workspace
    const id = env.WORKSPACE.idFromName("workspace-123");
    
    // Get a reference (stub) to that specific instance
    const stub = env.WORKSPACE.get(id);
    
    // Forward the request — this ALWAYS goes to the same instance
    return stub.fetch(request);
  }
};
// The Durable Object itself
export class Workspace {
  private state: DurableObjectState;
  private connections: Set<WebSocket> = new Set();

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/ws") {
      // Handle WebSocket upgrade
      const pair = new WebSocketPair();
      this.connections.add(pair[1]);
      pair[1].accept();

      pair[1].addEventListener("message", (msg) => {
        // Broadcast to all connected users
        for (const conn of this.connections) {
          if (conn !== pair[1]) {
            conn.send(msg.data as string);
          }
        }
      });

      return new Response(null, { status: 101, webSocket: pair[0] });
    }

    return new Response("Hello from Workspace!");
  }
}

That's a real-time collaborative workspace in ~40 lines. No Redis. No WebSocket server. No infrastructure.

Four Storage Layers

One of the most powerful aspects of Durable Objects is the storage architecture. Each layer serves a different purpose:

Storage LayerAnalogySpeedPersistenceBest For
In-Memory (class properties)Your desk⚡ Instant❌ Lost on hibernationActive WebSocket connections, session cache
KV Storage (state.storage)Desk drawer🔥 Fast✅ YesUser config, metadata, simple state
SQLite (state.storage.sql)Filing cabinet📊 Fast + Queryable✅ YesStructured data, relationships, indexes
External (R2, D1)Warehouse🏗️ Network call✅ YesLarge files, cross-object analytics
export class UserSession {
  // In-memory: fast but lost on hibernation
  private activeConnections: Map<string, WebSocket> = new Map();

  async fetch(request: Request): Promise<Response> {
    // KV Storage: simple, persistent key-value pairs
    await this.state.storage.put("lastActive", Date.now());
    const preferences = await this.state.storage.get("preferences");

    // SQLite: structured, queryable data
    this.state.storage.sql.exec(
      `INSERT INTO activity_log (action, timestamp) VALUES (?, ?)`,
      "page_view", Date.now()
    );

    // Query with SQL
    const recentActivity = this.state.storage.sql.exec(
      `SELECT * FROM activity_log ORDER BY timestamp DESC LIMIT 10`
    ).toArray();

    return Response.json({ preferences, recentActivity });
  }
}

Alarms: Built-In Scheduling Per Object

Need to send a reminder 30 days after signup? Or auto-expire an inactive session?

Durable Objects have alarms—scheduled callbacks that execute on a specific object at a specific time.

export class Subscription {
  async createTrial(userId: string): Promise<Response> {
    await this.state.storage.put("userId", userId);
    await this.state.storage.put("plan", "trial");
    
    // Set alarm for 14 days from now
    const trialEnd = Date.now() + 14 * 24 * 60 * 60 * 1000;
    await this.state.storage.setAlarm(trialEnd);
    
    return Response.json({ message: "Trial started", expiresAt: trialEnd });
  }

  // This runs automatically when the alarm fires
  async alarm(): Promise<void> {
    const userId = await this.state.storage.get("userId");
    const plan = await this.state.storage.get("plan");
    
    if (plan === "trial") {
      // Trial expired — send notification, downgrade, etc.
      await notifyUser(userId, "Your trial has expired!");
      await this.state.storage.put("plan", "expired");
    }
  }
}

No cron jobs. No separate scheduler service. The alarm lives with the object it belongs to.

When to Use Durable Objects

Perfect for:

  • 🎯 Per-user or per-tenant state (SaaS multitenant apps)
  • 🤖 AI Agents (each agent is its own Durable Object with memory)
  • 📝 Real-time collaboration (Google Docs-style)
  • 💬 Chat rooms and WebSocket servers
  • ⏰ Per-entity scheduled tasks (subscription renewals, reminders)
  • 🚦 Rate limiting per user
  • 🔒 Distributed locks and coordination

Not ideal for:

  • 📦 Storing large files (use R2)
  • 📊 Cross-user analytics (use Workers Analytics Engine or D1)
  • ⚡ Truly stateless operations (use regular Workers)
  • 🔍 Queries across all users (use D1 with proper indexes)

The AI Agent Connection

Here's where Durable Objects get really interesting in 2026: AI Agents.

Each AI agent needs:

  • Persistent memory across conversations
  • The ability to run background tasks
  • WebSocket connections for real-time streaming
  • Scheduled actions (follow-ups, reminders)

A Durable Object is literally the perfect primitive for this. One Durable Object per agent. Each agent has its own memory, its own WebSocket connections, and its own scheduled alarms.

Cloudflare actually built their Agents SDK on top of Durable Objects for exactly this reason.

Pitfalls to Watch For

After working with Durable Objects, here are the gotchas:

1. Single-Threaded Per Instance

Each Durable Object processes one request at a time. If you have a viral chat room with 10,000 concurrent users, that single thread becomes a bottleneck. Solution: Shard across multiple objects.

2. Cold Starts Are Real

If a Durable Object has been hibernating, the first request takes longer (it needs to re-instantiate). Design your UX accordingly.

3. Don't Treat It Like a Database

Durable Objects are for coordination and state, not for storing terabytes of data. Use R2 and D1 for heavy storage.

The Bottom Line

Durable Objects solve the fundamental tension of serverless: how to be stateless at the infrastructure level while being stateful at the application level.

They give you:

  • The simplicity of serverless (no servers to manage)
  • The power of stateful servers (memory, WebSockets, coordination)
  • The efficiency of edge computing (runs close to your users)
  • The economics of pay-per-use (scales to zero)

If you're building real-time apps, AI agents, or multi-tenant SaaS—Durable Objects aren't optional anymore. They're the foundation.


Are you building with Durable Objects? I'd love to hear what you're using them for. Drop a comment below!

BA

Babatunde Abdulkareem

Full Stack & ML Engineer

Like this article

Comments