---
topic: web-performance
author: Crashtech Editorial
published: Nov 14, 2026 · read: 4 min
---

Serverless Cold Starts Explained: MicroVMs, Snapshots and What You Can Control

Why a function's first call is slow, how microVMs and snapshot restore cut it, and the code and config choices that shrink cold starts on your side.

– –

Serverless promised “just run some code”, and for the first call after a quiet period it keeps that promise slowly. Understanding where the time goes tells you which part you can fix and which part belongs to the platform.

Why serverless exists

The original observation was that customers kept whole fleets of virtual servers alive to do occasional small jobs: write a record, process an uploaded file, react to an event. Those machines sat idle and still cost money. The product answer was a service that allocates compute on demand, runs a function, bills for the time used and hides servers entirely.

At launch that meant a handler function per event, automatic scaling per request, stateless execution (anything that must persist goes to storage), and billing that started with 100 ms-class rounding and moved to 1 ms granularity. Memory is the only size knob, with CPU scaling proportionally.

What happens on a cold start

  1. Provision an environment

    The platform finds capacity and sets up an isolated sandbox for your function.

  2. Download the code

    Your package or container image is fetched. Bigger artefacts take longer.

  3. Start the runtime

    The language runtime boots. A JVM with class loading takes much longer than a small Go or Node process.

  4. Run your initialisation

    Code outside the handler runs: imports, SDK clients, configuration, database connections, model loads.

  5. Invoke the handler

    Only now does your request start executing.

A warm invocation reuses an existing environment, skipping steps one to four. That explains why latency is bimodal: most calls are fast and the first after idle (or during a scale-out burst) is slow.

Platform work, bottom up

Lightweight microVMs

An early design dedicated a full virtual machine instance per tenant. It was secure and wasteful. Firecracker, a minimal virtual machine monitor built on the Linux kernel’s virtualization support, creates microVMs that boot in a fraction of a second with a tiny memory footprint, yet keep hardware-level isolation between customers. It lets many isolated sandboxes share a host efficiently, which cuts both start time and cost.

Snapshot restore

For runtimes with heavy initialisation, SnapStart moves that work to publish time: initialise the function once, snapshot the memory and disk state, and restore new environments from the snapshot. Reported startup reductions run up to 90 percent for Java.

Snapshots change your code's assumptions

A restored environment resumes from a point in time. Things established during init, such as random seeds, unique ids, network connections and cached credentials, may be stale or duplicated across restored copies. Re-establish connections and regenerate uniqueness after restore, using the runtime hooks provided.

Layers and containers

Layers let functions share libraries and binaries without repackaging them in each deployment, so packages stay smaller. Container image support (up to several gigabytes) fits large dependencies such as machine learning inference, with the trade-off that large images take longer to fetch cold.

Advertisement

What you control

LeverEffect on cold startNotes
Smaller packageFaster downloadTree-shake, drop unused dependencies, prune dev files
Fast-starting runtimeFaster runtime bootGo, Rust, Node and Python usually beat a plain JVM unless snapshotted
Lazy initialisationMoves work off the critical pathInitialise a client on first use, not at import
Initialise outside the handlerWarm invocations reuse itConnection clients, config, compiled regexes
More memoryMore CPU, faster initOften cheaper overall despite the higher rate per ms
Snapshot restoreSkips initialisationCheck restore hooks and uniqueness
Provisioned concurrencyEliminates cold starts for reserved capacityPays for idle readiness. Use for predictable, latency-critical paths
Warmers (scheduled pings)Keeps a few environments aliveFragile. Does not help during scale-out bursts
import boto3

_client = None                     # created once per environment, reused when warm

def get_client():
    global _client
    if _client is None:
        _client = boto3.client("dynamodb")
    return _client

def handler(event, context):
    return get_client().get_item(...)

When serverless is the wrong shape

A steady, high-throughput service with strict single-digit-millisecond latency is often cheaper and simpler as a long-running container. Serverless shines for spiky, event-driven and low-duty-cycle workloads, and wherever the operations overhead of owning servers is the larger cost.

Do

Measure cold start frequency and duration with real traffic, set memory by testing, keep packages lean, and use provisioned concurrency only for the paths that need it.

Don't

Write a keep-warm cron and call the problem solved. Concurrent bursts create new environments regardless, and the warmer itself is code to maintain.

Takeaway

A cold start is five steps of setup you only pay for on a fresh environment. Platforms shrink the first two with microVMs and the middle ones with snapshots, and you shrink the rest with small packages, lazy and well-placed initialisation, adequate memory and, where justified, reserved warm capacity. Measure first, then spend.

Advertisement

Frequently asked questions

What is a cold start?

The extra latency when a serverless platform has no ready execution environment for your function and must create one: allocate an isolated sandbox, fetch your code, start the runtime and run your initialization code. Later invocations on the same warm environment skip those steps and are much faster.

How does SnapStart reduce cold start time?

It initialises your function once at publish time, takes a snapshot of the memory and disk state of the initialised environment, and restores new environments from that snapshot instead of running initialization again. For runtimes with long startup such as Java, reported reductions reach up to ninety percent.

What is provisioned concurrency?

A setting that keeps a chosen number of execution environments initialised and ready, so requests hit warm instances. It removes cold starts for that capacity at a steady cost, so it suits latency-critical, predictable traffic, not rarely used functions.

What can I do in my own code to reduce cold starts?

Keep the deployment package small, import only what you use, do heavy initialisation lazily or outside the critical path, choose a fast-starting runtime when latency matters, reuse connections created outside the handler, and avoid loading large models or files at startup unless a snapshot or provisioned capacity covers it.

Sources & further reading

/* Comments */