Quick Answer

A cold start is the extra latency a serverless function pays the first time it runs on a fresh execution environment: the platform has to provision a container or microVM, load the runtime and your deployment package, and run your initialization code before it can handle the actual request. AWS's own documentation puts typical cold start duration anywhere from under 100 milliseconds to over a second, and it depends heavily on package size and what your init code does. A warm start skips all of that by reusing an environment left over from a previous invocation.

What a Cold Start Actually Is

This post is about the general serverless / Function-as-a-Service execution model — the AWS Lambda style of "upload a function, the platform runs it on demand and you pay per invocation." That's a distinct question from what serverless means as a category (covered in general terms elsewhere on this site), and it's a distinct question from edge functions specifically used for caching and personalizing content at CDN points of presence (also covered separately) — this post is about the mechanics of the execution model itself, wherever it runs.

Per AWS's own documentation: "When Lambda receives a request to run a function through the Lambda API, the service first prepares an execution environment... this initialization phase, the service downloads your code, starts the environment, and runs any initialization code outside of the main handler. Finally, Lambda runs the handler code." The first two of those steps — downloading code and setting up the environment — are what's meant by a cold start. Once that environment exists, Lambda tries to reuse it for the next invocation instead of tearing it down; a request that lands on a reused environment is a warm start, and it skips straight to running your handler because everything else is already sitting there ready.

You are billed for cold-start time — AWS explicitly bills the initialization phase as part of your invocation. So this isn't just a latency curiosity, it directly costs money on high-traffic, spiky, or infrequently-called functions.

Init, Invoke, Shutdown: The Lifecycle, Straight From AWS's Docs

AWS documents the execution environment lifecycle as three phases, and being precise about what happens in each one clears up most of the confusion around cold starts:

  • Init — in order: start any registered extensions, bootstrap the language runtime itself, then run your function's static initialization code (anything outside the handler — imports, global variables, opening a database connection). This phase is capped at 10 seconds for a normal, on-demand function; if it doesn't finish in time, Lambda retries the Init phase at the moment of the actual invocation, counted against your configured function timeout instead.
  • Invoke — your handler function actually runs, for exactly one request (or, on a warm environment, this repeats for many subsequent requests without ever going through Init again).
  • Shutdown — when Lambda decides to retire an environment, it sends a shutdown event; extensions get a bounded window to clean up (0 ms for a function with no extensions, up to 2000 ms with an external extension registered) before Lambda forcibly kills the process.

AWS logs cold starts explicitly. A failed Init phase produces a line like this in CloudWatch, verbatim from AWS's documentation:

INIT_REPORT Init Duration: 1236.04 ms Phase: init Status: timeout

and a successful invocation that included a cold start reports it as a separate field on the normal report line, distinct from your function's actual execution duration:

REPORT RequestId: 527cb862-... Duration: 3016.78 ms Billed Duration: 3101 ms Memory Size: 128 MB Max Memory Used: 31 MB Init Duration: 84.00 ms

That separation matters: Init Duration is the cold-start tax, Duration is your actual handler running. A function that looks like it "suddenly got slow" on one request but not the next almost always has a non-zero Init Duration on the slow one and none on the fast one.

What Actually Makes Init Slow

Per AWS's own numbers: "Cold starts typically occur in under 1% of invocations. The duration of a cold start varies from under 100 ms to over 1 second." The width of that range is the interesting part — it's driven almost entirely by what happens during static initialization, which AWS's documentation identifies as the largest contributor to pre-execution latency, and names three concrete factors:

  • The size of the function's deployment package — imported libraries, dependencies, and any attached layers all have to be downloaded and loaded before your code can run.
  • The amount of initialization code and work it does — more global setup means more work before the handler can start.
  • How long it takes the libraries and services you're initializing to actually set up their own resources — for example, opening a database connection pool during init.

AWS's guidance follows directly from this: import only what you use (their documented example is requiring aws-sdk/clients/dynamodb instead of the entire aws-sdk package), do connection setup in the global scope so it's reused across warm invocations instead of repeated per-request, and consider splitting a function with many unrelated dependencies into several smaller, more specialized functions that each carry less init weight. None of this is folklore — it's literally AWS's own recommended optimization guidance for exactly this problem.

Provisioned Concurrency, SnapStart, and Pre-Warmed Instances

