StackPractices
intermediate By Mathias Paulenko

Serverless Cost Estimation Template

Template for estimating serverless costs per workload: invocation pricing, GB-second math, data transfer, and the hidden line items that blow up estimates.

Topics: serverless

Overview

Nobody gets a surprise AWS bill because Lambda was expensive. They get one because they never ran the numbers in the first place. This template gives you a repeatable way to price a serverless workload before you deploy it, and to check the estimate against the real invoice every month after that. Run it alongside the serverless deployment checklist so cost lands in the same review as the launch criteria.

Serverless billing has four moving parts: invocations, execution duration, memory allocation, and data transfer. Each provider weighs them differently, and each one hides a second tier of charges (logs, tracing, queue requests, NAT) that doesn’t show up in the headline pricing. An estimate that only covers invocations and GB-seconds will undershoot by 20-40% on a real workload.

Serverless cost estimation flow: traffic forecast and load tests feed invocation count and duration, which combine with memory into GB-seconds, then add request fees, provisioned concurrency, API Gateway, and hidden costs to produce a monthly estimate that gets validated against the actual bill.

1. Pricing Model Reference

Prices below are us-east-1 / equivalent regions as of mid-2026. Verify against the official pages before presenting an estimate to finance; providers adjust these numbers without warning.

1.1 AWS Lambda Pricing

ComponentFree tierPrice after free tier
Requests1M/month free$0.20 per 1M requests
Compute (GB-second)400K/month free$0.0000166667 per GB-second (x86) / $0.0000133334 (arm64)
PC capacity (GB-second)None$0.0000041667 per GB-second (x86)
PC duration (GB-second)None$0.0000097222 per GB-second (x86)
Data transfer (in)Always free$0.00 per GB
Data transfer (out)100GB/month free$0.09 per GB

Provisioned concurrency has no free tier: once enabled, the function’s requests and duration bill at full rates, and the reserved capacity bills around the clock whether or not it serves traffic.

1.2 Azure Functions Pricing

ComponentFree tierPrice after free tier
Executions1M/month free$0.20 per 1M executions
GB-seconds400K/month free$0.000016 per GB-second
Premium plan (vCPU)None$0.192/hour per vCPU
Premium plan (memory)None$0.0139/hour per GB

1.3 GCP Cloud Functions Pricing (gen2)

Gen2 functions bill on Cloud Run’s model: vCPU-seconds and GiB-seconds instead of the old GHz-second pricing used by gen1.

ComponentFree tierPrice after free tier
Invocations2M/month free$0.40 per 1M invocations
vCPU-seconds180K/month free$0.0000240 per vCPU-second
GiB-seconds360K/month free$0.0000025 per GiB-second
Data transfer (out)1GB/month free$0.12 per GB (premium tier)

2. Cost Calculation Formula

2.1 Lambda Compute Cost

Monthly compute cost = (invocations × duration_seconds × memory_GB) × price_per_GB_second

Example:
  Invocations: 10M/month
  Duration: 200ms average (0.2 seconds)
  Memory: 512MB (0.5 GB)

  GB-seconds = 10,000,000 × 0.2 × 0.5 = 1,000,000 GB-seconds
  Billable = 1,000,000 - 400,000 free tier = 600,000 GB-seconds
  Cost = 600,000 × $0.0000166667 = $10.00/month

The free tier is account-wide, not per-function. When estimating several functions, deduct it once against the biggest workload (or skip it for a conservative number); subtracting 400K GB-seconds from every function understates the bill.

2.2 Lambda Request Cost

Monthly request cost = (invocations - free_tier) × price_per_million / 1,000,000

Example:
  Invocations: 10M/month
  Free tier: 1M

  Billable = 10,000,000 - 1,000,000 = 9,000,000
  Cost = 9 × $0.20 = $1.80/month

2.3 Total Lambda Cost

