Skip to main content
Cloud & AI Hub
Browse
Glossary AI Directory Playgrounds Models Prompts Explainers Strategy Matrix Benchmark Decoder
beginner 7 mins Read

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

RuntimeTypical Cold Start
Python 3.12150–400ms
Node.js 20150–400ms
Java 21 (JVM)500ms–3s
Java 21 (SnapStart)~200ms (Lambda feature)
Go (compiled)50–200ms
.NET 8400ms–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.