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.
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.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.