def estimate_lambda_cost(
    invocations: int,
    avg_duration_ms: float,
    memory_mb: int,
    provisioned_concurrency_gb_seconds: int = 0,
    data_transfer_out_gb: float = 0,
) -> dict:
    """Estimate monthly AWS Lambda cost."""

    duration_s = avg_duration_ms / 1000
    memory_gb = memory_mb / 1024
    gb_seconds = invocations * duration_s * memory_gb

    # With provisioned concurrency enabled the free tier doesn't apply,
    # and execution duration bills at the lower PC rate.
    has_pc = provisioned_concurrency_gb_seconds > 0
    duration_rate = 0.0000097222 if has_pc else 0.0000166667
    free_gb_seconds = 0 if has_pc else 400_000
    free_requests = 0 if has_pc else 1_000_000

    compute_cost = max(gb_seconds - free_gb_seconds, 0) * duration_rate
    request_cost = (max(invocations - free_requests, 0) / 1_000_000) * 0.20

    # Provisioned concurrency capacity: billed 24/7 for the configured amount
    pc_cost = provisioned_concurrency_gb_seconds * 0.0000041667

    # Data transfer cost
    free_tier_data = 100
    billable_data = max(data_transfer_out_gb - free_tier_data, 0)
    data_cost = billable_data * 0.09

    total = compute_cost + request_cost + pc_cost + data_cost

    return {
        "compute_cost": round(compute_cost, 2),
        "request_cost": round(request_cost, 2),
        "provisioned_concurrency_cost": round(pc_cost, 2),
        "data_transfer_cost": round(data_cost, 2),
        "total_monthly_cost": round(total, 2),
        "gb_seconds": int(gb_seconds),
    }

# Example usage
cost = estimate_lambda_cost(
    invocations=10_000_000,
    avg_duration_ms=200,
    memory_mb=512,
)
print(f"Total monthly cost: ${cost['total_monthly_cost']}")

3. Workload Estimation Template

3.1 Per-Function Estimation

Function: processOrder
──────────────────────────────────────────────────
Metric                  | Value          | Source
───────────────────────┼────────────────┼──────────────────
Expected invocations   | 10M/month      | Traffic forecast
Average duration       | 200ms          | Load testing
Memory allocation      | 512MB          | Power Tuning
p99 duration           | 500ms          | Load testing
Data transfer out      | 5GB/month      | Payload size × invocations
Provisioned concurrency| 10 concurrent  | Latency requirement

Estimated monthly cost (PC enabled: no free tier, PC duration rate):
  Compute:  $9.72   (1,000,000 GB-s × $0.0000097222)
  Requests: $2.00   (10M × $0.20/1M, no free tier with PC)
  PC:       $54.00  (10 × 0.5GB × 30 days × 24h × 3600s × $0.0000041667)
  Data:     $0.00   (under 100GB free tier)
  Total:    $65.72/month

Same function without provisioned concurrency: $10.00 compute + $1.80 requests = $11.80/month. The latency guarantee costs $54/month in reserved capacity alone, and that number belongs in the estimate so the trade-off is explicit.

3.2 Multi-Function Estimation

workloads = [
    {"name": "processOrder", "invocations": 10_000_000, "duration_ms": 200, "memory_mb": 512},
    {"name": "sendEmail", "invocations": 5_000_000, "duration_ms": 100, "memory_mb": 256},
    {"name": "generateReport", "invocations": 100_000, "duration_ms": 5000, "memory_mb": 2048},
    {"name": "webhookHandler", "invocations": 50_000_000, "duration_ms": 50, "memory_mb": 128},
]

total_cost = 0
for w in workloads:
    cost = estimate_lambda_cost(w["invocations"], w["duration_ms"], w["memory_mb"])
    print(f"{w['name']}: ${cost['total_monthly_cost']}/month")
    total_cost += cost["total_monthly_cost"]

print(f"\nTotal monthly cost: ${total_cost:.2f}")

3.3 Event-Driven Workload Example

Not every workload is invocation-dominated. An event-driven pipeline triggered by S3 events shows a different cost shape, where the orchestration layer outweighs compute:

MetricValueSource
S3 events500K/monthTraffic forecast
Lambda invocations500K/month (one per event)Trigger mapping
Avg duration3sLoad testing
Memory1024MBPower Tuning
Step Functions transitions6 per documentWorkflow definition
SQS messages500K/month, batched by 10Buffer before processing
Lambda compute:   500K × 3s × 1GB = 1.5M GB-s − 400K free tier = 1.1M × $0.0000166667 = $18.33
Lambda requests:  500K (under the 1M free tier)                                        = $0.00
Step Functions:   500K docs × 6 transitions = 3M transitions × $0.025/1K               = $75.00
SQS requests:     50K batches × ~3 API calls = 150K requests × $0.40/1M                = $0.06
Total: ≈ $93.40/month

Step Functions costs 4x the Lambda compute here. An estimate that only priced the function would report $18/month and miss 80% of the bill. Fill in the section 4 hidden-cost table before trusting the function math.


4. Hidden Costs

The line items below routinely account for a third of a serverless bill. Estimate them explicitly. A “cheap” function with verbose logging can cost more in CloudWatch ingestion than in compute.

4.1 API Gateway Costs

