Explain why serverless functions should usually be treated as ephemeral and stateless, with durable state moved into external stores and managed systems.
Stateless compute is one of the most important mental models in serverless design. A function may reuse an execution environment, hold temporary values in memory, or write short-lived files to local ephemeral storage, but none of those should be treated as durable system state. The function runtime is best understood as transient execution space. Durable truth belongs somewhere else: databases, object stores, caches, workflow engines, queues, or other managed systems designed to survive retries, restarts, and scale-out behavior.
This matters because serverless platforms routinely create, reuse, and discard execution environments based on demand. If a design depends on “the same function instance will probably still be here,” it is relying on accidental behavior instead of stable architecture.
flowchart LR
A["Trigger arrives"] --> B["Function execution"]
B --> C["Temporary in-memory values"]
B --> D["Ephemeral local files"]
B --> E["External durable state"]
C --> F["Lost after execution or reuse changes"]
D --> F
E --> G["Available across retries, scale-out, and new executions"]
What to notice:
Stateless does not mean the system has no state. It means the compute unit should not rely on local runtime state as the authoritative source of truth between executions. The application absolutely still has state:
The key point is where that state lives and how reliably it can be recovered.
Treating compute as ephemeral has several benefits:
This is one reason serverless can scale operationally: the durable parts of the system are pushed into purpose-built managed services rather than being hidden inside long-lived process memory.
The following kinds of state usually belong outside the runtime:
By contrast, temporary parsed input, short-lived transformation buffers, and transient local objects are fine to keep in memory as long as the system does not depend on them after execution ends.
Consider a document-processing workflow. A weak design stores progress in memory and assumes the next step runs in the same warm environment. A stronger design writes progress to a durable store and uses queues or workflow orchestration to continue safely.
1workflow_state:
2 store: document-jobs
3 fields:
4 - document_id
5 - status
6 - last_completed_step
7 - retry_count
1type DocumentJob = {
2 documentId: string;
3 objectKey: string;
4};
5
6export async function extract(job: DocumentJob) {
7 const record = await jobStore.get(job.documentId);
8 if (record?.lastCompletedStep === "extract") {
9 return;
10 }
11
12 const document = await objectStore.read(job.objectKey);
13 const metadata = await extractor.read(document);
14
15 await metadataStore.put({ documentId: job.documentId, metadata });
16 await jobStore.update(job.documentId, {
17 status: "metadata-extracted",
18 lastCompletedStep: "extract",
19 });
20}
What this demonstrates:
Warm reuse can make some values available across invocations, such as cached clients, parsed configuration, or reusable library initialization. That can improve performance. The mistake is to confuse opportunistic reuse with durable system design.
A safe rule is:
If a feature only works when reuse happens, the design is too fragile.
Externalization does not mean every problem should go into one relational table. Different kinds of state belong in different stores:
The goal is not “put everything in a database.” The goal is “put each durable concern in a system designed for it.”
A team processes invoices in a serverless function and keeps deduplication state in memory because the same execution environment is “usually” reused for a burst of requests. Is that acceptable?
Not as a correctness design. The stronger answer is that in-memory reuse may help performance, but deduplication is a durability and correctness concern. If the system must avoid duplicates across retries, cold starts, or scale-out, the deduplication marker needs an external durable home.