Multi-Cloud · lesson 7 of 8
Intermediate
5Advanced
3Also part of Multi-Cloud
8- + CloudWatch Monitoring Basics from AWS
- + Azure Monitor Basics from Azure
- + Cloud Monitoring Basics from Google Cloud
- + AWS Cost and Well-Architected Basics from AWS
- + Azure Cost Management Basics from Azure
- + GCP Cost and Billing Basics from Google Cloud
- + Availability, Regions, and the Arithmetic of Nines from Cloud Interviews
- + Scaling, Queues, and Backpressure from Cloud Interviews
Serverless Cost Comparison: Lambda vs Azure Functions vs Cloud Run
1M invocations/month, 200ms duration, 256MB memory — the actual monthly bill computed on Lambda, Azure Functions, and Cloud Run, with the math shown.
By SkillByExample · Published
A features comparison can’t be checked. A cost comparison can — run the numbers for a concrete workload and the arithmetic either holds up or it doesn’t.
The workload
1,000,000 invocations/month, 200ms average execution time, 256 MB memory allocated, HTTP request-triggered, one request roughly one instance-second (low concurrency — traffic doesn’t cluster enough for one instance to serve many requests at once).
AWS Lambda
Published list pricing (x86, per-request and per-GB-second), with the always-on free tier applied:
# lambda_cost.py
invocations = 1_000_000
duration_s = 0.2
memory_gb = 256 / 1024
gb_seconds = invocations * duration_s * memory_gb
print(f"GB-seconds: {gb_seconds:,.0f}")
# List price, no free tier
request_cost = invocations / 1_000_000 * 0.20
duration_cost = gb_seconds * 0.0000166667
print(f"List price: ${request_cost + duration_cost:.2f}/month "
f"(requests ${request_cost:.2f} + duration ${duration_cost:.2f})")
# Permanent free tier: 1M requests + 400,000 GB-seconds/month
billable_requests = max(0, invocations - 1_000_000)
billable_gb_s = max(0, gb_seconds - 400_000)
free_tier_cost = billable_requests / 1_000_000 * 0.20 + billable_gb_s * 0.0000166667
print(f"With free tier: ${free_tier_cost:.2f}/month")
$ python lambda_cost.py
GB-seconds: 50,000
List price: $1.03/month (requests $0.20 + duration $0.83)
With free tier: $0.00/month
50,000 GB-seconds is well under the 400,000 GB-second free allowance, and 1,000,000 requests exactly matches the free request allowance — this specific workload costs $0/month on Lambda, not as a rounding artifact but because it genuinely fits inside a permanent (not trial) free tier.
Azure Functions (Consumption plan)
Same shape of pricing — per-execution plus per-GB-second — with a matching free grant.
# functions_cost.py
executions = 1_000_000
duration_s = 0.2
memory_gb = 256 / 1024
gb_seconds = executions * duration_s * memory_gb
execution_cost = executions / 1_000_000 * 0.20
duration_cost = gb_seconds * 0.000016
print(f"List price: ${execution_cost + duration_cost:.2f}/month "
f"(executions ${execution_cost:.2f} + duration ${duration_cost:.2f})")
billable_executions = max(0, executions - 1_000_000)
billable_gb_s = max(0, gb_seconds - 400_000)
free_tier_cost = billable_executions / 1_000_000 * 0.20 + billable_gb_s * 0.000016
print(f"With free tier: ${free_tier_cost:.2f}/month")
$ python functions_cost.py
List price: $1.00/month (executions $0.20 + duration $0.80)
With free tier: $0.00/month
Same result, same reason: Azure’s Consumption plan free grant is structured almost identically to Lambda’s (1M executions, 400,000 GB-seconds), so this workload is free there too.
Cloud Run
Cloud Run’s pricing model is structurally different: CPU-seconds and memory-seconds are billed separately rather than combined into one GB-second figure, and the free tier is split the same way.
# cloudrun_cost.py
requests = 1_000_000
duration_s = 0.2
vcpu = 1
memory_gib = 256 / 1024
vcpu_seconds = requests * duration_s * vcpu
gib_seconds = requests * duration_s * memory_gib
print(f"vCPU-seconds: {vcpu_seconds:,.0f} GiB-seconds: {gib_seconds:,.0f}")
cpu_cost = vcpu_seconds * 0.000024
mem_cost = gib_seconds * 0.0000025
req_cost = requests / 1_000_000 * 0.40
print(f"List price: ${cpu_cost + mem_cost + req_cost:.2f}/month "
f"(cpu ${cpu_cost:.2f} + memory ${mem_cost:.2f} + requests ${req_cost:.2f})")
# Free tier: 180,000 vCPU-s, 360,000 GiB-s, 2,000,000 requests
billable_cpu = max(0, vcpu_seconds - 180_000)
billable_mem = max(0, gib_seconds - 360_000)
billable_req = max(0, requests - 2_000_000)
free_tier_cost = billable_cpu * 0.000024 + billable_mem * 0.0000025 + billable_req / 1_000_000 * 0.40
print(f"With free tier: ${free_tier_cost:.2f}/month")
$ python cloudrun_cost.py
vCPU-seconds: 200,000 GiB-seconds: 50,000
List price: $5.33/month (cpu $4.80 + memory $0.13 + requests $0.40)
With free tier: $0.48/month
Cloud Run comes out most expensive here even after its free tier: $0.48/month against $0.00 on the other two. The reason is the CPU free-tier allowance (180,000 vCPU-seconds) is smaller relative to this workload’s 200,000 vCPU-seconds than Lambda’s and Azure’s compute allowances are relative to their 50,000 GB-seconds — this workload clears the memory-seconds free tier comfortably (50,000 of 360,000) but slightly exceeds the CPU one.
The number that matters more than the ranking
$0.48/month is not a meaningful cost difference at this volume — the real lesson is in why Cloud Run’s number moves at all: it’s billing CPU-seconds at a materially higher per-second rate than the other two bill GB-seconds, on the assumption that concurrency will bring the effective cost down. This calculation assumed one request keeps one instance busy for its whole duration — no sharing. Cloud Run’s default concurrency is 80 requests per instance; if real traffic overlaps enough that one instance serves even 4-5 requests at once instead of 1, the same 200,000 vCPU-seconds of request-processing time gets served by a fraction of the wall-clock instance-seconds Cloud Run actually bills for, and the ranking in this lesson reverses. Compute your own workload’s expected concurrency before trusting this result at a different traffic shape.
Practice
1. Recompute all three at 10x the volume (10M invocations/month) and see which free tiers get exceeded.
Lambda: GB-seconds 500,000 -> exceeds 400,000 free -> billable cost ~$3.47/month
Functions: GB-seconds 500,000 -> exceeds 400,000 free -> billable cost ~$3.40/month
Cloud Run: vCPU-s 2,000,000, GiB-s 500,000 -> both exceed free tiers -> ~$47.23/month
At 10x volume Cloud Run’s gap widens rather than narrows, because its rate-per-unit disadvantage doesn’t change with volume — only concurrency changes it.
2. Recompute Cloud Run's cost assuming average concurrency of 8 requests per instance instead of 1.
concurrency = 8
vcpu_seconds = requests * duration_s * vcpu / concurrency
gib_seconds = requests * duration_s * memory_gib / concurrency
vCPU-seconds: 25,000 GiB-seconds: 6,250
With free tier: $0.00/month
Both figures now clear their free-tier thresholds easily, and Cloud Run becomes free too — concurrency, not the platform, decided the outcome.
3. Increase memory to 512MB on all three (duration held constant) and recompute.
Doubling memory roughly doubles GB-seconds/GiB-seconds on all three, since it’s a direct multiplier. Lambda and Azure Functions both stay within their free tiers (100,000 GB-seconds, still under 400,000); Cloud Run’s memory-seconds figure doubles to 100,000 GiB-s, still under its 360,000 free allowance, so only the CPU-driven cost (unaffected by the memory change) continues to apply.
4. A workload runs 50ms average duration instead of 200ms, same 1M invocations and 256MB. Recompute all three.
GB-seconds / vCPU-seconds / GiB-seconds all drop to 1/4 of the original figures.
Lambda: $0.00 (well within free tier)
Functions: $0.00 (well within free tier)
Cloud Run: vCPU-s 50,000 — now under the 180,000 free allowance — $0.00
At a quarter of the duration, Cloud Run’s CPU usage drops below its free-tier threshold too and all three platforms land at $0 — confirming the earlier result was specifically about this workload’s duration, not a fixed platform ranking.
Next: a single Terraform root module structure that provisions the same conceptual resource across all three provider blocks.