Skip to content
CodeBrewerz logoCodeBrewerz
Cloud

What Is Serverless, Really? The Honest Cost, Limits and Payoff

Serverless is not "no servers". It is a usage based renting a system that bills on usage milliseconds rather than by the hours provisioned. Here are the pros and cons and hard limitations.

Published 8 min readBy Piyush Jain
  • Serverless
  • AWS Lambda
  • Cloud
  • Architecture

There are still servers. The name describes what you no longer think about, not what stopped existing in the same way “wireless” does not mean there are no wires anywhere in the phone network.

A more useful definition would be, serverless is renting a process by the millisecond, where someone else decides when it starts and when it is thrown away.

Everything interesting comes from that one sentence.

What actually happens when a function is invoked?

An event arrives, it could be an HTTP request through an API gateway, a message on a queue, an object landing in a bucket, a cron tick. The platform looks for an execution environment that is already running your code and idle.

If it finds one, it hands the event straight to your handler. That is a warm invocation, and it is fast. If it does not, it builds one: a microVM is created — Firecracker on AWS, in about 120 milliseconds — your package is downloaded and unpacked, the language runtime starts, your module-level code runs, and only then does your handler see the event. That is a cold start.

Figure 1

Where a cold start goes, and what a warm one skips

Cold invocation385 ms
VM
init
you
Warm invocation45 ms
Sandbox 120 ms
Runtime + your imports 220 ms
Your handler 45 ms

V8 start plus module resolution. Bundling to a single file and trimming the SDK surface cuts this considerably.

Runtime
Typical observed ranges for AWS Lambda on x86, not a guarantee — cold start cost varies with package size, VPC configuration and region. The structure is what matters: sandbox creation and runtime init happen once per execution environment, and every subsequent request through that environment pays only the handler.

Two things are happening here that are worth internalizing.

The runtime matters more than your code. A JVM cold start can be twenty times a Go one before your handler executes a single statement. If cold starts are a problem for you, changing the runtime is going to have a better effect than any optimization inside the function.

Everything at module scope is paid on every cold start. Importing a full cloud SDK to use one client, opening a database connection eagerly, reading a large config file. Each of these is charged once per execution environment, and the number of execution environments is not something you control.

How does the scaling actually work?

The platform allocates one execution environment per concurrent request. Not per request per concurrent request. Ten requests per second that each take 100 ms need one environment. Ten requests per second that each take 10 seconds need a hundred.

Figure 2

A spike, and two ways of having enough machines

demand / per-request scalingautoscaled instances (3 min to react)
Peak concurrency720
Ramp8 → 720 in 6 min
Unserved1880 req-min

Per-request scaling has no ramp to speak of — it allocates an execution environment per concurrent request, so the capacity line and the demand line are the same line. The price for that is paid elsewhere: each of those new environments is a cold start, and whatever database sits behind them now has several hundred clients that appeared in under a minute.

A model of a thirty-minute traffic spike. Autoscaling is drawn with a three-minute reaction time and 60 concurrent requests per instance — optimistic for a container that has to pull an image and pass a health check. The shaded region is demand with nowhere to go: queued requests, timeouts, and the pager.

That is the headline feature, and it is genuinely remarkable. No capacity plan, no autoscaling policy, no warm pool, no reaction delay. Compared to a pod of containers that takes three minutes to notice a spike, this is not an incremental improvement.

The bill for that arrives somewhere else. Every new environment during a ramp is a cold start, so the fastest-growing part of a spike is also the slowest-responding. And your database now has several hundred new clients that appeared inside a minute, which is exactly the failure people hit first, because a Postgres instance sized for twenty connections does not care that the thing opening them is fashionable. The answer is a proxy or pooler that uses the database protocol and holds the real connections: RDS Proxy, PgBouncer, Neon’s pooled endpoint, Cloudflare Hyperdrive.

What does it cost?

Two components on every major platform: a charge per invocation, and a charge per gigabyte-second of memory held while your code runs.

Figure 3

When does per-request billing stop being cheaper?

Lambda per month$1.29
Always-on container$25.001 vCPU, 2 GB, running 24/7
Crossoverrequests per month at this size and duration
LambdaContainer

AWS Lambda pricing for x86 in us-east-1 at the time of writing — $0.20 per million requests and $0.0000166667 per GB-second — against a 1 vCPU / 2 GB always-on container at about $25 a month. Prices move; the shape of the curve does not. Free-tier allowances are excluded deliberately, because a business should not plan around them.

Move the sliders and the shape becomes clear. At low volume, serverless is close to free and a container is close to entirely idle. Once traffic exceeds a specific threshold, per-request billing loses to always-on capacity, a disadvantage that grows as your traffic patterns become more predictable.

