Learn how we reduced Lambda cold starts from 3.2s to 950ms using provisioned concurrency, SnapStart, and ARM64. Real-world optimization techniques with code...
Why Cold Starts Dominate Perceived Latency
Serverless functions pay for idle time with nothing—and pay for the first request after idle with everything. A cold start is the full path from request arrival to a ready runtime: provisioning a worker, loading the language runtime, initializing your handler and its dependencies, then executing the first business logic. Warm invocations skip most of that path. When the cold path sits in the multi-second range, it becomes the latency story users actually feel, even if warm paths are fast.
On our stack, cold starts were measuring about 3.2 seconds before optimization. That was not a single bug; it was the sum of architecture choices—heavy dependency graphs, a larger package, and no concurrency reservation for the paths that matter. The target was not zero cold starts. It was making the cold path short enough (down to about 950ms) that it stopped dominating end-to-end response time for the functions we care about most.
Provisioned Concurrency: Buy Warmth Where It Matters
Provisioned concurrency keeps a fixed number of execution environments initialized and ready. Those environments have already paid the cold-start cost; incoming traffic hits a pre-warmed pool instead of spinning up from scratch. The tradeoff is straightforward: you pay for reserved capacity whether traffic arrives or not. That cost is only justified on latency-sensitive routes—API entry points, auth, checkout-style paths—not on every background job or low-traffic admin endpoint.
Use it surgically. Measure which functions see cold starts under real traffic, set provisioned concurrency to cover expected concurrent demand plus a small buffer, and leave burst capacity for rare spikes. Pair it with alarms on concurrent executions and throttles so you notice when demand outgrows the reservation. Provisioned concurrency alone will not fix a bloated package; it only hides the cost for the reserved pool.
SnapStart and ARM64: Shrink What Initialization Must Do
SnapStart freezes a fully initialized execution environment and restores later invocations from that snapshot. Restoration is cheaper than re-running full class loading, static init, and dependency wiring from scratch. It works best when you push as much setup as possible into the init phase and keep restore-time work minimal—no random seeds that must change per instance, no open connections that become invalid after restore, no side effects that assume a fresh process every time.
Moving to ARM64 (Graviton-class runtimes) is a complementary lever. Smaller instruction cost per unit of work and better price-performance often mean faster init and lower cost for the same memory setting. Combine ARM64 with a tighter deployment package: fewer transitive dependencies, lazy loading for code paths not needed at cold start, and configuration that does not force network calls during init. SnapStart reduces how much init you re-run; ARM64 and package discipline reduce how expensive that init was in the first place.
- Push framework and client construction into init; keep the handler thin.
- Avoid open sockets, thread pools, and unique-per-instance secrets during snapshot capture.
- Prefer ARM64 when your language runtime and libraries support it cleanly.
- Measure cold and warm separately so you know which lever moved which number.
How We Stack the Three Levers
The path from 3.2s cold starts to about 950ms was not one feature flip. Package and init hygiene first: smaller artifacts and less work before the first useful line of handler code. SnapStart next, so restore replaces full re-init on supported runtimes. Provisioned concurrency last and only on the critical subset of functions, so the remaining cold path is rare and short when it still happens.
Validate with the metrics that match user experience: cold-start duration, p95/p99 of the full request, error rates after restore, and cost of reserved concurrency. Revisit after every meaningful dependency or framework change—cold-start debt returns quietly when the package grows again. The durable practice is treating cold start as a first-class latency budget, not an unavoidable tax of serverless.