Cloudflare Workers runs JavaScript, TypeScript, or WebAssembly at Cloudflare's edge locations rather than in a single origin data center — the code executes in the V8 isolate closest to the requesting user, with cold starts measured in single-digit milliseconds because isolates start far cheaper than a container or a VM. That changes what patterns make sense: the constraints (no persistent local disk, a CPU-time budget per request, no long-lived in-memory state across requests) shape the architecture as much as the speed benefit does.
The isolate model, and why it's fast
A Worker runs inside a V8 isolate — the same lightweight execution context V8 uses to sandbox tabs in Chrome — rather than inside a container or microVM. Isolates share a V8 process, so spinning one up avoids the OS-level overhead of a container cold start entirely. The tradeoff is the execution model: each request gets a fresh (or pooled but stateless-from-your-perspective) isolate, CPU time per request is capped (10ms on the free tier, up to 30s+ on paid plans depending on configuration), and there's no filesystem — any persistence has to go through Cloudflare's bound services.
KV, Durable Objects, and R2 — picking the right one
Workers KV is an eventually-consistent, globally-replicated key-value store — good for data that's read far more than written and can tolerate a few seconds of staleness after a write (config flags, cached API responses, feature flags). Durable Objects provide strongly consistent, single-instance state with built-in coordination — the right tool when you need a single source of truth per key, like a WebSocket connection's session state, a rate limiter, or a per-user counter that must never race. R2 is Cloudflare's S3-compatible object storage, notable mainly for having no egress fees, which makes it attractive for serving large static assets from a Worker without the bandwidth bill.
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const cacheKey = `page:${url.pathname}`;
const cached = await env.PAGE_CACHE.get(cacheKey);
if (cached) {
return new Response(cached, {
headers: { "content-type": "text/html", "x-cache": "HIT" },
});
}
const origin = await fetch(`https://origin.example.com${url.pathname}`);
const body = await origin.text();
ctx.waitUntil(
env.PAGE_CACHE.put(cacheKey, body, { expirationTtl: 300 })
);
return new Response(body, {
headers: { "content-type": "text/html", "x-cache": "MISS" },
});
},
};
Patterns that fit the edge model well
A/B testing and feature flagging at the edge avoids a client-side flash of the wrong variant, since the Worker can decide and rewrite the response before it reaches the browser. API request aggregation — fanning a single client request out to multiple backend calls and merging the response at the edge — cuts round trips for mobile clients on slow networks. Auth-at-the-edge (validating a JWT or session cookie in the Worker before the request ever reaches origin) offloads simple checks from origin infrastructure and rejects bad requests closer to the user.
Image transformation, large JSON parsing, or anything CPU-bound can hit the per-request CPU time limit in ways that are invisible in local testing but fail intermittently in production under load. Offload genuinely heavy compute to a proper backend or Cloudflare's dedicated Images/Workers AI products rather than trying to force it through a general-purpose Worker.
There is no reliable in-memory state between requests
Global variables declared outside the `fetch` handler can persist across requests within the same isolate as an optimization, but Cloudflare gives no guarantee about isolate lifetime or which isolate handles the next request — treating any in-memory value as a cache that might vanish at any moment is the only safe mental model. Anything that needs to survive belongs in KV, Durable Objects, or an external store, never in a module-level variable you're hoping sticks around.
Because there's no SSH access and no persistent process to attach a debugger to, wrangler tail — streaming live logs from a deployed Worker — is the primary production debugging tool. Structuring logs as JSON from day one makes that stream far more useful once there's real traffic to sift through.
When Workers isn't the right fit
Long-running background jobs, anything requiring a persistent filesystem, or workloads needing more CPU time than the platform allows belong on a traditional server or a queue-backed worker instead. Workers earns its place at the request path — the layer between the user and your origin — not as a general-purpose compute platform.
Cloudflare Workers rewards designs that lean into statelessness and short execution windows: cache aggressively with KV, reach for Durable Objects only when you genuinely need strong consistency, and push anything CPU-heavy back to origin rather than fighting the isolate model.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.