Back to blogs

Eliminating AWS Lambda Cold Starts: A Deep Dive into Latency Optimization

3 min read
AWSLambdaPerformanceLatencyCost 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:

  1. Downloading the deployment package from S3
  2. Initializing the Lambda runtime (Node.js, Python, Java, etc.)
  3. Executing your function's initialization code
  4. 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 ms

The 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 = 10

This 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 datapoints

Track the cold start rate over time — it should be less than 5% for production workloads with Provisioned Concurrency.

Recommended Strategy

For production APIs:

  1. Baseline: Provisioned Concurrency = peak sustained load
  2. Bursts: Reserved Concurrency = 2x Provisioned to handle traffic spikes
  3. 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:

ScenarioCold StartWarmMonthly Cost
No optimization450ms15ms10,000
Optimized package280ms12ms10,000
+ Provisioned Concurrency (5 units)0ms12ms10,074
+ Memory increased to 512MB0ms8ms10,150

The $74 upfront cost eliminated $2000+ in cold-start-related latency damage and reduced p99 latency by 87%.