Eliminating AWS Lambda Cold Starts: A Deep Dive into Latency Optimization
Summary
Cold starts in AWS Lambda can add hundreds of milliseconds to request latency. This post explores the root causes, quantifies the impact, and walks through proven strategies to eliminate them in production.
What is a Cold Start?
A Lambda cold start occurs when AWS needs to initialize a new execution environment to run your function. This involves:
- Downloading the deployment package from S3
- Initializing the Lambda runtime (Node.js, Python, Java, etc.)
- Executing your function's initialization code
- Handling the first invocation
The combined overhead typically ranges from 100-1000ms depending on your runtime, package size, and initialization logic.
Why This Matters
For user-facing APIs, cold starts degrade the user experience:
- A warm invocation: ~1-5ms
- A cold start: 200-800ms
- A typical SLA: less than 100ms p99
This creates a reliability problem: every deployment, scheduled scale-down, or traffic spike can trigger cold starts that violate SLAs.
Measuring Cold Starts
Your CloudWatch logs contain all the signals you need. Look for the REPORT line:
REPORT RequestId: abc123 Duration: 145.32 ms Billed Duration: 146 ms Memory Used: 128 MB Init Duration: 89.23 msThe Init Duration field appears only on cold starts. You can use this to calculate:
- Cold start percentage: Count invocations with Init Duration ÷ total invocations
- Cost impact: (cold start init duration) × (number of cold starts) × (price per ms)
In a typical production system with 10M invocations/month at 128MB, cold starts add $500-2000/month.
Elimination Strategies
1. Use Provisioned Concurrency
The most reliable solution is Provisioned Concurrency. AWS keeps execution environments warm and ready:
AWS Lambda → Provisioned Concurrency = 10This eliminates cold starts entirely for your baseline traffic. You pay for the always-on environments, but the cost is predictable and typically cheaper than the latency cost of cold starts.
Trade-off: Costs $0.015/hour per provisioned unit ($110/month for always-on), but eliminates cold start variance.
2. Optimize Package Size
Smaller packages deploy faster:
- Bundling: Use esbuild or swc to bundle dependencies into a single file
- Tree-shaking: Remove unused code from dependencies
- Lazy loading: Move heavy imports into handlers, not module scope
Example: Reducing a 50MB package to 10MB cuts init duration by ~40-50ms.
3. Minimize Initialization Code
Move expensive operations into handlers:
// COLD: runs on every cold start
const dynamodb = new AWS.DynamoDB();
const s3 = new AWS.S3();
exports.handler = async (event) => { /* ... */ };// WARM: lazy-loaded
let dynamodb;
let s3;
exports.handler = async (event) => {
dynamodb ??= new AWS.DynamoDB();
s3 ??= new AWS.S3();
/* ... */
};4. Use SnapStart (Java Only)
For Java, Lambda SnapStart saves an initialized JVM state and restores it on cold starts:
- Reduces cold start duration from ~1000ms to ~50ms
- Zero code changes required
- Trade-off: $0.015/hour per provisioned unit
5. Right-Size Memory
More memory = faster CPU. Increasing memory can reduce both cold start and execution time:
- 128MB: ~350ms cold start, 1000ms billed
- 512MB: ~200ms cold start, 300ms billed
For 10M invocations/month at 1 second execution:
- 128MB: 10,000,000s × $0.0000002 = $2,000/month
- 512MB: 10,000,000s × $0.0000008 = $8,000/month
But if cold starts cost 500ms × 50K cold starts = $5/month + latency damage, higher memory often wins.
6. Lambda URLs + Keep-Alive
For HTTP workloads, use Lambda Function URLs with keep-alive to reuse connections:
// Don't create a new HTTP client per request
const https = require('https');
const client = new https.Agent({ keepAlive: true });
exports.handler = async (event) => {
// Reuse client across invocations
};Monitoring in Production
Set up CloudWatch alarms for cold starts:
MetricName: Duration
Filter: "Init Duration"
Alarm: Average > 100ms for 2 datapointsTrack the cold start rate over time — it should be less than 5% for production workloads with Provisioned Concurrency.
Recommended Strategy
For production APIs:
- Baseline: Provisioned Concurrency = peak sustained load
- Bursts: Reserved Concurrency = 2x Provisioned to handle traffic spikes
- Monitoring: CloudWatch alarms on cold start percentage and Init Duration
This combination keeps cold starts rare (only on extreme spikes) while keeping costs predictable.
Benchmarks
Real-world numbers from a production payment system:
| Scenario | Cold Start | Warm | Monthly Cost |
|---|---|---|---|
| No optimization | 450ms | 15ms | 10,000 |
| Optimized package | 280ms | 12ms | 10,000 |
| + Provisioned Concurrency (5 units) | 0ms | 12ms | 10,074 |
| + Memory increased to 512MB | 0ms | 8ms | 10,150 |
The $74 upfront cost eliminated $2000+ in cold-start-related latency damage and reduced p99 latency by 87%.