1 Overview
Function-as-a-service platforms let you deploy a single unit of code that the provider runs on demand, in response to an event, without you provisioning or patching a server. The three best-known examples are the function products from the largest cloud providers, and while their branding differs, the underlying execution model is remarkably similar. You package code plus a declared runtime, upload it, and the platform handles placement, scaling, and teardown. You pay for invocations and for the compute time your code consumes, usually rounded up to some minimum billing increment.
The differences that matter are not the headline features but the edges: how cold starts behave, how concurrency is capped and priced, how long a function may run, how large a deployment package may be, and how tightly the function integrates with queues, schedules, and HTTP gateways. Those edges determine whether a workload is pleasant or painful to operate. This article compares the pattern generically, so the reasoning applies regardless of which vendor's logo is on the console.
A useful mental model is that you are renting a very small, very short-lived computer that appears when an event arrives and disappears when your handler returns. Everything good and bad about serverless follows from that sentence. You get elasticity and near-zero idle cost, and in exchange you accept startup latency, execution time limits, and a programming environment that resets between invocations.
2 Why this workload pattern matters
Most production systems are not one big monolith or one big function; they are a mix. The function pattern is attractive precisely where traffic is unpredictable, where work is naturally event-shaped, and where the cost of keeping a server warm would be wasted. A webhook receiver, an image thumbnailer, a nightly report generator, or a queue consumer that drains a backlog all fit the model well because each unit of work is small, independent, and triggered by something.
The pattern also changes team economics. Small teams can ship a feature without designing capacity, choosing instance sizes, or writing autoscaling rules. That is a genuine reduction in operational surface area, and it lets engineers spend attention on product behavior rather than fleet management. For organizations that already run containers or virtual machines, functions become the escape hatch for spiky or low-volume work that would otherwise force overprovisioning.
The trade-off is that serverless moves complexity rather than deleting it. You still must think about concurrency, retries, idempotency, timeouts, and observability, and you now do so inside a runtime you do not control. Choosing the pattern is therefore less about which provider is best and more about whether the workload's shape, latency budget, and cost curve actually match a per-invocation model. When they do, the result is elegant; when they do not, teams often discover they have rebuilt a queue and a scheduler on top of a platform that already had both.
3 The architecture in plain terms
A function platform has four moving parts. First, an event source: an HTTP request through a gateway, a message on a queue or topic, a file landing in storage, or a timer. Second, a control plane that receives the event and decides which function version to run. Third, a pool of execution environments, which are sandboxes created from your deployment package and a base runtime image. Fourth, a result path: a response to the caller, a write to a data store, or a message published onward.
When an event arrives, the platform either reuses a warm environment or creates a new one, which involves downloading your code, starting the runtime, and running initialization code before your handler executes. That startup path is the cold start. Warm environments persist for a while and can serve many invocations, but the platform may freeze, recycle, or scale them at any time, so you cannot rely on in-memory state surviving between calls. Anything durable belongs in an external store.
Concurrency is the key architectural dial. Each platform lets you cap how many environments may run at once, and each has a default that is generous enough to overwhelm a small database. The mature pattern is to treat the function as a thin adapter: validate the event, do a bounded amount of work, write to a durable store, and hand off anything long-running to another queue or job. Functions that call other functions synchronously tend to produce fragile chains, so most experienced teams keep the call graph shallow and let events flow through durable buffers instead.
4 Scaling and cost trade-offs
Scaling is automatic and horizontal. The platform adds execution environments as events arrive and removes them when demand falls, which means you never choose an instance size and you rarely think about capacity. The catch is that scaling is fast in the wrong direction too: a burst of events can spawn hundreds of concurrent environments, each opening a database connection or calling a downstream API, and the bottleneck moves from your compute to whatever sits behind it. Concurrency limits exist mainly to protect those dependencies.
Cost is where the model gets interesting. You pay per invocation and per unit of execution time, often with a free tier and a minimum billing increment per call. At low or spiky volume this is usually cheaper than keeping servers idle, because idle costs nothing. At steady, high utilization it tends to be more expensive than reserved capacity or a long-running container, because you are paying a per-request premium for elasticity you no longer need. The break-even point depends on request duration, memory allocation, and how much of your traffic is genuinely bursty.
Memory is usually coupled to CPU, so raising memory can lower total cost by shortening runtime, up to a point. Long-running functions are penalized twice: they consume billed time and they hold concurrency slots. The practical guidance is to keep handlers short, push heavy work to specialized services, and measure rather than assume. A workload that looks cheap on a napkin can become expensive once retries, cold starts, and per-invocation logging are included.
5 Failure modes and what to watch
The most common failure is not a crash but a cascade. A downstream dependency slows down, function invocations time out, the platform retries, and the retries multiply load on the already-struggling dependency. Because delivery is often at-least-once, retries can also produce duplicate side effects: double charges, duplicate emails, repeated writes. Handlers must be idempotent, keyed on a stable event identifier, and safe to run more than once.
Cold starts are the second recurring issue. They are most visible for latency-sensitive HTTP endpoints and for runtimes with heavy initialization, such as large dependency trees or connection setup. The mitigations are well known: keep packages small, lazy-load what you can, avoid work at module scope, and use provisioned or pre-warmed capacity where the latency budget truly demands it. Provisioned capacity costs money even when idle, so it should be a deliberate choice, not a default.
Other failure modes include hitting concurrency limits and getting throttled, exceeding execution time or payload size limits, exhausting memory, and losing in-flight work when an environment is recycled. Observability is harder because each invocation is a separate, short-lived context; without structured logs, correlation identifiers, and distributed tracing, debugging becomes guesswork. Treat every function as a distributed system component and design for partial failure from the start.
6 Operating it in practice
Day-to-day operation centers on deployment, configuration, and observability. Deployments should be automated and versioned, with the ability to shift traffic between versions and roll back quickly. Configuration, secrets, and environment variables belong in managed stores rather than baked into packages. Because the runtime is managed, patching the base image is largely the provider's job, but your dependencies are still yours to update, and stale libraries are a common source of security findings.
Observability deserves real investment. Emit structured logs with a request or event identifier, capture duration and outcome metrics, and trace calls across functions and downstream services. Set alarms on error rate, throttles, duration percentiles, and queue age rather than only on raw invocation counts. Load-test with realistic bursts, because a function that behaves well under steady traffic can still fall over when a thousand events arrive at once and every one of them opens a connection.
Finally, decide deliberately when not to use functions. Long-running batch jobs, latency-critical services with steady traffic, and workloads that need persistent connections or large local state are often better served by containers or reserved compute. The strongest architectures use functions for the event-shaped edges and conventional compute for the steady core, with clear ownership and cost visibility across both. Review the bill monthly against the architecture; serverless cost surprises usually come from retries, logging volume, and accidental fan-out, not from the base invocation price.
7 Frequently asked questions
Are the three major function platforms basically interchangeable? For simple event handlers, largely yes: the programming model, scaling behavior, and pricing shape are similar enough that porting is mostly a packaging and integration exercise. The differences sharpen around cold-start behavior, maximum execution duration, concurrency controls, local development tooling, and how deeply each integrates with its own ecosystem of queues, gateways, and identity services. Lock-in is real but usually lives in those integrations rather than in the handler code itself.
When should I choose functions over containers? Choose functions when work is event-driven, short, and bursty, and when you would otherwise pay for idle capacity. Choose containers when you need steady throughput, long-running processes, persistent connections, custom system dependencies, or predictable unit economics at scale. Many teams run both, using functions as the glue and containers as the engine.
How do I control cost? Keep handlers short, right-size memory, avoid synchronous chains, cap concurrency to protect dependencies, sample or reduce log verbosity, and watch retry storms. Measure duration and invocation counts per function so you can attribute spend. And remember that the cheapest architecture is often the one with the fewest moving parts, not the one with the most fashionable runtime.