How does AWS Lambda cold start work and how do you optimize it?
Quick Answer
A cold start occurs when Lambda must provision a new execution environment: downloading code, starting the runtime, initializing dependencies, and running your init code. This adds 100ms-10s of latency depending on runtime, package size, and VPC configuration. Optimize with provisioned concurrency, smaller packages, lazy initialization, and SnapStart (Java).
Detailed Answer
AWS Lambda cold starts are one of the most misunderstood performance characteristics in serverless computing. Think of it like starting a car engine versus driving an already-running car. A warm invocation (engine running) just executes your handler function. A cold start (cold engine) requires Lambda to perform several heavy steps before your code can run.
The cold start sequence has distinct phases. Phase 1 is environment provisioning: Lambda allocates a microVM using Firecracker (AWS's lightweight virtualization technology), provisions CPU and memory based on your configuration, and sets up the network stack. This takes approximately 100-200ms and is outside your control. Phase 2 is code download: Lambda downloads your deployment package from S3 (or pulls your container image from ECR). A 50MB zip takes meaningfully longer than a 5MB zip. Phase 3 is runtime initialization: the language runtime starts — the Python interpreter, the Node.js V8 engine, or the JVM. The JVM is notoriously slow here, often adding 2-5 seconds. Phase 4 is your initialization code: anything outside your handler function runs — importing libraries, establishing database connections, loading ML models, reading configuration from Parameter Store. This is where most of the controllable latency lives.
After the cold start, the execution environment stays warm for 5-15 minutes (AWS does not guarantee a specific duration). Subsequent invocations reuse the warm environment and skip directly to executing the handler. This is why global variables persist between invocations — a database connection pool initialized in the cold start is reused across warm invocations.
VPC-attached Lambda functions historically had severe cold starts (10+ seconds) because Lambda had to create an Elastic Network Interface. Since 2019, AWS uses Hyperplane (the internal network function virtualization platform), which pre-creates shared ENIs. This reduced VPC cold starts to roughly the same as non-VPC cold starts, but there is still additional latency for initial security group and subnet configuration.
Optimization strategies, ordered by impact
1. Provisioned Concurrency: AWS pre-initializes a specified number of execution environments that are always warm. You pay for them whether they are used or not. This completely eliminates cold starts for that capacity. It is essential for latency-sensitive workloads like payment processing APIs where a 2-second cold start is unacceptable.
2. SnapStart (Java only): AWS takes a Firecracker microVM snapshot after your initialization code runs, then restores from this snapshot instead of re-initializing. This reduces Java cold starts from 5+ seconds to under 200ms. It has limitations — you must handle uniqueness restoration since random seeds and in-memory state are shared across restored snapshots.
3. Minimize package size: Remove unnecessary dependencies. Use Lambda Layers for shared libraries. For Node.js, use esbuild or webpack to tree-shake. For Python, strip unused libraries and avoid including test files. A 5MB package cold-starts measurably faster than a 50MB package.
4. Lazy initialization: Do not load everything at init time. If only 10% of invocations need the ML model, load it on first use rather than on every cold start. Cache it in a global variable for subsequent warm invocations.
5. Choose efficient runtimes: Python and Node.js cold-start in 100-300ms. Java cold-starts in 2-5 seconds (without SnapStart). Go and Rust (via custom runtime) cold-start in under 50ms because they compile to native binaries with no runtime overhead.
6. Increase memory allocation: Lambda allocates CPU proportionally to memory. A 128MB function gets a fraction of a vCPU. A 1769MB function gets a full vCPU. Higher CPU speeds up initialization code that is CPU-bound (parsing, compilation). Sometimes doubling memory halves cold start time and the cost stays the same because the function runs half as long.
Production monitoring: Use CloudWatch Metrics with the Init Duration metric to track cold starts. Set alarms for cold start percentage exceeding your SLA threshold. Use X-Ray to visualize the initialization breakdown.