Understanding Serverless Cold Starts
Why AWS Lambda and Google Cloud Run functions experience cold start latency, what happens during initialization, and how to minimize it.
Last reviewed: July 27, 2026
A cold start happens when a serverless platform has no warm instance of your function ready and has to provision one from scratch — pulling the code package, initializing the runtime, and running any startup code before it can handle the request. That added latency (often hundreds of milliseconds to a few seconds) only hits requests that arrive when no instance is already running; subsequent requests reuse the warm instance and skip it.
Introduction
Cold starts are the latency penalty paid when a serverless function is invoked for the first time (or after a period of inactivity). While a warm Lambda invocation might complete in 5ms, the cold start can add 200ms–3 seconds depending on the runtime, code size, and initialization work. For latency-sensitive endpoints, this is unacceptable. Understanding what happens during cold start initialization is essential for minimizing its impact.
What Happens During a Cold Start
When AWS Lambda has no warm execution environment available for a function invocation:
flowchart LR
A["Provision microVM (~50-100ms)"] --> B["Load language runtime (~50-200ms)"]
B --> C["Run your init code (variable)"]
C --> D["Handler executes"]
Total cold start = infrastructure init + runtime init + your init code + handler execution.
Infrastructure Phase (AWS-controlled, ~50-100ms)
- Lambda provisions a Firecracker microVM (lightweight VM for security isolation)
- Mounts the function’s code package/container image
- Sets up network interfaces and IAM credentials
Runtime Phase (Language-controlled, ~50-200ms)
- Start the language runtime (Node.js, Python, Java JVM)
- Runtime sizes: Python ~10ms, Node.js ~50ms, Java (JVM) ~500ms–2s
Initialization Phase (Your code, variable)
- Execute code outside the handler (module-level imports, SDK clients, DB connections)
- This is where most cold start optimization happens
import boto3 # ~50ms to import
import psycopg2 # ~30ms to import
from sqlalchemy import create_engine # ~100ms to import
# ❌ Connecting inside handler — runs on EVERY invocation
def lambda_handler(event, context):
conn = psycopg2.connect(DATABASE_URL) # ~200ms every call
result = conn.execute("SELECT ...")
return result
# ✅ Connecting outside handler — runs only on cold start, reused on warm calls
engine = create_engine(DATABASE_URL, pool_size=1, max_overflow=0) # Once per container
def lambda_handler(event, context):
with engine.connect() as conn: # Reuses existing connection
result = conn.execute("SELECT ...")
return result
Cold Start by Runtime
| Runtime | Typical Cold Start |
|---|---|
| Python 3.12 | 150–400ms |
| Node.js 20 | 150–400ms |
| Java 21 (JVM) | 500ms–3s |
| Java 21 (SnapStart) | ~200ms (Lambda feature) |
| Go (compiled) | 50–200ms |
| .NET 8 | 400ms–1.5s |
Minimizing Cold Start Impact
1. Reduce Package Size
# Check Lambda package size
du -sh .aws-sam/build/MyFunction/
# Python: Use Lambda Layers for large dependencies
# Node.js: Bundle with esbuild (tree-shaking)
npx esbuild src/index.ts --bundle --minify --target=node20 --outfile=dist/index.js
2. Lambda SnapStart (Java) / Provisioned Concurrency
# Serverless Framework: Provisioned Concurrency
functions:
api:
handler: handler.main
provisionedConcurrency: 5 # Keep 5 warm instances always running
# Cost: ~$0.015/hour per provisioned instance
3. Lazy Initialization Pattern
# Only initialize when first needed (amortize cold start across multiple handler calls)
_db_connection = None
def get_db():
global _db_connection
if _db_connection is None:
_db_connection = psycopg2.connect(DATABASE_URL)
return _db_connection
def lambda_handler(event, context):
conn = get_db() # Only connects on first call, reuses after
4. Optimize Imports
# ❌ Import at module level even if rarely used
import numpy as np # 200ms
import pandas as pd # 400ms
# ✅ Import lazily only when needed
def process_data(data):
import numpy as np # Only pays import cost when this function runs
return np.array(data).mean()
Key Takeaways
- Cold starts occur when Lambda has no warm execution environment — caused by first invocation, scaling out, or idle timeout (~15 minutes).
- Cold start = infrastructure provisioning + runtime startup + your initialization code — only the last part is in your control.
- Code outside the handler runs only on cold start and is reused on warm invocations — initialize SDK clients and DB connections here.
- Provisioned Concurrency eliminates cold starts by keeping N pre-initialized environments running — costs money even when idle.
- Java cold starts are the most severe (500ms–3s) due to JVM initialization — use Lambda SnapStart or switch to Go/Python for latency-sensitive functions.
Common questions
What actually takes the time during a cold start?
Runtime initialization (starting the language VM/interpreter), loading your deployment package, and executing any module-level or global initialization code in your function — establishing a database connection at import time, for instance, runs on every cold start, not every request.
Does a bigger deployment package make cold starts worse?
Yes — larger packages take longer to fetch and unpack before the runtime can start. Trimming dependencies, using layers for shared code, and avoiding bundling things the function doesn't need at import time are the most direct levers.
Do all serverless platforms have the same cold start behavior?
No — it varies by runtime (compiled languages like Go generally cold-start faster than JVM-based ones), by provider, and by execution model. Cloudflare Workers, for example, uses V8 isolates instead of full containers specifically to make cold starts near-instant.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.