Cold Starts: What They Cost and How to Cut Them
20 Oct 2026A 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
- Sandbox/container init. Spinning up the isolate or container that will run your function.
- Runtime init. Starting Node, Python, or the language runtime inside that sandbox.
- Module load. Importing your dependencies — this scales with how many packages you
require/importat the top of your handler. - 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).