How Cloudflare Workers run code at the edge
How Cloudflare Workers use V8 isolates instead of containers, the event-driven execution model, KV storage, Durable Objects, and the global Anycast network to execute code within milliseconds of users.
The Interview Question
Interviewer: "Your team deploys API logic to Cloudflare Workers instead of a traditional origin server. A product manager asks why cold starts feel instant compared to AWS Lambda. Walk me through how Workers actually execute your code at the edge, how isolation works without containers, and where the tradeoffs are."
This question tests whether you understand why V8 isolates are fundamentally different from containers, how Anycast routing places requests on nearby machines, and where edge compute breaks down (CPU limits, storage consistency, coordination). The interviewer wants mechanism-level depth, not marketing language about "running code at the edge."
What to Clarify Before Answering
You: "Before I dive in, let me clarify the scope..."
- "Are we talking about the Worker runtime itself, or also the storage primitives like KV, Durable Objects, and R2?"
- "Should I cover how the Anycast network routes requests to the nearest data center?"
- "Is the focus on isolation guarantees (V8 isolates vs containers) or on the execution model (event-driven, CPU time limits)?"
- "Do we need to compare with Lambda@Edge and Deno Deploy, or just explain Workers in isolation?"
Why this matters: "Edge compute" covers routing, isolation, execution, storage, and coordination. A candidate who scopes the answer demonstrates they understand each layer independently. It also prevents you from spending 10 minutes on networking when the interviewer cares about the runtime model.
The 30-Second Answer
Cloudflare Workers run JavaScript (and WASM) inside V8 isolates, not containers or VMs. Each isolate is a lightweight execution context within a shared V8 engine process, with its own heap but no filesystem, no process tree, no network namespace. This gives Workers sub-5ms cold starts compared to 100ms-1s for container-based serverless. When a request arrives, Cloudflare's Anycast network routes it to the nearest of 300+ data centers using BGP. The local machine's runtime dispatches the request to an isolate, which runs the Worker's fetch() handler. The Worker has 50ms of CPU time (not wall-clock time) per request on the free plan, 30 seconds on paid plans. For state, Workers use KV (eventually consistent key-value store replicated to all edges), Durable Objects (single-instance coordination with strong consistency), and R2 (S3-compatible object storage with zero egress fees). The entire model optimizes for latency over raw compute power.
The Architecture Overview
Looking at the diagram above, a user request never touches a centralized origin by default. The Anycast network advertises the same IP address from every data center, so BGP routes the packet to the geographically nearest POP. That POP terminates TLS, parses the HTTP request, and hands it to the Worker runtime. The runtime selects (or creates) an isolate for the matching Worker script and invokes the fetch() handler. The Worker can read from KV, coordinate through Durable Objects, store files in R2, or make subrequests to an origin server.
The critical insight is that the isolate is already warm in most cases. Cloudflare keeps isolates alive for minutes between requests, so repeat traffic avoids even the sub-5ms cold start. This is why Workers feel faster than Lambda for latency-sensitive workloads.
V8 Isolates: Containers Without the Container
The core innovation of Cloudflare Workers is replacing containers with V8 isolates for multi-tenant isolation. I find this the most important concept to understand because it explains almost every tradeoff in the platform.
What is a V8 Isolate?
A V8 isolate is an independent instance of the V8 JavaScript engine's heap and execution context. Multiple isolates run within a single operating system process. Each isolate gets its own JavaScript heap, its own garbage collector, and its own set of compiled code. But they share the V8 engine binary, the underlying OS process, and the CPU.
Think of it like apartments in a building. Each apartment (isolate) has its own interior walls, furniture, and locks. But they share the building's foundation, plumbing, and electrical systems (the OS process and V8 engine). A container-based approach would be more like separate houses, each with its own foundation, plumbing, and land.
Why Isolates Instead of Containers?
The difference comes down to startup time and memory overhead. A container needs a filesystem image, a process tree, network namespace setup, and cgroup configuration. Even a minimal container takes 50-200ms to start cold. A V8 isolate takes less than 5ms because it only needs to allocate a heap and initialize the JavaScript context.
| Property | V8 Isolate | Container (Lambda) | microVM (Firecracker) |
|---|---|---|---|
| Cold start | < 5ms | 100ms-1s | 125ms |
| Memory overhead | ~3-5MB per isolate | 35-50MB per container | 20-30MB per VM |
| Isolation level | V8 heap separation | OS process + namespace | Hardware virtualization |
| File system access | None | Full (within container) | Full (within VM) |
| Language support | JS/TS/WASM only | Any language | Any language |
| Max execution time | 30s CPU (paid) | 15 minutes | 15 minutes |
The isolation tradeoff
V8 isolates share an OS process. This means a V8 engine bug could theoretically leak data between isolates. Cloudflare mitigates this with multiple layers: per-isolate memory limits, CPU time enforcement via V8's own interrupt mechanism, and runtime monitoring for anomalous behavior. But the isolation boundary is weaker than a container or VM. For compliance-heavy workloads requiring strict tenant isolation, this matters.
Memory Isolation in Practice
Each isolate has a hard memory limit (currently 128MB). If a Worker exceeds this, the isolate is terminated and the request receives an error. The V8 garbage collector runs independently per isolate, so one tenant's GC pressure does not affect another tenant's latency.
There is no shared mutable state between isolates. Each Worker script gets a fresh global scope. The globalThis object in one isolate is completely invisible to another. Even two requests to the same Worker script may land in different isolates, so you cannot rely on in-memory state persisting between requests (though Cloudflare does reuse warm isolates when possible).
The Request Lifecycle: From User to Isolate
When a user makes an HTTPS request to a domain proxied through Cloudflare, several systems coordinate to get that request to a Worker and back. I will walk through each step.
Step 1: Anycast Routing
Cloudflare advertises the same IP address range from every one of its 300+ data centers using BGP Anycast. When a user's device resolves the DNS name, it gets an IP address that belongs to Cloudflare's Anycast range. The user's ISP router then forwards the packet to the nearest Cloudflare POP based on BGP shortest-path routing.
This means a user in Tokyo hits the Tokyo POP, a user in Frankfurt hits the Frankfurt POP, and a user in Sao Paulo hits the Sao Paulo POP. No centralized load balancer, no geographic DNS tricks. BGP itself does the routing.
Step 2: TLS Termination and HTTP Parsing
The POP terminates the TLS connection. Cloudflare manages the TLS certificates, so the Worker never sees raw TCP or handles certificate management. The POP parses the HTTP request, extracts headers, body, and URL, then hands a structured request object to the Worker runtime.
Step 3: Worker Dispatch
The runtime matches the request URL against configured Worker routes (e.g., api.example.com/*). It then dispatches the request to an isolate running that Worker's script. If a warm isolate exists from a recent request, the runtime reuses it. If not, it cold-starts a new isolate in under 5ms.
Step 4: Execution
The Worker's fetch() event handler runs. It receives a Request object, can make subrequests using fetch(), access KV or Durable Objects, and must return a Response object. The CPU time clock tracks only actual computation, not I/O wait. So a Worker that spends 49ms waiting for a subrequest and 1ms computing still only used 1ms of CPU time.
CPU time vs wall-clock time
This distinction is crucial. Workers are billed on CPU time, not wall-clock time. A Worker that makes 5 sequential fetch() calls to external APIs might take 500ms of wall-clock time but only 2ms of CPU time. The 50ms CPU limit on paid plans is generous when you understand this distinction.
Step 5: Response
The runtime serializes the Response object back to HTTP, the POP encrypts it with TLS, and sends it back to the user. The entire round trip (including Worker execution) typically completes in 10-50ms for edge-cached responses, or 50-200ms when the Worker makes subrequests to an origin server.
Workers KV: Eventually Consistent Edge Storage
Workers KV is a globally distributed key-value store optimized for read-heavy workloads. I think of it as a global cache with write-behind propagation rather than a traditional database.
How KV Works Internally
When you write a key to KV, the write goes to a central store. Cloudflare then propagates that value to all 300+ edge locations. Reads always go to the local edge, which is why reads are fast (under 1ms) but writes take time to propagate globally (up to 60 seconds, typically under 10 seconds).
KV Characteristics
| Property | Value |
|---|---|
| Max key size | 512 bytes |
| Max value size | 25 MB |
| Read latency (cached) | < 1ms |
| Write propagation | Up to 60 seconds globally |
| Consistency model | Eventually consistent |
| Operations per second | Unlimited reads, ~1 write/sec per key |
| Storage limit | Unlimited (paid plan) |
KV is not a database
KV is optimized for read-heavy, write-rare workloads like configuration, feature flags, and static asset metadata. If you need more than ~1 write per second per key, or you need read-after-write consistency, KV is the wrong tool. Use Durable Objects instead.
When to Use KV
Use KV for data that changes infrequently and tolerates eventual consistency: feature flags, configuration JSON, localization strings, A/B test assignments, and cached API responses. Do not use KV for counters, session state, or anything where stale reads cause correctness issues.
Durable Objects: Single-Instance Coordination
Durable Objects solve the problem that KV cannot: strong consistency and coordination. Each Durable Object is a JavaScript class instance that runs on exactly one machine globally. All requests to the same Durable Object ID are routed to that single instance, giving you linearizable reads and writes.
How Durable Objects Work
When you create a Durable Object with a specific ID (e.g., env.COUNTER.idFromName("room-123")), Cloudflare routes all requests for that ID to a single data center. The object runs in an isolate on one machine. It has an in-memory state (the JavaScript object), a durable transactional storage API (SQLite-backed), and processes requests sequentially.
All requests for room-123 go to Dallas (or wherever Cloudflare places the object). The Workers in Tokyo and London forward their requests over Cloudflare's backbone network. The Durable Object processes them one at a time, ensuring strong consistency.
The Cost of Strong Consistency
The tradeoff is latency. If a user in Tokyo hits a Durable Object homed in Dallas, the round trip adds 100-200ms of network latency. Cloudflare mitigates this by placing Durable Objects near the first requester (using "jurisdiction hints" or automatic placement). But the physics of the speed of light mean that globally distributed users will see variable latency to a single-location object.
Durable Objects Use Cases
Durable Objects are the right choice for: chat rooms (WebSocket state), collaborative editing (conflict resolution), rate limiters (accurate counters), shopping carts (consistency), game state (authoritative server), and coordination locks (distributed locking without external services).
R2: S3-Compatible Object Storage
R2 is Cloudflare's object storage service. It is API-compatible with S3 (you can use existing S3 SDKs by changing the endpoint URL), but it has zero egress fees. This makes it attractive for workloads where data is read frequently by Workers or served to users.
Why R2 Exists
Traditional cloud storage charges for egress bandwidth. If you store 1TB in S3 and serve it globally, the egress fees dwarf the storage cost. R2 eliminates egress entirely. You pay only for storage ($0.015/GB/month) and operations (Class A: $4.50/million, Class B: $0.36/million).
| Property | R2 | S3 Standard |
|---|---|---|
| Storage cost | $0.015/GB/month | $0.023/GB/month |
| Egress cost | $0 | $0.09/GB |
| PUT/POST | $4.50/million | $5.00/million |
| GET | $0.36/million | $0.40/million |
| Durability | 99.999999999% (11 nines) | 99.999999999% (11 nines) |
| S3 API compatible | Yes | Native |
Workers access R2 directly through bindings (zero-latency, no network hop). External clients access R2 through an S3-compatible API endpoint.
Execution Limits and Runtime Constraints
Understanding what Workers cannot do is as important as understanding what they can do. The platform enforces strict limits that shape how you architect solutions.
Limits Table
| Limit | Free Plan | Paid (Bundled) | Paid (Unbound) |
|---|---|---|---|
| CPU time per request | 10ms | 50ms | 30 seconds |
| Memory per isolate | 128MB | 128MB | 128MB |
| Subrequests per request | 50 | 50 | 1,000 |
| Script size | 1MB | 10MB | 10MB |
| KV reads per request | 1,000 | 1,000 | 1,000 |
| Environment variables | 64 | 128 | 128 |
| Request body size | 100MB | 100MB | 100MB |
CPU time is the real constraint
Most developers hit the CPU time limit before any other limit. Remember, this is compute time, not wall-clock time. Heavy JSON parsing, crypto operations, and image manipulation consume CPU time quickly. If your Worker does complex computation, consider offloading to a regular server or using WebAssembly for performance-critical paths.
What Workers Cannot Do
Workers have no access to: raw TCP/UDP sockets (only HTTP, WebSocket, and a limited connect() API), the filesystem (no fs module, no temp files), long-running background processes (each request must complete), native Node.js modules (though many are now shimmed via the nodejs_compat flag), and blocking operations (no sleep(), everything is async).
These constraints mean Workers are not suitable for: video transcoding, ML model inference (large models), database servers, or any workload requiring persistent processes.
What Happens When Things Break
| Failure | What Happens | How to Detect | How to Fix |
|---|---|---|---|
| CPU time exceeded | Request returns 1102 error | exceeded.cpuMs in Worker logs | Optimize hot paths, move compute to origin |
| Memory limit exceeded | Isolate killed, request returns error | exceeded.memory in logs | Reduce allocations, stream instead of buffer |
| Subrequest limit hit | fetch() throws exception | Error in Worker logs | Batch API calls, use fewer subrequests |
| KV read stale data | Silent, returns old value | Compare with central store | Design for eventual consistency, use cache TTL |
| Durable Object overloaded | Requests queue, latency spikes | Increasing response times | Shard across multiple DOs |
| Edge POP failure | Anycast reroutes to next POP | Automatic, transparent | No action needed, Anycast handles failover |
| Script deployment failure | Old version continues serving | Wrangler deploy error output | Fix script errors, redeploy |
The key resilience insight
Because Workers run on 300+ POPs with Anycast routing, individual POP failures are invisible to users. Traffic automatically shifts to the next nearest POP. This is a fundamentally different failure model from centralized serverless platforms where a region outage takes down your function.
Performance Characteristics
| Operation | Latency | Notes |
|---|---|---|
| Cold start (new isolate) | < 5ms | V8 isolate creation, no container overhead |
| Warm request (reused isolate) | < 1ms dispatch | Isolate already initialized |
| KV read (cached at edge) | < 1ms | Local edge cache hit |
| KV write propagation | 10-60 seconds | Eventually consistent replication |
| Durable Object read/write | 1-5ms (co-located), 50-200ms (cross-region) | Depends on DO placement |
| R2 GET (from Worker) | 5-20ms | Direct binding, no network hop |
| R2 GET (from internet) | 20-100ms | Via S3-compatible API endpoint |
| Subrequest to origin | 20-500ms | Depends on origin location |
| WASM execution overhead | ~1.5x native | V8 WASM runtime, JIT compiled |
How This Compares to Alternatives
| Feature | Cloudflare Workers | AWS Lambda@Edge | Deno Deploy | Fastly Compute |
|---|---|---|---|---|
| Cold start | < 5ms | 50ms-1s | < 10ms | < 35ms |
| Runtime | V8 isolates | Containers (Node.js) | V8 isolates | WASM (Wasmtime) |
| Max execution | 30s CPU (paid) | 30s (viewer), 60s (origin) | Unlimited (paid) | 60s |
| Edge locations | 300+ | 13 CloudFront POPs | 35+ regions | 70+ POPs |
| KV storage | Workers KV | DynamoDB (not at edge) | Deno KV | Built-in KV |
| Strong consistency | Durable Objects | Not built-in | Not built-in | Not built-in |
| Object storage | R2 (zero egress) | S3 | Not built-in | Not built-in |
| Language support | JS/TS/WASM | Node.js/Python | JS/TS/WASM | WASM (any lang) |
| Free tier | 100K req/day | No free tier | 1M req/month | No free tier |
I reach for Cloudflare Workers when I need sub-10ms response times at the edge, globally distributed reads from KV, and simple coordination through Durable Objects. I switch to Lambda when the workload needs more than 30 seconds of compute, requires native binary execution, or needs deep AWS service integration. I consider Deno Deploy when the team wants a Node.js-compatible API surface with fewer platform-specific abstractions.
Interview Cheat Sheet
- When asked about cold starts: "Workers use V8 isolates, not containers. An isolate is a lightweight JavaScript execution context within a shared V8 process. Cold start is under 5ms because there is no filesystem, no process tree, and no network namespace to initialize."
- When asked about isolation: "Each isolate has its own heap, its own GC, and no shared mutable state. The isolation boundary is the V8 engine, not an OS process or VM. This is weaker than container isolation, but 100x faster to start."
- When asked about routing: "Cloudflare uses BGP Anycast. The same IP is advertised from 300+ locations. BGP shortest-path routing sends each user to the nearest POP. No DNS-based geo-routing needed."
- When asked about consistency: "KV is eventually consistent with 10-60 second propagation. For strong consistency, use Durable Objects, which run on exactly one machine and process requests sequentially."
- When asked about limits: "The key constraint is CPU time, not wall-clock time. A Worker waiting on I/O does not consume CPU time. The 50ms CPU limit on paid plans is generous for most request-handling logic."
- When asked about state management: "Use KV for read-heavy, write-rare data. Use Durable Objects for coordination, counters, and WebSocket state. Use R2 for large objects. Never rely on isolate-local memory persisting between requests."
- When asked about R2 vs S3: "R2 is S3-compatible with zero egress fees. Use the same SDKs, change the endpoint URL. The cost advantage grows with read-heavy workloads where egress dominates S3 bills."
- When asked about WASM: "Workers support WebAssembly for CPU-intensive code. Rust, C, and Go can compile to WASM and run in the same isolate. This gives near-native performance for compute-heavy tasks within the same isolation model."
- When asked about Durable Objects placement: "Cloudflare places a Durable Object near its first requester. Subsequent requests from different regions pay cross-region latency. You can use jurisdiction hints to control placement for data residency compliance."
Test Your Understanding
Quick Recap
- Cloudflare Workers run JavaScript and WASM inside V8 isolates, not containers, giving sub-5ms cold starts and ~3MB memory overhead per tenant.
- Anycast routing (BGP) directs every request to the nearest of 300+ edge POPs without DNS-based geo-routing.
- Workers are billed on CPU time, not wall-clock time, so I/O-heavy Workers use far less CPU than you might expect.
- Workers KV provides globally replicated reads under 1ms but is eventually consistent with up to 60-second propagation delay.
- Durable Objects provide strong consistency by running a single instance on one machine, at the cost of cross-region latency for distant users.
- R2 is S3-compatible object storage with zero egress fees, accessible directly from Workers through bindings.
- The primary constraints are 128MB memory per isolate, 50ms-30s CPU time limits, and no filesystem or raw socket access.
- The isolation model (V8 heap separation) is weaker than container or VM isolation, but the performance tradeoff is what makes edge compute viable at Cloudflare's scale.
Related Concepts
- How Lambda Works - Compare container-based serverless with isolate-based edge compute to understand when each model fits.
- How Linux Containers Work - Understand the namespace and cgroup isolation that Workers deliberately avoid for faster startup.
- How S3 Works - R2's design is directly inspired by S3's durability model, and understanding S3 internals helps evaluate R2's tradeoffs.