What are Cold Starts and Warm Starts, and Why Do They Matter for Performance
A cold start is the latency you pay when the platform has no ready-to-run execution environment for your code, so before your handler can run it must create one: download the deployment artifact, boot a lightweight virtual machine, start the language runtime, and execute the initialization code that lives outside your handler. A warm start reuses an environment that already did all of that, so only the handler runs. Everything interesting about cold starts is about that one-time setup path — where the milliseconds go, and how to make the platform skip or pre-pay for them.
The idea generalizes (a cold browser cache, an app launched after reboot, an empty CPU pipeline), but those are all the same shape: first use pays for state that later uses inherit for free. The place it actually bites a systems engineer today is serverless — AWS Lambda, Google Cloud Functions, Azure Functions — because there the environment is created and destroyed by the platform on your behalf, and a cold start sits directly in the user's request path. So we trace Lambda concretely.
The Lambda execution lifecycle: INIT vs INVOKE
Lambda splits every environment's life into three phases: Init, Invoke, and Shutdown. A cold start is an Invoke that had to be preceded by a full Init because no environment existed. A warm start is an Invoke that landed on an environment still alive from a previous request.
- Init runs once per environment. Lambda fetches your code, starts a Firecracker microVM, boots the runtime (JVM, CPython, Node, etc.), and runs your module-level / static initialization — imports, SDK client construction, connection-pool creation, framework bootstrap (Spring, etc.).
- Invoke is your handler function executing against an already-initialized environment. This is the only part a warm request pays.
- Shutdown tears the environment down after it has been idle (typically several minutes to ~15 min; the exact idle window is not contractual).
Because Init is amortized across every subsequent Invoke on that environment, the whole cold-start problem reduces to: how long is Init, and how often are you forced to pay it? You are forced to pay it on the first request, on every scale-up to a new concurrent environment, and after each idle reap.
Worked trace: one Java Lambda, cold then warm
The numbers below are representative of a small Spring-based Java 21 function behind API Gateway. Exact values vary by memory setting (more memory = more vCPU = faster init), dependency weight, and region, but the proportions are what matter: the platform-owned steps are cheap and roughly fixed; your own init code is the elephant.
| Phase | What happens | Cold | Warm |
|---|---|---|---|
| Download code | Fetch artifact to the worker (often cached in a shared layer) | ~80 ms | skipped |
| microVM boot | Firecracker starts a fresh microVM (~125 ms, ~150/s per host) | ~125 ms | skipped |
| Runtime init | JVM starts, JIT cold, classes loaded | ~400 ms | skipped |
| Init code (outside handler) | Spring context, dependency injection, JDBC pool, AWS SDK clients, config load | ~2500 ms | skipped |
| Handler | Your actual business logic | ~40 ms | ~40 ms |
| Total | ≈ 3.2 s | ≈ 40 ms |
The lesson: cutting cold starts is almost never about the platform's 200 ms of boot — it is about your ~2.5 s of framework/DI/connection setup. A Python function with a couple of light imports typically inits in ~150-400 ms total (microVM + CPython + import boto3); the same function that eagerly imports pandas or opens a warmed connection pool can jump to 1-3 s. Node is usually ~200-500 ms. Language choice matters, but what you do at import time matters more.
The old VPC tax (now mostly gone). Before September 2019, a function attached to a VPC created an Elastic Network Interface per environment on cold start, adding 10+ seconds. AWS re-architected this with Hyperplane (shared, pre-created ENIs via VPC-to-VPC NAT), dropping it to sub-second. If you read older material warning that "VPC = huge cold starts," that specific problem is fixed — but a cold start still pays first-connection DNS resolution and TCP/TLS handshakes to your database, which is real and lands in your init code above.
Mitigation mechanics: how each fix actually works
Provisioned Concurrency (PC). You declare N; Lambda runs Init on N environments ahead of time and keeps them warm and ready. Requests up to N never see a cold start — Init already happened. You pay a flat per-GB-hour rate for those N environments for as long as PC is enabled, whether or not they serve traffic. Spillover beyond N falls back to on-demand and can still cold-start. You can autoscale PC on a schedule or utilization target via Application Auto Scaling.
SnapStart. When you publish a version, Lambda runs Init once, takes an encrypted memory + disk snapshot of the initialized Firecracker microVM, and caches it. On a cold start it restores from the snapshot instead of re-running Init, using lazy (copy-on-write, on-demand page) loading so restore is fast — typically pulling cold starts from seconds to a few hundred milliseconds. For Java it hooks into CRaC (Coordinated Restore at Checkpoint) so you can register beforeCheckpoint/afterRestore callbacks. SnapStart is free for Java; for Python and .NET there are charges for snapshot caching and restore. Crucially it does not require paying for idle capacity — the snapshot is the cost.
Keep-warm pings. A scheduled event (e.g. EventBridge every 5 min) invokes the function to prevent reaping. It keeps roughly one environment alive, does nothing for concurrent cold starts under load, and burns invocations. It is a stopgap, not a scaling strategy.
Pitfalls
- Init has a 10 s ceiling. Heavy frameworks (a large Spring context, eager bean creation) can blow past the ~10 s Init limit → Lambda aborts and retries Init, so the user sees a much longer stall or an error. Move non-critical work out of module scope and lazy-init on first use.
- Warm environments keep global state — and that cuts both ways. Module-level variables, static fields, and
/tmp(512 MB by default, up to 10 GB) persist across warm invocations on the same environment. Good for reusing DB pools and SDK clients; dangerous if you accidentally cache one user's data in a global and serve it to the next request, or let an in-memory cache grow unbounded. - Cold-start storms on scale-up. A traffic burst that needs 200 concurrent environments pays 200 simultaneous cold starts. PC only covers your reserved N; the spillover all cold-starts at once, spiking p99 exactly when you're busiest.
- SnapStart uniqueness bug. Every restored environment shares one snapshot, so anything captured at snapshot time is duplicated: a seeded
java.util.Random, a generated UUID, a cached auth token that later expires, an open socket. Re-seed randomness and re-establish connections in anafterRestorehook. - Cold starts hide in averages. They're a small fraction of invocations, so mean latency looks fine while a subset of users waits seconds. Watch p99/max and the CloudWatch
Init Durationfield (and X-Ray) — not the average. - Provisioned-concurrency bill shock. PC bills 24/7 even at 3 a.m. Forgetting to schedule it down for low-traffic windows can cost more than the compute you're saving.
When to use each — and when NOT to
The decision is a spend-vs-latency trade against your traffic shape and runtime.
- Plain on-demand (accept cold starts). Choose when traffic is spiky, bursty, or low-volume, the work is async/queue-driven, and p99 in the low seconds is acceptable. You gain true scale-to-zero (pay nothing when idle) and zero operational tuning. It costs you unpredictable tail latency in the request path.
- Provisioned Concurrency. Choose when you have a predictable latency-critical baseline (a synchronous API with a known floor of RPS) and cold starts are unacceptable. You gain guaranteed-warm, flat p99. It costs you money for idle reserved capacity and it doesn't cover burst spillover. Prefer on-demand when traffic is unpredictable or the function is not latency-sensitive.
- SnapStart. Choose for heavy-init runtimes (especially Java/Spring, also Python/.NET) where you want most of PC's cold-start reduction without paying for idle capacity. You gain sub-second cold starts nearly for free (Java). It costs you snapshot-restore correctness work (the uniqueness/stale-connection pitfalls) and it can't beat a truly always-warm PC environment. Prefer PC when you need a hard latency guarantee and can't tolerate any restore variance; prefer SnapStart when cost matters and the runtime's init is the whole problem.
- Leave serverless entirely (ECS/EKS/always-on containers). Choose when traffic is steadily high (so per-invoke Lambda pricing exceeds a reserved fleet) and cold starts are simply unacceptable. You gain zero cold starts and full runtime control. It costs you always-paid idle capacity, autoscaling ops, and the loss of scale-to-zero. Prefer Lambda when load is spiky or low; prefer containers when it's high and flat.
Crisp rule: spiky/tolerant → on-demand; latency-critical predictable baseline → Provisioned Concurrency; heavy Java/Python init on a budget → SnapStart; steady high volume that hates cold starts → move off serverless.
Takeaways
- A cold start is a warm start plus the one-time Init phase (download → microVM boot → runtime start → your init code); optimize the phase that dominates — almost always your init code, not the platform's ~200 ms.
- You pay Init on the first request, on every scale-up to a new environment, and after idle reaping — so a single burst can trigger many simultaneous cold starts.
- Provisioned Concurrency trades money-for-idle to eliminate cold starts; SnapStart trades a restore-time correctness burden for near-free sub-second cold starts on heavy runtimes.
- Measure with
Init Durationand p99/max, never the average — cold starts hide in the tail.
Sources: AWS Lambda Developer Guide (execution environment lifecycle, Init/Invoke/Shutdown phases, Provisioned Concurrency, SnapStart); the Firecracker NSDI 2020 paper and firecracker-microvm.github.io (microVM boot times); AWS Compute Blog on the September 2019 Hyperplane VPC networking change and on Lambda SnapStart internals; OpenJDK CRaC project (Coordinated Restore at Checkpoint). Representative timing figures are order-of-magnitude and vary with memory/vCPU allocation, dependencies, and region. Re-authored and deepened for this guide.
🤖 Don't fully get this? Learn it with Claude
Stuck on What are Cold Starts and Warm Starts, and Why Do They Matter for Performance? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **What are Cold Starts and Warm Starts, and Why Do They Matter for Performance** (System Design) and want to truly understand it. Explain What are Cold Starts and Warm Starts, and Why Do They Matter for Performance from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **What are Cold Starts and Warm Starts, and Why Do They Matter for Performance** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **What are Cold Starts and Warm Starts, and Why Do They Matter for Performance** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **What are Cold Starts and Warm Starts, and Why Do They Matter for Performance** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.