The Runtime Theory
Cloud & Infrastructure

Cold Starts Are Not a Myth

What a serverless cold start actually measures — microVM boot, runtime init, handler wiring — and how Lambda SnapStart and Firecracker change the numbers without changing the physics.

The Runtime Theory Team4 min read#serverless#lambda#firecracker#snapstart#cold-starts
On this page

Cold starts are not a myth, a feeling, or a benchmark artifact. They are a sequence of verifiable machine operations with measurable durations, and every serverless platform performs them on your function's behalf. The debate exists because platforms optimize the median while your users experience the cold path — but the underlying mechanics are not in dispute.

What the cold start interval actually contains

A cold start is the elapsed time between "the platform decides to run your function" and "your handler's first line executes." On AWS Lambda, that interval is composed of four named stages:

text
1. sandbox creation — Firecracker microVM boot + network setup        ~100–200 ms
2. runtime bootstrap — language runtime process, stdlib imports       ~50–300 ms
3. handler wiring — loading your code, resolving the handler symbol,   ~10–100 ms
                     building the invoke request context
4. your code — module-level initialization before the handler runs     variable

Each stage is separately instrumentable and separately optimizable. That is why the platform-provided "cold start" latency table is a lie of aggregation: a 128 MB Node.js function with no dependencies cold starts in a few hundred milliseconds, while a 1 GB JVM function that initializes an AWS SDK v2 HTTP client, a connection pool, and a configuration loader cold starts in 2–4 seconds. The platform fixed the first three stages; your code dominates the fourth.

Firecracker: the microVM under the cold start

Lambda runs on Firecracker, a virtual machine monitor written in Rust that boots a minimal microVM in about 125 ms — roughly 1% the boot time of a full KVM guest. The design trades almost everything a classic hypervisor gives you for startup speed:

text
Firecracker microVM boot budget:
  kernel load and decompression   ~10 ms
  vmlinux boot (no BIOS, no initrd userspace)  ~60 ms
  control plane + API setup       ~30 ms
  network namespace wiring        ~25 ms

A microVM is still a VM: separate kernel, separate address space, hardware-mediated isolation from other tenants. Cold starts are not slow because of the hypervisor — they are slow because a whole kernel has to boot, a whole runtime has to initialize, and the platform has to connect your function to its frontend. The container-init and code-loading work is on top of the microVM, not instead of it.

SnapStart: skipping init by checkpointing

SnapStart attacks the problem directly: instead of booting a microVM and initializing your runtime on every cold start, the platform booted one once, ran init up to a checkpoint you define, and stored a snapshot. A cold start then becomes a restore:

json
{
  "SnapStart": { "ApplyOn": "PublishedVersions" },
  "Runtime": "java21",
  "Handler": "com.example.App::handleRequest"
}
bash
aws lambda update-function-configuration \
  --function-name my-java-function \
  --snapstart ApplyOn=PublishedVersions

Measured behavior: a Java 21 function with a heavy SDK init that cold-started in 1.8–2.5 s restores from a snapshot in 150–400 ms — the init time disappears. But the snapshot is a frozen machine state, and the caveats are where teams get burned:

  • Network connections are not restorable. Sockets open at checkpoint time are dead after restore; the AWS SDK's HTTP connection pool reconnects lazily, which can add seconds of retry backoff to the first request.
  • Entropy and randomness must be regenerated. Crypto key material, session tokens, and random UUIDs created before the checkpoint are shared by every restored instance. Lambda marks the process and re-seeds /dev/urandom, but any application-level key cached before the checkpoint must be rotated by you.
  • The checkpoint is taken on a cold run. Your init code runs, gets snapshotted, and every subsequent restore inherits whatever state that first run left behind — including bugs that only manifest on cold paths.

Why it still matters

The cold start tax compounds in the way that matters most: concurrency bursts. When 200 requests arrive at an idle function, the platform provisions ~200 microVMs in parallel, and every one of those requests pays the full cold path — the aggregate p99 is the cold start, not the warm median. Warm instances are a cache with a time-to-live; a cache miss during a spike is not an anomaly, it is the expected failure mode.

bash
# measure what users see, not what the console shows
for i in $(seq 1 30); do
  curl -s -o /dev/null -w "%{time_total}\n" \
    -H "x-api-key: $KEY" \
    https://api.example.com/prod/handler
done | sort -n | tail -5

The last 5 lines of that loop are your real cold-start percentile. If they are 20x your median, you do not have a latency problem — you have a cold-fleet problem.

The mitigation playbook, in order of effectiveness

  1. Provisioned concurrency for hot paths — you pay a per-instance hourly rate, but the platform keeps the sandbox warm and the p95 flat. This is the only mechanism that eliminates cold starts rather than reducing them.
  2. SnapStart for JVM and .NET runtimes with heavy init. Restore beats re-init when the initialization is expensive and deterministic.
  3. Lazy initialization in the handler. Move SDK clients, config parsing, and connection pools out of module scope and into a memoized first-call pattern — warm invocations stay warm and cold ones only pay for what they use.
  4. Smaller, purpose-built functions. A function that imports three SDK clients pays for all three on every cold start. Split handlers that don't share init.
  5. Skip serverless for the hot path entirely when the SLO can't tolerate the distribution. A provisioned container or instance has a boot cost, but it boots once, not per request.

The runtime view

  • Cold start = microVM boot + runtime init + handler wiring + your module init.
  • Warm instances are a cache; cache misses during bursts are inevitable, not anomalous.
  • SnapStart trades init time for restore time and hands you the responsibility for state that does not survive a checkpoint.
  • Measure the cold percentile, because that is the number your users feel.