ComponentFree tierPrice
Requests (HTTP API)300M/month$1.00 per 1M
Requests (REST API)1M/month (12mo)$3.50 per 1M
Data transfer out100GB/month$0.09 per GB
WebSocket connections1M/month (12mo)$0.80 per 1M

4.2 Step Functions Costs

TypeFree tierPrice
Standard workflow4K/month (12mo)$0.025 per 1,000 transitions
Express workflow4K/month (12mo)$1.00 per 1M invocations + $0.03 per 1K GB-seconds

4.3 Common Hidden Costs

Hidden costImpactMitigation
CloudWatch Logs ingestion$0.50 per GBSet log retention, use sampling
CloudWatch Logs storage$0.03 per GBSet TTL on log groups
X-Ray tracing$0.50 per 1MSample traces (not 100%)
SQS requests$0.40 per 1MBatch messages
S3 PUT/GET requests$0.005 per 1KBatch S3 operations
Secrets Manager$0.40 per secretUse SSM Parameter Store (free)
DynamoDB read/write unitsPer-requestUse on-demand capacity
NAT Gateway$0.045 per GBUse VPC endpoints where possible

5. Cost Optimization Strategies

5.1 Memory Tuning

MemoryCPU shareCost per 1M invocations (200ms)
128MB1/10 vCPU$0.21
256MB1/5 vCPU$0.43
512MB2/5 vCPU$0.85
1024MB1 vCPU$1.71
2048MB2 vCPU$3.41

Optimal is the memory where cost × duration is minimized: more memory means faster execution and fewer GB-seconds per invocation.

The cheapest configuration on paper is rarely cheapest in production. A 128MB function that takes 2 seconds often costs more than a 1GB function that finishes in 300ms. Run AWS Lambda Power Tuning against real payloads before locking the estimate.

5.2 Optimization Checklist

  • Profile memory with AWS Lambda Power Tuning on real payloads, not synthetic ones
  • Reserve provisioned concurrency for the few functions with hard latency SLAs
  • Batch SQS messages to cut invocation count
  • Filter events in EventBridge so handlers don’t fire on noise
  • Cap log retention at 7-30 days and drop DEBUG logging in production
  • Move non-secret config to SSM Parameter Store instead of Secrets Manager
  • Add VPC endpoints where NAT Gateway egress is the driver
  • Strip dev dependencies and test fixtures from the deployment package
  • Share common dependencies through Lambda Layers
  • Reconcile the estimate against Cost Explorer monthly

Adopting This Template

  • Estimate before the first deploy, not after the first bill. Run the numbers in the design review so cost becomes an input to architecture decisions (memory size, sync vs async, polling vs events) rather than a surprise.
  • Feed it real data. Traffic forecasts and load-test durations beat guesses. If you’ve got neither, model three scenarios (expected, 3x, 10x) and budget for the middle one.
  • Reconcile monthly. Pull the actual per-function cost from Cost Explorer, compare it to the estimate, and update the inputs when drift exceeds ~15%. The template only stays honest if someone owns this loop. The cloud cost optimization guide covers the tagging and chargeback setup that makes per-function actuals possible.
  • Version the estimate with the code. Commit the filled-in template next to the infrastructure definition so cost assumptions are visible in review, and stale numbers get caught in diffs.

Companion files: the serverless-cost-estimation-template companion repo has this worksheet as a standalone Markdown file plus an estimate-costs.py calculator that prices one function from CLI flags or a whole workload list from JSON, with x86 and arm64 rates.

Validating the Estimate

An estimate nobody checks turns into fiction within a quarter. Keep the loop small enough that someone actually runs it:

  1. Pull actuals per function. In Cost Explorer, filter by service and group by resource. This only works if functions carry cost-allocation tags, so enable them before launch or the per-function numbers are estimates of their own.
  2. Compute drift. drift = (actual − estimate) / estimate. Inside ±15% the model is fine; beyond it, find which input moved.
  3. Localize the gap. If compute drifted, invocations or duration moved, so compare p50/p99 in CloudWatch against the load-test numbers. If the gap sits outside Lambda, the section 4 hidden-cost table is the place to look.
  4. Update inputs, not the rate card. Provider prices rarely move mid-quarter; your traffic shape does. Fix the forecast first, then the unit prices.
  5. Log every round. The worksheet’s validation log keeps each monthly comparison, so the next estimate starts from measured inputs rather than the original guess.

Worked example: the template estimated $65.72/month for processOrder; the bill shows $84.10 (+28% drift). CloudWatch shows p99 duration doubled after a downstream API slowed down. Update the duration input, re-run the calculator, and the new estimate lands within 5% of the next bill. The model was fine; the input went stale.