One counter intuitive detail worth knowing: on Lambda, memory and CPU are the same dial. Doubling memory doubles the CPU share, so a compute-bound function at 1024 MB can finish in less than half the time it took at 512 MB — and cost less, because you are billed for GB-seconds and the seconds fell faster than the gigabytes rose. Never assume the smallest memory setting is the cheapest. Always measure it.

The costs that do not appear in the calculator are usually the ones that matter: API Gateway per request, NAT gateway data processing when your function sits in a VPC, CloudWatch Logs ingestion for functions that log generously, and the cost of every other managed service the architecture now needs.

Where does it genuinely fit?

Event handlers and glue. A file lands in a bucket and needs a thumbnail. A webhook arrives and needs validating and forwarding. A queue message needs processing. This is the original use case and it is still the best one: event-driven, independent, short.

Scheduled work. A daily/monthly report, an hourly sync, a cleanup job. Paying for a machine that runs for four minutes a day is absurd; this is the alternative.

Spiky and unpredictable APIs. Anything where traffic can multiply by fifty without notice, and where provisioning for the peak means paying for the peak all month.

Genuinely small things. A contact form, an webhook, a side project. The free tiers are generous and the operational surface is minimal.

Data pipelines with fan-out. A thousand files that each need the same transform/processing is a thousand parallel invocations, and you did not have to build a worker pool.

Where does it not?

Long-running work. Lambda’s hard limit is fifteen minutes. Anything longer has to be split into steps with state between them — Step Functions, Durable Functions — which is a real architecture, not a configuration change.

Steady, predictable, high traffic. If you have a floor of a thousand requests per second all day, you are buying compute at a premium to get elasticity you are not using. Move it.

Latency-sensitive user paths where cold starts are unacceptable. Provisioned concurrency exists and works, you pay to keep environments warm, but paying to keep them warm is paying for always-on capacity with extra steps.

Anything that wants local state. No local filesystem, no in-memory cache, no sticky sessions. Environments are recycled without warning.

WebSockets and other long-lived connections. The connection has to be held by something else — API Gateway on AWS, or a stateful primitive like Cloudflare Durable Objects — and your function is invoked per message. It works, but the mental model is different enough to catch people out.

Heavy dependencies. A large machine-learning package can push a cold start past several seconds. While container images and caching layers can reduce this time, they cannot bypass the physical limits of transferring large amounts of data.

What are the real trade-offs?

In favour: no servers to patch or provision, no capacity planning, scale-to-zero, per-request billing, and a security surface where the operating system is not a problem. For a small team this is a genuine multiplier, it is the difference between shipping the feature and building the platform to run the feature.

Against: cold starts you do not fully control; hard execution limits, local development that is always a simulation of production, complex observability because a request now touches five managed services, a bill that is easy to model and easy to be surprised by, and lock-in that is real, though usually overstated, the handler is portable, the twelve services around it are not.

The one that deserves more attention than it gets is debuggability. A monolith on a box can be attached to with a debugger. A serverless architecture is a distributed system from the first day, and distributed systems are debugged with traces and structured logs or they are not debugged at all. Budget for that on day one rather than during the first incident.

Which platform?

AWS Lambda is the reference implementation and has the deepest integration surface. Every AWS service can trigger it. Google Cloud Run blurs the line usefully - it runs a container, scales to zero, and it does not matter if your application is an ordinary HTTP server, which makes it the easiest migration target for something that already exists. Azure Functions has the strongest story for stateful orchestration through Durable Functions. Cloudflare Workers is a different shape entirely — V8 isolates rather than containers, cold starts in single-digit milliseconds, and code that runs in hundreds of locations rather than one region, and the tradeoff? A runtime that is not Node.js and limits that are tighter.

That last distinction is the one worth understanding. Container-based platforms give you a familiar runtime with a start-up cost. Isolate-based platforms give you a near-zero start-up cost with an unfamiliar runtime. The trade-off you go with depends entirely on whether your problem is cold starts or dependencies.

The short version

Serverless is a billing model and a scaling model wearing a deployment model’s clothes. It is excellent when work is sporadic, short, event-shaped and independent. It is poor when work is long, steady, stateful or latency dependent.

The engineering question is never “should we use serverless”. It is “for this particular workload, is elasticity worth more than control”, and that question has a different answer for the image resizer than it does for the API that serves the homepage. Most healthy systems end up with both.

Next step

Want This Built, Not Just Explained?

CodeBrewerz builds the systems these posts take apart: web, mobile, cloud and the infrastructure underneath. Tell us what you are building.

Start a conversation