A cold start is a latency penalty paid when a serverless platform must create and initialize a new environment before running your code. It is not a bug but a bill: scale-to-zero means idle environments are reclaimed so idle costs nothing — and the first request after the reclaiming pays the setup. Understanding the phases, the real numbers, and the mitigation ladder turns cold starts from a vague fear into a priced engineering decision.
Key takeaways
| Question | Answer |
|---|---|
| The cause | Scale-to-zero: no idle cost ⇒ someone initializes on demand |
| The phases | Allocate → download code → boot runtime → run init code → handle |
| The numbers | ~100 ms–1 s+ typical; under 1% of steady traffic, most of sparse traffic |
| The forgotten trigger | Concurrency scale-out — warmers can’t fix it |
| The ladder | Free code fixes first; paid always-warm capacity last |
The five phases, with the clock running
COLD START typical cost
1 Allocate a sandbox (micro-VM / container) ~50–100 ms
2 Download your deployment package 50–500 ms ← scales with bundle size
3 Boot the language runtime 50–1,000 ms ← interpreter fast, VM slow
4 Run YOUR init code (imports, SDK clients, DB conns) 0 ms–seconds ← the biggest lever you own
5 Invoke the handler the actual work
WARM START = step 5 only (+ ~1–10 ms thaw). The environment was frozen,
not destroyed — connections and caches outside the handler survive,
which is why init discipline pays rent on every later request.
The contrast worth measuring yourself — a function on an always-running backend, where the race never starts:
// JavaScript / Node.js — Back4app JS SDK
// Cloud Code runs on an always-on backend — no scale-from-zero cold start
const t0 = Date.now();
const avg = await Parse.Cloud.run('averageStars', { movie: 'Arrival' });
console.log(`Round trip: ${Date.now() - t0} ms`); // consistent p50 ≈ p99
// The FaaS mitigations (warmers, provisioned capacity, bundle diets)
// don't apply here: the process serving this call was already running. // Flutter / Dart — Back4app Flutter SDK
// Cloud Code runs on an always-on backend — no scale-from-zero cold start
final t0 = DateTime.now();
final response = await ParseCloudFunction('averageStars')
.execute(parameters: {'movie': 'Arrival'});
print('Round trip: ${DateTime.now().difference(t0).inMilliseconds} ms');
// Consistent p50 ≈ p99: the process serving this call was already running. // iOS / Swift — Back4app Swift SDK
// Cloud Code runs on an always-on backend — no scale-from-zero cold start
let t0 = Date()
let avg: Double = try await Cloud.run(name: "averageStars",
parameters: ["movie": "Arrival"])
print("Round trip: \(Int(Date().timeIntervalSince(t0) * 1000)) ms")
// Consistent p50 ≈ p99: the process serving this call was already running. // Android / Kotlin — Back4app Android SDK
// Cloud Code runs on an always-on backend — no scale-from-zero cold start
val t0 = System.currentTimeMillis()
val avg = ParseCloud.callFunction<Double>(
"averageStars", mapOf("movie" to "Arrival"))
println("Round trip: ${System.currentTimeMillis() - t0} ms")
// Consistent p50 ≈ p99: the process serving this call was already running. Cold vs. warm starts
| Cold start | Warm start | |
|---|---|---|
| Environment | Created and initialized now | Reused, thawed |
| Added latency | ~100 ms to seconds | Single-digit ms |
| When | First call, post-deploy, post-idle, scale-out | Steady traffic within the warm pool |
| Init code | Runs | Skipped — its results persist |
| Billing note | On major platforms, the init phase is now billed like execution | Handler time only |
How long, and how often — honestly
Duration depends mostly on runtime and bundle: interpreted runtimes (JavaScript, Python) typically 200–400 ms; compiled (Go, Rust) under ~100–300 ms; VM-based (JVM, .NET) 500 ms to multiple seconds, with snapshot-restore features cutting the JVM figure dramatically; p99s run 2–3× the median, and bloated dependency trees have been measured multiplying startup several-fold regardless of language. Frequency is where most explainers tell only half the story. Steady production traffic sees cold starts on under one percent of invocations — reassuring, and real. But the math inverts for sparse traffic: Fowler’s arithmetic — a function invoked once an hour cold-starts essentially every time, and dev environments see cold rates of 30–90%. And the trigger everyone forgets: concurrency scale-out — when ten requests arrive and three environments are warm, seven cold-start simultaneously, in the middle of your traffic spike, which is precisely when it hurts.
The mitigation ladder, priced
In cost order, cheapest first. Free, code-level: shrink the deployment bundle and prune dependencies — the single biggest lever most teams haven’t pulled; import only the SDK submodules you use; lazy-load heavy modules used by rare code paths; open database connections in init scope so warm starts reuse them. Cheap, configuration: raise memory (CPU scales with it, materially for VM runtimes); enable snapshot-restore where the platform offers it. Fragile, folk-remedy: keep-warm pings — a scheduled request every few minutes keeps one environment warm and does nothing for scale-out; honest status: legacy hack. Paid, definitive: pre-provisioned capacity (“provisioned concurrency,” “minimum instances”) — N environments always initialized, cold starts eliminated up to N, at an always-on price that partially cancels pay-per-use. The irony deserves its sentence: the fix for serverless’s signature problem is buying back the server you were promised you didn’t have.
Isolates: how edge runtimes sidestep it
Edge platforms report near-zero cold starts by changing the architecture rather than warming it: instead of booting a container or micro-VM per tenant, they run thousands of V8 isolates — browser-tab-style sandboxes — inside one long-running process. Creating an isolate context costs single-digit milliseconds and megabytes, not hundreds of milliseconds and a runtime boot. The trade is the environment: a web-standard API subset, tight CPU caps, no filesystem — a different tool, not a free upgrade, covered honestly in the edge entry.
Common use cases where cold starts matter
- Interactive APIs — a human is watching the spinner; p99 includes the cold tail.
- Payments and checkout — latency-sensitive and conversion-priced.
- Login and session issuance — the first impression path, often after idle periods.
- Sparse-traffic endpoints — admin tools and internal APIs where nearly every call is cold.
- Where they don’t: queued jobs, webhooks, scheduled work — async absorbs the tail invisibly.
Should you optimize cold starts? A decision matrix
| Situation | Do |
|---|---|
| Async/queued/scheduled workloads | Nothing — cold starts are invisible here |
| Steady high traffic, interactive | Code-level fixes; measure before paying |
| Sparse traffic, user-facing | Minimum instances on that path — or an always-on backend |
| VM runtime (JVM/.NET), latency-sensitive | Snapshot-restore + memory first |
| Spiky traffic with strict p99 | Provisioned capacity sized to the spike |
| Gateway-style logic, global users | Isolate-based edge runtimes |
| Most app backends on a BaaS | Already solved — no scale-from-zero exists |
Limitations and trade-offs
- Mitigations trade money for latency. Pre-warmed capacity is a standing bill; decide per-endpoint, not platform-wide.
- Warmers lie to dashboards. A pinged function looks healthy while every real traffic spike still cold-starts the scale-out.
- Init thrift has limits. Aggressive lazy-loading moves latency from cold starts into first-use paths — measure where you moved it.
- Benchmarks age fast. Runtime rankings and platform numbers shift yearly; stale figures (and long-fixed networking penalties) still circulate — date your data.
- The metric that matters is yours. Percentage-cold statistics describe someone else’s traffic; only your p99 under production-like load justifies spending.
Cold starts and Back4app
Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. The cold-start story here is architectural absence: Cloud Code runs inside an always-running backend, so there is no scale-from-zero moment — no init race, no warm pool to manage, no provisioned-capacity line item — and the code tabs’ measurement shows what that buys: p50 and p99 within shouting distance of each other, on the first request of the day and the millionth. The trade is the one named in BaaS vs. serverless: you give up per-invocation billing granularity for consistent latency and an attached database — which, for user-facing app backends, is usually the side of the trade the users can feel.
Frequently asked questions
What is a cold start?
The extra latency when a serverless platform must build a fresh execution environment before running your function: allocate an instance, download your code, boot the runtime, run your initialization code — and only then handle the request. A warm start skips straight to the last step.
How long does a cold start take?
Typically a hundred milliseconds to over a second. Interpreted runtimes (JavaScript, Python) land around 200–400 ms; compiled ones (Go, Rust) can dip below 100 ms; VM-based runtimes (JVM, .NET) run 500 ms to several seconds, with framework-heavy setups worst. Tail latencies run two to three times the median.
What causes cold starts?
Scale-to-zero economics: idle environments are reclaimed so you pay nothing at rest, which means the first request after idleness — or after a deploy, or above the current warm pool during a traffic spike — must wait for a new environment to initialize. The billing model and the latency are the same coin.
What is the difference between a cold start and a warm start?
A warm start reuses a frozen-but-initialized environment: the platform thaws it and calls your handler, adding single-digit milliseconds. Everything outside the handler — connections, caches, loaded modules — survives, which is why initialization discipline pays off across every warm invocation after.
How often do cold starts happen?
Both answers are true: under about one percent of invocations for steady production traffic — and the overwhelming majority for sparse traffic, since a function called hourly cold-starts nearly every time. Bursts add more: each concurrent request above the warm pool triggers its own cold start.
How do you reduce cold starts?
Climb the ladder from free to paid: shrink the deployment bundle and prune dependencies (the biggest free win), lazy-load rarely used imports, pick a faster runtime, raise memory (CPU scales with it), use snapshot-restore where offered — and only then pay for pre-warmed capacity.
What is provisioned concurrency?
The paid fix, also sold as minimum instances: the platform keeps N environments initialized ahead of demand, eliminating cold starts for traffic up to N. The irony is priced in — you reintroduce always-on cost to a pay-per-use model, which is why it belongs on latency-critical paths only.
Do keep-warm pings work?
Partially, and fragilely: a scheduled ping keeps one environment warm, but does nothing when concurrency scales out — the tenth simultaneous request cold-starts no matter how warm the first environment is. Warmers are the legacy hack; minimum instances are the supported answer.
Which runtimes have the fastest cold starts?
Compiled-to-native first (Rust, Go), interpreted next (JavaScript, Python), VM-with-JIT last (.NET, JVM) — though snapshot-restore features cut JVM cold starts dramatically. Bundle size often matters as much as the runtime: heavy dependency trees measurably multiply startup.
Do cold starts actually matter for my app?
Only where a human is waiting: synchronous, user-facing hot paths like interactive APIs and payments. Queues, webhooks, scheduled jobs, and other async work absorb cold starts invisibly. Measure tail latency under production-like traffic before spending money on the problem.