Troubleshooting

  • Actual bill runs 30%+ over estimate: check the hidden-cost table first. CloudWatch Logs ingestion and NAT Gateway egress are the usual suspects, followed by retry storms multiplying invocations.
  • Estimate drifts every month: your inputs went stale. Traffic grows, durations creep up as payloads get bigger, and free tiers get consumed by new services. Re-run load tests quarterly and refresh the numbers.
  • A function’s real cost doesn’t match its calculated cost: duration is probably skewed. Look at p50 vs p99: a function that averages 200ms but spikes to 5s on cold starts or downstream timeouts bills far more GB-seconds than the average suggests.
  • Provisioned concurrency costs more than expected: it’s billed around the clock, not per invocation. Ten units of PC on a 512MB function is ~$54/month in reserved capacity even if the function never runs, and the free tier stops applying to that function entirely. Audit PC allocations monthly.
  • Cost spike with no traffic change: check for retry loops, a cron expression that fires more often than intended, or an event source mapping that re-triggers on partial batch failures.
  • Estimate was right but the bill is still surprising: you priced the function, not the system. Add API Gateway, Step Functions transitions, DynamoDB/S3 request costs, and cross-AZ transfer to the model.

See Also

References

Frequently Asked Questions

How accurate are serverless cost estimates?

Within 10-20% when you feed them load-test data instead of guesses. The number one error source is invocation count. Production traffic routinely doubles or triples the forecast during launches and peak events. Model a worst-case scenario at 3x expected traffic, set billing alarms at 50%, 75%, and 100% of budget, and reconcile estimates against the real bill monthly.

When does serverless become more expensive than EC2?

For low or bursty traffic (under ~40% utilization), serverless wins easily. Past ~70% sustained utilization, EC2 or Fargate gets cheaper because you pay for capacity instead of per-invocation. Rough rule of thumb: a function running continuously every second costs 2-3x an equivalent EC2 instance. Run your actual numbers through the calculator. The break-even point moves with memory, duration, and volume.

How do I reduce CloudWatch Logs costs?

Cap retention at 7-30 days instead of "never expire," keep production logging at INFO, strip verbose logging from hot paths, and sample high-volume logs. For long-term retention, ship to S3 and archive to Glacier at $0.004/GB versus CloudWatch's $0.03/GB. One function writing 1KB of logs per invocation at 10M invocations/month pays $5/month in ingestion alone.

What is the cost impact of provisioned concurrency?

PC bills you for reserved capacity around the clock, whether or not it's used: ten concurrent executions at 512MB come to 10 × 0.5GB × 30 days × 24h × 3600s × $0.0000041667 ≈ $54/month. On top of that, executions on provisioned environments bill duration at $0.0000097222/GB-s and the free tier stops applying to the function entirely. Keep PC for functions with strict latency SLAs where cold starts aren't acceptable, and never enable it in dev or staging.

How do I set up billing alerts for serverless workloads?

Create an AWS Budget with alerts at 50%, 75%, and 100% of monthly spend, tag functions with cost-allocation tags, and filter them in Cost Explorer. Turn on AWS Cost Anomaly Detection for unexpected spikes. For finer granularity, emit an estimated-cost custom metric per invocation and alarm on it in CloudWatch.

How do I estimate costs for Step Functions workflows?

Standard workflows bill $0.025 per 1,000 state transitions; Express bills $1.00 per 1M invocations plus $0.03 per 1K GB-seconds. A 5-step workflow running 100K times/month costs $12.50 on Standard versus about $0.10 on Express plus compute. Pick Standard for long-running orchestrations (over ~5 minutes) and Express for short, high-volume flows.

How do I estimate a function that doesn't exist yet?

Use the closest analog: same runtime, similar payload size, similar downstream calls. Write a minimal prototype that stubs the dependencies, run a 1K-invocation load test to get p50/p99 duration, and feed those numbers into the calculator. Mark the estimate as provisional in the worksheet and re-measure after the first week in production; prototype numbers typically run 10-20% optimistic because real dependencies add latency.

Is data transfer between AWS services always charged?

No. Same-region traffic between Lambda and S3, DynamoDB, or SQS is free inbound and mostly free outbound. You pay for cross-region traffic, cross-AZ traffic in some configurations, and everything leaving to the internet at the EC2 data-out rate. The mistake to avoid is assuming "AWS to AWS" means free; NAT gateway processing fees and cross-region replication are where that assumption breaks.