AWS Lambda cold starts remain one of the most frustrating challenges for serverless developers. That initial delay — sometimes hundreds of milliseconds — can make or break user experience in latency-sensitive applications. In this guide, we’ll explore exactly why cold starts happen in 2026, measure them accurately, and apply proven strategies to eliminate or minimize their impact on your production workloads.
What Causes Cold Starts?
When a Lambda function hasn’t been invoked recently, AWS must spin up a new execution environment. This involves:
- Provisioning a micro-VM (Firecracker)
- Downloading and extracting your deployment package
- Initializing the runtime (Node.js, Python, Java, etc.)
- Running your initialization code (imports, DB connections, etc.)
The total cold start duration depends heavily on your runtime choice, package size, and initialization logic. Java and .NET historically suffer the worst (1-5 seconds), while Python and Node.js typically see 200-500ms cold starts.
Measuring Cold Starts Accurately
Before optimizing, you need baseline measurements. Use CloudWatch Logs Insights to identify cold starts:
fields @timestamp, @duration, @initDuration, @memorySize
| filter ispresent(@initDuration)
| stats count() as coldStarts,
avg(@initDuration) as avgInitMs,
max(@initDuration) as maxInitMs,
pct(@initDuration, 99) as p99InitMs
by bin(1h)The @initDuration field only appears on cold start invocations, making it the perfect filter. Run this query over 7 days to understand your cold start frequency and severity.
Strategy 1: Provisioned Concurrency
The most direct solution — keep execution environments warm. AWS charges for provisioned concurrency whether it’s used or not, so apply it strategically.
# Set provisioned concurrency via AWS CLI
aws lambda put-provisioned-concurrency-config \
--function-name my-api-handler \
--qualifier prod \
--provisioned-concurrent-executions 10
# Use Application Auto Scaling for dynamic provisioning
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id function:my-api-handler:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--min-capacity 5 \
--max-capacity 50Combine this with a target tracking scaling policy set to 70% utilization for the best balance between cost and performance.
Strategy 2: SnapStart (Java & .NET)
If you’re running Java or .NET, Lambda SnapStart is a game-changer. It snapshots the initialized execution environment and restores it on cold start, cutting init times from seconds to under 200ms.
# Enable SnapStart in your SAM template
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: com.example.Handler::handleRequest
Runtime: java21
SnapStart:
ApplyOn: PublishedVersions
AutoPublishAlias: liveImportant caveat: SnapStart restores from a snapshot, so any state initialized during the init phase (random seeds, timestamps, unique IDs) will be identical across restored instances. Use CRaC hooks to reinitialize such state:
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
public class Handler implements Resource, RequestHandler<Event, Response> {
private Connection dbConnection;
public Handler() {
Core.getGlobalContext().register(this);
this.dbConnection = createConnection();
}
@Override
public void afterRestore(Context<? extends Resource> context) {
// Re-establish connections after snapshot restore
this.dbConnection = createConnection();
}
}Strategy 3: Minimize Package Size
Smaller packages download and extract faster. This is especially impactful for interpreted runtimes like Python and Node.js.
For Node.js: Tree-Shake and Bundle
// esbuild.config.mjs
import { build } from 'esbuild';
await build({
entryPoints: ['src/handler.ts'],
bundle: true,
minify: true,
platform: 'node',
target: 'node20',
outfile: 'dist/handler.js',
external: ['@aws-sdk/*'], // Already in Lambda runtime
treeShaking: true,
});The external: ['@aws-sdk/*'] line is crucial — the AWS SDK v3 is already available in the Lambda runtime, so bundling it only inflates your package.
For Python: Use Lambda Layers Wisely
# Create a slim layer with only what you need
pip install pandas numpy -t python/lib/python3.12/site-packages/ \
--no-cache-dir --only-binary=:all:
# Remove unnecessary files
find python/ -name "*.pyc" -delete
find python/ -name "__pycache__" -type d -exec rm -rf {} +
find python/ -name "tests" -type d -exec rm -rf {} +
zip -r9 layer.zip python/Strategy 4: Lazy Initialization
Don’t load everything at init time. Defer expensive operations until they’re actually needed:
import boto3
from functools import lru_cache
# BAD: Initializes on every cold start
# s3_client = boto3.client('s3')
# dynamodb = boto3.resource('dynamodb')
# table = dynamodb.Table('my-table')
# GOOD: Initialize on first use
@lru_cache(maxsize=1)
def get_s3_client():
return boto3.client('s3')
@lru_cache(maxsize=1)
def get_table():
return boto3.resource('dynamodb').Table('my-table')
def handler(event, context):
# Only initializes what this specific path needs
if event['path'] == '/upload':
get_s3_client().put_object(...)
elif event['path'] == '/data':
get_table().get_item(...)Strategy 5: ARM64 (Graviton) Runtime
Switching to ARM64 architecture gives you both cost savings (20% cheaper) and faster cold starts. Graviton3 processors initialize runtimes measurably faster:
# In your serverless.yml or SAM template
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Architectures:
- arm64
Runtime: python3.12
MemorySize: 512Increasing MemorySize also proportionally increases CPU allocation, which speeds up initialization. For cold-start-sensitive functions, bumping from 128MB to 512MB often pays for itself.
Strategy 6: Extension-Free Deployments
Each Lambda extension adds to cold start time. Audit your extensions and remove any that aren’t strictly necessary:
# List extensions in your function
aws lambda get-function --function-name my-handler \
--query 'Configuration.Layers[*].Arn'Common culprits include observability agents that add 100-300ms. Consider using CloudWatch embedded metrics or lightweight alternatives like the Lambda Powertools library instead of full APM agents.
Putting It All Together: A Decision Framework
Here’s how to choose your strategy:
- Measure first — Know your p99 cold start latency and frequency
- Low-hanging fruit — Bundle/minify, use ARM64, remove unused extensions
- Java/.NET? — Enable SnapStart immediately
- Still too slow? — Add Provisioned Concurrency with auto-scaling
- Cost-sensitive? — Use lazy initialization + scheduled warming (a simple CloudWatch Events rule that pings your function every 5 minutes)
Conclusion
Cold starts don’t have to be a dealbreaker for serverless. With the right combination of packaging optimization, runtime selection, and provisioned concurrency, you can achieve consistent sub-100ms response times even for latency-critical APIs. Start by measuring your current cold start profile, apply the low-cost optimizations first, and reach for provisioned concurrency only when needed. Your users — and your AWS bill — will thank you.

Leave a Reply