Why Serverless Functions Have Cold Starts
A cold start is the extra latency a serverless function pays when a new execution environment must be provisioned before it can run. Causes and fixes.
A cold start is the extra latency a serverless function incurs when a request arrives and no warm execution environment is available to handle it — the platform has to provision a new one, load the code into it, and initialize the runtime before your function’s actual logic can run. A “warm” invocation, by contrast, reuses an environment that’s still alive from a previous request and skips straight to execution. The gap between the two can be the difference between single-digit-millisecond and multi-hundred-millisecond response times, and it’s one of the main tradeoffs teams weigh when deciding whether serverless fits a given workload.
What actually happens during a cold start
A cold start isn’t one thing — it’s a stack of setup costs that all have to clear before your code runs:
- Resource allocation. The platform schedules a new sandboxed execution environment (typically a lightweight VM or container) somewhere in its fleet.
- Runtime initialization. The language runtime — the Node.js engine, the JVM, the Python interpreter — boots up inside that environment.
- Code download and initialization. Your deployment package is fetched and loaded, and any top-level (module-scope) code runs: imports, SDK client construction, database connection setup, configuration parsing.
- Handler execution. Only now does your actual function logic run, which is the part a warm invocation skips straight to.
Runtime choice matters a lot here: the JVM’s cold start is typically much heavier than a lightweight interpreted runtime or a compiled binary, because class loading and JIT warm-up add real time before the first request completes. This is one reason serverless platforms increasingly support compiled languages and lightweight runtimes as first-class options — less to initialize means a shorter cold path.
What keeps functions warm
Serverless platforms keep an execution environment alive for some period after a request completes, betting that another request will arrive before it’s torn down. If the next request comes in during that window, it reuses the warm environment; if too much time passes, the platform reclaims the resources and the next request pays the full cold-start cost again. This is why sporadic, spiky traffic patterns see cold starts far more often than steady, high-throughput ones — a function invoked once every few minutes barely benefits from warm reuse at all, while one handling requests every few seconds stays warm almost continuously.
Concurrency adds another wrinkle: each concurrent request generally needs its own environment, so a sudden burst of simultaneous traffic can trigger many parallel cold starts even if the function has been warm and idle-free for hours — one warm instance doesn’t help ten requests that all arrive at once.
What makes a cold start worse
- Package size. Larger deployment bundles take longer to fetch and load. Unused dependencies bundled into a function add cold-start latency for no runtime benefit.
- Heavy module-scope initialization. Constructing a database client, loading a large configuration file, or initializing an SDK at import time all run on every cold start, before the handler is even reached.
- VPC attachment. Attaching a function to a private network historically added meaningful cold-start overhead on some platforms, because network interface provisioning had its own setup cost — this has improved significantly on major platforms but is still worth checking for the specific provider in use.
- Language and runtime. Interpreted languages with lighter startup tend to cold-start faster than runtimes requiring JIT warm-up or heavy class loading.
Mitigation strategies
- Provisioned or reserved concurrency. Some platforms let you pay to keep a fixed number of environments permanently warm, trading cost for predictable latency on functions where cold starts are unacceptable — a checkout API, for instance, versus a nightly batch job.
- Minimize the dependency footprint. Trim unused imports and avoid pulling in a whole SDK when you only need one client. Smaller bundles load faster on every cold start.
- Lazy-load what you can. Move expensive initialization out of module scope and into the handler where it’s only paid when actually needed, or cache it across warm invocations using a module-level variable that persists between requests on the same warm instance.
- Choose runtimes deliberately. For latency-sensitive functions, a lighter-weight runtime is often worth more than marginal convenience from a heavier one.
- Design around it rather than eliminate it. For non-interactive workloads — queue processors, scheduled jobs, background workers — cold-start latency often doesn’t matter at all, and fighting it is wasted effort better spent elsewhere.
Cold starts vs. traditional infrastructure
| Serverless (cold) | Serverless (warm) | Traditional server | |
|---|---|---|---|
| Latency on first request | Highest | Near-baseline | N/A — already running |
| Idle cost | None | None | Pays for capacity even when idle |
| Scaling model | Automatic, per-request | Automatic, per-request | Manual or autoscaled at the VM/container level |
| Best fit | Bursty, infrequent workloads | Steady, frequent workloads | Latency-critical, constant-traffic workloads |
This is the same tradeoff that shows up in edge functions, which push execution physically closer to users specifically to shrink network latency — but a cold start inside an edge function is still a cold start, just measured against a smaller latency budget where it’s proportionally more noticeable.
The takeaway
A cold start is the one-time cost of provisioning and initializing an execution environment before your code can run, and it exists precisely because serverless platforms don’t keep infrastructure running when nothing is using it — that’s the same tradeoff that makes serverless cheap for idle time. Keep bundles small, defer expensive initialization out of the hot path, and reach for provisioned concurrency only on the specific functions where cold-start latency is actually unacceptable, rather than treating it as a problem to solve everywhere at once.
Tagged
Keep reading
Chisato · · 5 min read What Is Tail Latency? p99 and Slow Requests, Explained
Tail latency is the response time of your slowest requests, measured as p99 or p999. Why averages hide it, what causes it, and how to reduce it.
Chisato · · 4 min read What Is Replication Lag? Causes and Fixes
Replication lag is the delay between a write on the primary database and its arrival on a replica. What causes it, how to measure it, and how to reduce it.
The Lycoris Team · · 4 min read What Is Load Testing?
Load testing measures how a system behaves under expected and peak traffic, revealing bottlenecks and capacity limits before real users do.