Two billing shapes, one crossing point
A function is billed as a variable cost with no fixed component: nothing runs, nothing is charged. A container fleet is billed as a fixed cost with no variable component: the instances are up whether or not requests arrive. Plot both against monthly volume and you get a line through the origin and a horizontal line. They cross exactly once, and everything about the serverless-versus-container debate is on one side or the other of that point.
Below the crossover, serverless wins and the margin widens as volume falls. A service handling 200,000 requests a month costs a few dollars on functions and the price of three always-on instances on containers, whatever those instances do with their remaining time. Above the crossover, containers win and the margin widens as volume rises, because you have started paying per request for capacity you could have rented by the hour.
Two details move the crossover a long way. The first is that the container fleet must be sized for peak, not average, so its cost carries whatever idle capacity your traffic shape forces on you — this calculator prices that idle share explicitly. The second is that the serverless charge is proportional to memory as well as time, so a function configured with four times the memory it needs costs four times as much per second and moves the crossover down by the same factor.
The honest framing is that this is a cost comparison, not an architecture recommendation. Cold starts, execution time limits, connection pooling against a relational database, and the operational cost of managing a cluster are all real and none of them appears in the arithmetic below.
GB-seconds, the request fee, and where the crossover comes from
Serverless compute is billed in gigabyte-seconds: memory in gigabytes multiplied by duration in seconds, summed over every invocation. For a month that is N × d × m, where d is seconds and m is gigabytes. Multiply by the GB-second rate and add the per-request fee, and the monthly bill is N(f + d·m·r). The bracket is the cost of one invocation, and it is the single most useful number here — multiply it by a million and you have a figure you can compare across services.
The container side is k × 730 × h: instances, hours in an average month, hourly rate. It contains no reference to traffic at all, which is the entire point.
Setting the two equal gives the crossover directly: N* = k·730·h / (f + d·m·r). Read it as the number of invocations it takes to spend the container bill one invocation at a time. The consequences are worth stating plainly. Halving function duration doubles the crossover. Halving memory doubles it too, provided the function still runs in the same time — which is the catch, because on most platforms memory and CPU are allocated together.
That coupling produces a genuine optimisation. If doubling memory halves duration, the GB-second cost is unchanged and the invocation completes twice as fast, so latency improves for free. If doubling memory reduces duration by less than half, cost rises. The only way to know is to measure duration at two or three memory settings, and it is one of the few tuning exercises in cloud that reliably pays for itself.
Worked example: 30 million invocations against three always-on instances
Your API handles 30,000,000 invocations a month. Each runs 120 ms on average with 512 MB allocated. The request fee is $0.20 per million and compute is $0.0000166667 per GB-second. The container alternative is three instances at $0.0416 an hour, sized for a peak your average load only reaches 45% of.
- GB-seconds. 30,000,000 × 0.12 s × 0.5 GB = 1,800,000 GB-seconds.
- Request charge. 30 million ÷ 1 million × $0.20 = $6.00.
- Compute charge. 1,800,000 × $0.0000166667 = $30.00.
- Serverless total. $6.00 + $30.00 = $36.00 a month.
- Cost per invocation. $0.0000002 request + 0.06 GB-s × $0.0000166667 = $0.0000002 + $0.000001 = $0.0000012, or $1.20 per million.
- Container total. 3 × 730 h × $0.0416 = $91.10 a month.
- Crossover. $91.10 ÷ $0.0000012 = 75.9 million invocations a month.
- Idle capacity. At 45% average utilisation, $91.10 × 0.55 = $50.11 of the container bill is capacity nobody is using.
At 30 million invocations serverless costs $36.00 against $91.10, a saving of $55.10 a month. You would need to grow to 75.9 million invocations — two and a half times current volume — before the containers became the cheaper option, and even then only if three instances still absorbed the peak.
How to read the crossover before you commit to an architecture
Compare the crossover against your growth rate, not just your current volume. A crossover two and a half times above today's traffic is about a year of headroom at 8% monthly growth and about three months at 40%. Anything within a year of reach should be treated as reached, because the migration takes longer than the number suggests.
Weigh the idle-capacity figure separately from the comparison. A container fleet at 45% average utilisation is not necessarily wasteful — it is what absorbing a peak looks like. But it does tell you how much of the container path is available to recover through autoscaling before the comparison needs redoing. If the fleet can scale from three instances at peak to one at trough, its effective monthly cost drops and the crossover falls with it.
Do not read a serverless win as a reason to leave memory untuned. Cost is linear in memory at constant duration, so an over-provisioned function is paying a multiple on the largest term in the bill. Measure duration at 256, 512, 1024 and 2048 MB and pick the setting where cost per invocation is lowest — it is often not the lowest memory setting, because CPU scales with memory.
Finally, if you are above the crossover, the container path itself has a second decision inside it. Steady always-on capacity is exactly the profile that commitments are priced for, and a one-year commitment moves the container line down by a third or more, pushing the crossover lower again. Interruption-tolerant work can go further with spot capacity.
Serverless cost per million invocations by memory and duration
| Memory | 50 ms | 100 ms | 500 ms | 1,000 ms |
|---|---|---|---|---|
| 128 MB | $0.3042 | $0.4083 | $1.2417 | $2.2833 |
| 512 MB | $0.6167 | $1.0333 | $4.3667 | $8.5333 |
| 1,024 MB | $1.0333 | $1.8667 | $8.5333 | $16.8667 |
| 2,048 MB | $1.8667 | $3.5333 | $16.8667 | $33.5333 |
Read along the diagonals: 512 MB at 100 ms and 1,024 MB at 50 ms both cost $1.0333 per million, because doubling memory while halving duration leaves GB-seconds unchanged. That is the trade tuning exploits.
Where this comparison misleads people
- Comparing a function against an idle container fleet you would not actually run. If containers would autoscale to one instance overnight, size the container side on the average number of running instances, not the peak.
- Using median duration instead of mean. Billing sums every invocation, so a long tail that barely moves the p50 can dominate the bill. Take the mean from the platform's own duration metric.
- Forgetting the free tier while it still applies. Most providers grant a monthly allowance of requests and GB-seconds. It matters enormously at small volumes and rounds to nothing above a few million invocations.
- Ignoring what sits behind the function. A managed gateway, a queue, a NAT gateway for outbound traffic and provisioned concurrency are all separate meters, and on small functions they routinely exceed the function charge itself.
- Pricing containers without the platform underneath. A managed Kubernetes control plane, load balancers and node overhead are real monthly costs that a single instance rate does not include.
- Treating cold starts as a cost line. They are a latency problem, and the usual fix — provisioned concurrency — converts serverless back into a fixed hourly charge, which moves the crossover sharply in favour of containers.
Key terms
- GB-second
- One gigabyte of allocated memory held for one second of execution. The unit serverless compute is billed in, so cost is proportional to memory and duration together.
- Crossover volume
- The monthly invocation count at which per-invocation billing and always-on capacity cost the same. Below it functions are cheaper; above it, containers.
- Provisioned concurrency
- Pre-warmed function instances kept ready to remove cold starts. They are billed by the hour, which gives the serverless path a fixed component and raises its cost at low volume.
- Peak sizing
- Choosing instance count so the busiest period is served acceptably. It is what forces idle capacity into the container bill, and it is why average utilisation is well below 100% on most fleets.
What the money does not decide
Cost is rarely the binding constraint at either extreme. Below a million invocations a month the absolute difference is a few dollars and you should choose on operational simplicity, which almost always favours functions. Far above the crossover the difference is large enough that architecture follows the money, and long-running or connection-heavy workloads have usually already been pushed off functions by execution limits and pooling problems rather than by price.
The interesting cases sit near the crossover, and there the deciding factors are traffic shape and team capacity. Spiky traffic favours functions even above the nominal crossover, because the container fleet would have to be sized for a peak it rarely reaches. Steady traffic favours containers even below it, because a commitment can be applied to steady capacity and cannot be applied to invocations.
Whichever way it lands, express the result as a unit cost so the decision is auditable later. Divide either monthly bill by the transactions it serves using the cost per transaction calculator, and revisit the comparison whenever volume doubles — the crossover does not move, but your position relative to it does.
