IAnswerable

Cold Starts: What They Cost and How to Cut Them

20 Oct 2026

A cold start is the time your platform spends provisioning a fresh execution environment before your code runs at all — separate from your code’s own execution time. On a cold request you pay both. On a warm request you pay only the second one.

Where the delay actually comes from

  1. Sandbox/container init. Spinning up the isolate or container that will run your function.
  2. Runtime init. Starting Node, Python, or the language runtime inside that sandbox.
  3. Module load. Importing your dependencies — this scales with how many packages you require/import at the top of your handler.
  4. Connection setup. Opening a database connection, loading secrets, initializing SDKs — anything in your global scope that runs before the handler executes.

Steps 1-2 are mostly the platform’s problem. Steps 3-4 are yours, and they’re usually where the real time goes on anything beyond a trivial function.

Measure it before you guess

Don’t optimize blind. Log a timestamp at the very top of your module (outside the handler) and again inside the handler on first invocation:

const moduleLoadStart = Date.now();
console.log("module loading");

// ... imports, DB client setup ...

export const handler = async (event) => {
  console.log("handler start, since module load:", Date.now() - moduleLoadStart, "ms");
  // ...
};

If that gap is large, your dependency graph or connection setup is the bottleneck, not the platform’s cold-start overhead.

Fixes ranked by effort

1. Trim your dependency graph. Every unused import adds parse and init time. Swap a heavy SDK for a lighter HTTP call if you only use one method from it. This is the highest-leverage, lowest-risk fix.

2. Lazy-load what you don’t always need. Move rarely-used imports inside the code path that actually needs them, not the top of the file:

export const handler = async (event) => {
  if (event.type === "rare-case") {
    const { heavyLib } = await import("./heavy-lib.js");
    return heavyLib.process(event);
  }
  // common path never pays heavyLib's load cost
};

3. Reuse connections across invocations. Initialize your DB client outside the handler, not inside it, so a warm invocation reuses the existing connection instead of reconnecting:

let dbClient;
export const handler = async (event) => {
  dbClient ??= await connectToDb();
  // ...
};

This only helps on warm starts, but warm starts are the majority of traffic on anything with steady load.

4. Provisioned/minimum instances. Most platforms (AWS Lambda provisioned concurrency, Cloudflare’s always-warm options, Vercel’s equivalent) let you pay to keep N instances warm at all times. This is the expensive fix — it costs money even during zero traffic — but it’s the only one that eliminates cold starts entirely for your baseline load.

5. Switch runtime. Interpreted-with-JIT runtimes (Node, Python) generally cold-start faster than JVM-based ones. If you’re on the JVM and cold starts are unacceptable, moving to a faster-starting runtime is a bigger lift but a real fix — not something to reach for first.

The gotcha

Cold starts get dramatically worse when your function is inside a VPC and needs to attach an ENI (AWS-specific, but similar penalties exist elsewhere for private networking). If your cold starts are in the multi-second range instead of tens to low-hundreds of milliseconds, check whether you’re paying a network-attachment penalty before you start trimming imports — that’s a different problem with a different fix (VPC endpoint reuse, or moving off VPC-required resources).