"Serverless" is a marketing name for a real architectural shift: instead of provisioning a server that sits ready to handle requests, you hand a provider a function and a set of events that should trigger it, and the provider decides when, where, and how many times to run it.
There are still servers underneath - the name refers to who manages them, not whether they exist.
Serverless Basics shows what that looks like in code for AWS Lambda; this page is the execution model underneath those examples - what actually happens between an event arriving and your handler returning, and why that model shapes almost every practice in this section.
A serverless function runs only in response to an event, inside a provider-managed execution environment that the provider creates, reuses, and eventually discards on its own schedule.
Insight: The provider - not your code - owns the process lifecycle, which removes idle-capacity cost and ops overhead but also removes assumptions ordinary Node servers rely on, like a long-lived process or a warm in-memory cache.
When to Use: Spiky or intermittent HTTP traffic, event-driven background processing (queues, streams, scheduled jobs), and workloads where paying only for actual invocation time matters more than predictable low-single-digit-millisecond latency.
Limitations/Trade-offs: Cold starts add latency variance you don't fully control, execution time and memory are capped by the provider, and anything a handler wants to persist has to live in an external backing service, never in local process memory.
A traditional Node server starts once, keeps a process alive indefinitely, and handles every request inside that same long-running process - which means in-memory caches, open database connections, and module-level state all survive between requests.
A serverless function inverts that: your code has no control over when a process starts, how long it lives, or whether the next invocation reuses the same one at all.
Instead, a function-as-a-service (FaaS) platform - AWS Lambda, Azure Functions, Google Cloud Functions - owns an execution environment: a sandboxed runtime instance that the platform creates on demand, hands one or more invocations to, and eventually tears down when it decides the environment is no longer useful to keep around.
Your only real contract with the platform is the handler: a function with a defined signature that the platform calls once per invocation, and whose return value (or resolved promise) the platform turns into a response or a completion signal.
Everything else - process creation, request routing to an available environment, scaling the number of concurrent environments up or down - is the platform's job, which is the actual meaning of "serverless": not the absence of servers, but the absence of your responsibility for managing them.
A simple way to hold this model: think of the provider as running a pool of identical, interchangeable workers who only show up when there's a job posted, and who the provider is free to send home the moment there's a lull.
Every invocation falls into one of two categories, and the difference between them is the single biggest source of serverless latency variance.
A cold start happens when the platform has no existing execution environment ready for your event, so it has to create one from scratch: download your deployment package, start the language runtime, run any top-level (init-phase) code in your module before your handler is even reachable, then finally call the handler.
A warm invocation happens when the platform already has an execution environment left over from a previous invocation and simply calls your handler again inside it - skipping every step above except the handler call itself.
Event arrives
-> platform looks for an idle execution environment
found -> warm invocation: call handler directly
none -> cold start: create environment, run init code, then call handler
-> handler runs, returns a result
-> environment stays warm for a while, or is torn down if idle too long
This is why code placement inside a handler file matters more in serverless than in a normal server: anything declared at module top level (a database client, a parsed config schema) runs once per environment, not once per invocation - so creating it outside the handler lets warm invocations skip that cost, while creating it inside the handler pays it on every single call.
// Runs once per execution environment - reused across warm invocationsconst client = createDatabaseClient();export const handler = async (event: RequestEvent) => { // Runs on every invocation - this environment may be brand new or warm return client.query(event.id);};
The other consequence of this model is statelessness: because you cannot predict whether the next invocation lands on this same environment or a freshly created one, any state your handler depends on - a session, a counter, a cache - has to live in a backing service (a database, object storage, a managed cache) rather than in a local variable, or your code will behave differently depending on an implementation detail you don't control.
Concurrency follows the same logic from the other direction: the platform can spin up many execution environments in parallel to handle simultaneous events, which is where serverless gets its elastic scaling story - but it also means your handler needs to assume it might be running many times over, simultaneously, against shared downstream resources like a database connection limit.
Execution time and memory aren't unlimited the way they effectively are on a server you provision yourself - every platform caps how long a single invocation may run and how much memory an execution environment gets, and exceeding either terminates the invocation.
That cap is a real architectural boundary, not just a tuning knob: work that genuinely needs to run for hours, or hold a persistent connection open (a WebSocket, a long-poll), doesn't fit the model at all and belongs on an always-on compute layer instead - see The Container Orchestration Model for that alternative.
Cold start latency itself has evolved as a first-class operational concern: smaller deployment bundles, fewer heavy imports at module scope, and features like provisioned/reserved concurrency (keeping a set number of environments pre-warmed) all exist specifically to manage the variance a cold start introduces - Cold Start Mitigation covers the concrete tactics.
Network-adjacent resources add their own wrinkle: attaching a function to a private network (a VPC, to reach a private database) can itself slow cold starts, because the platform now has to provision network interfaces as part of environment creation - a cost that doesn't exist for a function that only talks to public, internet-facing services.
Observability also looks different here: there's no long-lived process to attach a profiler to mid-flight, so serverless debugging leans heavily on structured logs and distributed tracing emitted per invocation, correlated by a request ID, rather than on inspecting a running process.
Compute Model
Strength
Weakness
Best Fit
Serverless (FaaS)
No idle cost; scales to zero and to many automatically
Cold starts, execution time limits, no local persistent state
Spiky HTTP, event-driven background work, scheduled jobs
Always-on containers (K8s/ECS)
Predictable low latency; long-lived connections work naturally
Pay for idle capacity; you own scaling and process supervision
Steady baseline traffic, WebSockets, long-running jobs
"Serverless means there's no server running my code." There's still a server - a provider-managed execution environment - the difference is that you never provision, patch, or scale it yourself.
"A cold start happens on every single invocation." Only when no idle execution environment is available; the platform reuses existing environments for warm invocations whenever it can, which is the common case under steady traffic.
"Code at the top of my handler file runs on every request." Code outside the handler function runs once per execution environment, not once per invocation - it's shared across every warm invocation that environment serves.
"Serverless functions can hold state between requests the way in-memory caches do on a server." Any state has to be treated as gone the moment an invocation ends, because the next event might land on a different, brand-new execution environment.
"Serverless is always cheaper than running containers." It's cheaper for spiky or low-average-utilization workloads; at sustained high, steady traffic, the per-invocation pricing model can cost more than an always-on server sized for that same load.
What does "serverless" actually mean if there are still servers?
It refers to who's responsible for managing the server, not whether one exists. The cloud provider handles provisioning, scaling, and patching the execution environment; you only supply the function and the events that should trigger it.
What is an execution environment?
A sandboxed runtime instance the platform creates to run your function - it starts the language runtime, runs your module's top-level code once, and then can serve one or more handler invocations before the platform eventually tears it down.
What's the difference between a cold start and a warm invocation?
A cold start means the platform had to create a brand-new execution environment before it could call your handler, paying the cost of runtime startup and your module's init code. A warm invocation reuses an environment left over from a previous call, skipping straight to the handler.
Why does it matter where I create a database client in my handler file?
Code at module top level runs once per execution environment and is reused by every warm invocation that environment serves, while code inside the handler function runs on every single call. Creating expensive resources like clients outside the handler avoids paying that cost repeatedly.
Can my function keep state between invocations?
Not reliably - you cannot control whether the next invocation reuses the current environment or lands on a new one, so any state that must persist belongs in an external backing service, such as a database or managed cache, not in a local variable.
Why do serverless functions have execution time and memory limits?
The platform is managing a shared, elastic pool of execution environments on your behalf, and unbounded per-invocation runtime would make that pool impossible to size or bill predictably. Workloads that genuinely need long-running or persistent-connection execution belong on always-on compute instead.
How does serverless scale to handle traffic spikes?
The platform creates additional execution environments in parallel as concurrent events arrive, rather than routing more requests through a single process. That's also why concurrent invocations can put unexpected pressure on downstream resources like a database's connection limit.
When does serverless NOT fit a workload?
When you need long-lived connections (WebSockets, long-polling), predictable sub-10ms latency regardless of cold starts, or steady round-the-clock traffic where an always-on server would actually cost less than per-invocation pricing.
Does attaching a function to a private network affect anything?
Yes - reaching a private resource like a database inside a VPC typically requires the platform to provision network interfaces as part of creating the execution environment, which can add measurable latency to cold starts specifically.
How is debugging different without a long-lived process?
There's no running process to attach a live profiler or debugger to between invocations, so serverless observability relies on structured logs and distributed traces emitted per invocation and correlated by a request ID.
Is serverless always the cheaper option compared to containers?
Not universally - it tends to win for spiky or intermittent traffic where a container would otherwise sit idle, but at sustained high traffic the per-invocation cost model can exceed what a correctly sized always-on server would cost.
What's the actual unit the platform is managing - a function, or something else?
The execution environment, not the function definition itself. Your function is just code the platform loads into whichever execution environment it decides to create or reuse for a given event.