7 ways to reduce AWS Lambda cold starts: From simple to advanced

A Lambda cold start occurs when AWS provisions a new execution environment for your function. This happens when no warm instance is available: the function has not been called recently, concurrent invocations exceed the number of warm instances, or a deployment has just completed. The cold start duration is the time to initialise the runtime, download the deployment package, and execute the function's initialisation code before the handler runs.

Cold starts matter for synchronous workloads (API Gateway-triggered functions where users are waiting for a response) and are largely irrelevant for asynchronous workloads (event-driven processing where some latency variance is acceptable). Before optimising, confirm that cold starts are actually the problem: cold start duration appears in the Init Duration field in Lambda logs and can be filtered in CloudWatch Logs Insights.

These seven techniques are ordered from easiest to implement to most involved.

1. Reduce package size

The deployment package (ZIP or container image) is downloaded to the execution environment on every cold start. A 10 MB package downloads faster than a 50 MB package. On a cold start, this is the first step in the initialisation sequence and it adds directly to cold start duration.

Reduce package size by: - Including only the dependencies the function actually uses. If you include the entire AWS SDK when you only need the S3 client, you are adding 10+ MB for no reason. - Tree-shaking JavaScript/TypeScript bundles. Use webpack, esbuild, or Rollup to build a bundle that includes only the imported code paths rather than entire libraries. - Using Lambda Layers for dependencies shared across multiple functions. A Layer is uploaded once and referenced by multiple functions; it is not included in each function's deployment package size calculation. - Checking for test files, documentation, and source maps included in the package accidentally.

For Node.js functions, npm run build with esbuild typically produces bundles of 1-5 MB even for functions with substantial dependencies, compared to 50-100 MB for a naively bundled node_modules directory.

2. Increase memory allocation

Lambda CPU allocation scales proportionally with memory. A 256 MB function gets half the CPU of a 512 MB function. More CPU means faster initialisation code execution, which means shorter cold starts.

The relationship is not linear at all points, but increasing memory from 128 MB to 512 MB or 1024 MB typically reduces cold start duration by 30-50% for CPU-bound initialisation code (database connection setup, module loading). For memory-limited functions, the reduction can be more significant.

Increasing memory also increases cost per invocation. Use AWS Lambda Power Tuning (an open-source tool deployable as a Step Functions workflow) to find the memory configuration that minimises cost while meeting your latency requirements. Power Tuning runs your function at multiple memory sizes and plots the cost-performance curve.

3. Minimise initialisation code

Lambda initialisation code runs outside the handler function, once per execution environment. It is cached across warm invocations but contributes fully to every cold start. The common pattern of loading configuration, setting up database connections, and initialising SDK clients in the initialisation block is correct for warm performance but maximises cold start cost.

Audit your initialisation code. Each line of initialisation code that takes 10ms on average contributes 10ms to every cold start. Common slow operations:

  • Database connection establishment: move to a connection pool that handles connections lazily, or accept the cost as necessary
  • External API calls during initialisation: avoid if at all possible; defer to the first invocation
  • Large module imports: optimise with tree-shaking (technique 1)
  • Secret retrieval from Parameter Store or Secrets Manager: cache with a short TTL rather than fetching on every cold start

Only put code in the initialisation block that is genuinely needed before the handler can run. Code that is only needed for specific invocation paths should be inside the handler, where it runs only when needed.

4. Choose a faster runtime

Lambda runtimes have different cold start characteristics. Compiled languages (Java, .NET, Go) have fast execution but slower initialisation due to JVM startup or runtime initialisation overhead. Interpreted languages (Python, Node.js) have faster initialisation but slower execution.

For cold-start-sensitive workloads, the ranking for initialisation speed is roughly: Go > Node.js > Python > .NET > Java (with JVM initialisation being the slowest by a significant margin).

For Java specifically, consider GraalVM native image compilation (via Lambda's custom runtime). Native compilation eliminates JVM startup time and can reduce cold start duration from 5-10 seconds to under 500ms. This requires changes to your build process and some libraries may need native image compatibility annotations.

5. Use Lambda SnapStart for Java

Lambda SnapStart is a feature specifically for Java functions using the Corretto 11 or Corretto 17 runtime. It takes a snapshot of the execution environment after initialisation and restores from that snapshot on subsequent cold starts rather than re-initialising from scratch.

SnapStart reduces cold start duration for Java functions from 5-10 seconds to under 500ms for most workloads. Enable it in the function configuration under General configuration > SnapStart.

Caveats: SnapStart is not compatible with all initialisation patterns. Functions that rely on initialisation state being unique per instance (randomness seeded from system time, connections established during init that have instance-specific identifiers) may behave incorrectly when restored from a snapshot. Review the SnapStart compatibility checklist before enabling.

6. Keep functions warm with scheduled invocations

A CloudWatch Events rule that invokes a function every 5 minutes keeps at least one execution environment warm. The function needs to handle ping events gracefully (checking for a ping event and returning without doing real work).

This technique is simple to implement but has limitations: it keeps only one instance warm, which does not help when concurrent cold starts are the issue. It also costs money for the scheduled invocations. For a single-instance API with occasional traffic, warming is effective and cheap. For a function handling burst traffic with many concurrent invocations, warming one instance does not help.

7. Use Provisioned Concurrency for predictable traffic

Provisioned Concurrency pre-initialises a specific number of execution environments and keeps them ready. Invocations routed to provisioned concurrency instances never experience a cold start: the execution environment is fully initialised and the handler runs immediately.

Configure Provisioned Concurrency in the function configuration or via Application Auto Scaling (which adjusts the provisioned count based on a schedule or metric, useful for known traffic patterns like business hours vs overnight).

The cost: Provisioned Concurrency is charged per provisioned GB-second, in addition to regular invocation charges. For a function that needs to maintain 10 always-warm instances, this is a meaningful additional cost. Calculate the provisioned concurrency cost against the value of eliminating cold starts for your specific traffic volume and latency requirement.

Provisioned Concurrency is the correct solution for user-facing API functions with strict P99 latency SLOs where cold starts occurring 1-5% of the time cannot be tolerated. For everything else, techniques 1-4 provide the best return for the least complexity and cost.

Where Critical Cloud comes in

Lambda performance optimisation in production requires visibility into actual cold start frequency, duration, and impact across your function estate, not just on a single function in isolation. As the world's first Powered by Datadog accredited partner, we monitor Lambda cold start duration, provisioned concurrency utilisation, and throttle rates across every function as live operational signals, so cold start regressions from new deployments are caught immediately rather than reported by users. See how Critical Support works.