All three major clouds document a version of the same fix: keep a standing pool of already-initialized environments around instead of creating one on demand. The names and specifics differ:

  • AWS Provisioned Concurrency pre-initializes a fixed number of execution environments ahead of time. AWS's own example: "a function with a provisioned concurrency of 6 has 6 execution environments pre-warmed." Traffic within that number never cold-starts; a burst beyond it still does. AWS also offers SnapStart, which takes a different approach — it snapshots the memory and disk state of an already-initialized environment once (at the time you publish a version) and restores new environments from that snapshot instead of re-running Init from scratch, with a separate 10-second Restore phase budget.
  • Azure Functions Premium plan documents this as always ready instances plus prewarmed instances working together, describing the combination as designed to "effectively eliminate cold starts." Always-ready instances run continuously regardless of load; prewarmed instances sit ready as a buffer that activates as traffic scales up, so a new instance is already warming before it's actually needed.
  • Google Cloud documents minimum instances for the same purpose on Cloud Run and Cloud Functions (2nd gen, which runs on Cloud Run) — a configured number of instances are "kept warm and ready to serve requests" even with zero incoming traffic, specifically to avoid the delay of provisioning from zero.

The common shape across all three: none of these eliminate cold starts as a concept, they eliminate them for a bounded amount of pre-paid, pre-warmed capacity. Traffic that exceeds that reserved capacity still triggers real cold starts on the overflow instances.

Why Language and Package Size Choices Matter Here

Because Init duration is dominated by "bootstrap the runtime" plus "run your static init code," the practical levers are the same regardless of which cloud you're on: a smaller deployment package loads faster, fewer and lighter imports mean less code to execute before your handler can run, and doing expensive setup (like opening a network connection) lazily or only where actually needed avoids paying for it on every cold start regardless of whether that code path is even used.

This is also why cold start behavior is not uniform across runtimes in practice — a function whose init phase amounts to "parse this file and define a function" inherently has less work to do than one that needs to start a virtual machine and JIT-compile classes before it can execute anything, and a function that imports a handful of small utilities has less to load than one pulling in a large data-processing or ML library. Rather than assert specific numbers per language here without a direct, current AWS/Azure/GCP benchmark to cite, the honest takeaway from the official guidance above is directional: package size and init-time work are the two levers documented as mattering, by AWS itself, regardless of which language you're using.

How This Differs From Edge Functions

It's worth being clear about one more distinction. Everything above describes the classic FaaS model: a container or microVM-based execution environment, provisioned per function, with a real (if sometimes small) cold-start cost. Edge functions — the kind used at CDN points of presence for caching and personalizing dynamic content — often use lighter isolation, such as V8 isolates instead of containers, which is a big part of why they can advertise near-instant startup at the edge. That's a genuinely different execution model built for a different job (short, cheap, geographically-distributed logic sitting in front of a cache), and it's covered on its own terms elsewhere on this site rather than re-explained here — the mechanism in this post is specifically the AWS Lambda-style general-purpose FaaS model.

Frequently Asked Questions

How common are cold starts in practice? According to AWS's own documentation, cold starts typically occur in under 1% of invocations for a function receiving steady traffic, because most requests land on an environment that's already warm from a previous invocation. They're proportionally far more common for functions that are invoked rarely, such as development and test functions, or anything with long idle gaps between calls.
Can I eliminate cold starts completely? Not entirely, but you can eliminate them for a bounded amount of traffic. AWS Provisioned Concurrency and SnapStart, Azure's always-ready and prewarmed instances, and GCP's minimum instances all pre-initialize a set number of environments in advance. Traffic within that reserved capacity skips the cold start; a burst beyond it still triggers one on the overflow.
Does a cold start count against my function's timeout? The Init phase has its own separate budget (10 seconds by default for on-demand functions on AWS, more for provisioned concurrency or SnapStart functions), tracked and logged separately from your function's configured execution timeout. AWS's CloudWatch logs report Init Duration as a distinct field precisely so you can tell the two apart.
Is a cold start the same thing as a timeout? No. A cold start is expected latency from setting up a fresh environment, and it happens on plenty of perfectly successful invocations. A timeout is a failure — the function (or, during Init, the extension/runtime bootstrap) didn't finish within its allotted budget at all.
Is the cold start problem unique to AWS Lambda? No, it's inherent to the on-demand FaaS execution model generally. Azure Functions and Google Cloud Functions document the same fundamental tradeoff and offer their own equivalents to AWS's provisioned concurrency — prewarmed/always-ready instances on Azure, minimum instances on GCP — because every platform that spins up fresh, isolated environments on demand faces the same problem.