· 8 min read

How to Reduce AWS Lambda Costs: A Practical Guide

Lambda’s pay-per-use pricing is supposed to eliminate waste. You only pay for what you use, right? In practice, Lambda costs creep up in ways that aren’t obvious from the billing dashboard.

Idle functions accumulate. Memory allocations are copy-pasted from templates. Log groups grow unchecked. Timeouts are set to 15 minutes “just in case.” And before long, your Lambda bill is 3x what it should be.

This guide covers the most impactful Lambda cost optimizations, each with a CLI command you can run today. These are the same checks that CostPatrol’s detection rules perform automatically.

1. Find and Remove Idle Functions (LAM-O003)

The most common Lambda waste: functions that exist but are never invoked. Every team accumulates these. A developer deploys a function for a one-time migration, a feature gets decommissioned but the Lambda stays, or a proof-of-concept never makes it to production.

Idle functions don’t cost much individually (Lambda storage is cheap), but they create operational noise and security surface area. Stale functions often have outdated runtimes, overly broad IAM roles, and unpatched dependencies.

Find functions with zero invocations in the last 30 days:

for fn in $(aws lambda list-functions \
  --query 'Functions[].FunctionName' --output text); do
  inv=$(aws cloudwatch get-metric-statistics \
    --namespace AWS/Lambda \
    --metric-name Invocations \
    --dimensions Name=FunctionName,Value=$fn \
    --start-time $(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S) \
    --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
    --period 2592000 \
    --statistics Sum \
    --query 'Datapoints[0].Sum // `0`')
  [ "$inv" = "0" ] && echo "UNUSED: $fn"
done

Before deleting, check for:

  • Scheduled invocations on cycles longer than 30 days (quarterly reports, annual jobs)
  • Step Functions workflows that reference the function
  • CloudFormation or Terraform stacks that manage the function
  • API Gateway integrations that may still route to it

Delete a confirmed unused function:

aws lambda delete-function --function-name my-unused-function

CostPatrol’s LAM-O003 rule automates this check across all functions in your account, cross-referencing invocation metrics with last-modified timestamps to filter out recently deployed functions.

2. Right-Size Memory Allocations (LAM-O002)

Lambda pricing is directly proportional to memory allocation. A function configured with 1024 MB costs exactly 2x as much per millisecond as the same function at 512 MB. But here’s what most people miss: Lambda allocates CPU proportionally to memory. So a function at 256 MB gets half the CPU of a 512 MB function.

This creates a common trap: developers set memory to 1024 MB or higher “for safety,” but the function only uses 200 MB. They’re paying 5x more than necessary.

Check memory utilization for a specific function:

aws logs filter-log-events \
  --log-group-name /aws/lambda/my-function \
  --filter-pattern "REPORT" \
  --start-time $(date -u -d '7 days ago' +%s)000 \
  --query 'events[].message' --output text | \
  grep -oP 'Max Memory Used: \K[0-9]+' | \
  sort -n | tail -5

This extracts the “Max Memory Used” from Lambda’s REPORT log lines. Compare the maximum memory used against the allocated memory.

Rules of thumb for right-sizing:

Memory Used / AllocatedAction
Under 30%Reduce allocation significantly
30-50%Reduce to ~1.5x peak usage
50-70%Good. Minor savings possible
70-90%Well-sized
Over 90%Consider increasing (risk of OOM)

Valid Lambda memory sizes: 128, 256, 512, 1024, 1536, 2048, 3008, 4096, 5120, 6144, 7168, 8192, 9216, 10240 MB

Update a function’s memory:

aws lambda update-function-configuration \
  --function-name my-function \
  --memory-size 512

Important caveat: Reducing memory also reduces CPU. If your function is CPU-bound (data processing, compression, encryption), lower memory might increase duration enough to offset the savings. Always test with realistic workloads before deploying memory changes.

CostPatrol’s LAM-O002 rule analyzes CloudWatch Logs Insights data across all functions, comparing peak memory usage against allocation. It recommends a specific memory size (peak usage x 1.3, rounded up to a valid Lambda size) with estimated savings.

3. Migrate to ARM64 Architecture (LAM-O001)

This is the easiest 20% savings you’ll find anywhere on AWS. Lambda functions running on ARM64 (Graviton2) cost 20% less per millisecond than x86_64, with equal or better performance for most workloads.

Check which functions are still on x86:

aws lambda list-functions \
  --query 'Functions[?Architectures[0]!=`arm64`].{Name:FunctionName,Runtime:Runtime,Arch:Architectures[0]}' \
  --output table

Compatible runtimes (no code changes required for most workloads):

  • Node.js 18.x, 20.x, 22.x
  • Python 3.9, 3.10, 3.11, 3.12, 3.13

Runtimes that may need testing:

  • Java 11, 17, 21 (native libraries may need ARM builds)
  • .NET 6, 8 (some NuGet packages include x86-only native code)

Migrate a function:

aws lambda update-function-configuration \
  --function-name my-function \
  --architectures arm64

For functions using Lambda Layers with compiled dependencies (e.g., sharp, Pillow, psycopg2), you’ll need ARM64-compatible layer versions. This is the main migration hurdle. Most pure-JavaScript and pure-Python functions work without changes.

CostPatrol’s LAM-O001 rule scans all functions, identifies x86_64 functions running ARM-compatible runtimes, and estimates the 20% savings based on current monthly cost.

4. Control CloudWatch Logs Costs (CWL-O001)

Lambda automatically sends all console.log/print output to CloudWatch Logs. The ingestion cost is $0.50/GB, and retention defaults to “never expire.” For high-traffic functions, log costs can exceed compute costs.

Check your most expensive log groups:

aws logs describe-log-groups \
  --query 'sort_by(logGroups, &storedBytes)[-10:].{Name:logGroupName,SizeGB:storedBytes,Retention:retentionInDays}' \
  --output table

Common issues:

  1. No retention policy. Logs accumulate forever. A function logging 1 GB/day at $0.50/GB ingestion accumulates $15/month in ingestion alone, plus growing storage costs.

  2. Verbose logging in production. Debug-level logging that was useful during development but never turned off.

  3. Logging entire request/response payloads. Common in API handlers. A 50 KB payload logged per invocation at 100,000 invocations/day = 5 GB/day = $75/month in ingestion.

Set a retention policy:

# Set 7-day retention for a Lambda log group
aws logs put-retention-policy \
  --log-group-name /aws/lambda/my-function \
  --retention-in-days 7

# Set 14-day retention for all Lambda log groups without a policy
for lg in $(aws logs describe-log-groups \
  --query 'logGroups[?retentionInDays==null && starts_with(logGroupName, `/aws/lambda/`)].logGroupName' \
  --output text); do
  aws logs put-retention-policy \
    --log-group-name "$lg" \
    --retention-in-days 14
  echo "Set 14d retention: $lg"
done

Recommended retention periods:

Log typeRetention
Development/staging3-7 days
Production (operational)14-30 days
Production (compliance)90-365 days
Debug/verbose1-3 days

For logs that need long-term retention, consider streaming to S3 ($0.023/GB storage) instead of keeping them in CloudWatch ($0.03/GB/month storage). The 30x storage cost difference adds up at scale.

CostPatrol’s CWL-O001 rule identifies log groups with retention periods exceeding 90 days or with no retention policy set, estimating savings from reducing retention to appropriate levels.

5. Fix Timeout Misconfigurations (LAM-O004)

Lambda’s maximum timeout is 15 minutes (900 seconds). Many functions are deployed with the maximum timeout “just in case,” even when they typically complete in 2-3 seconds.

The timeout itself doesn’t cost you money under normal operation. But when something goes wrong, an oversized timeout means Lambda keeps running (and billing) for up to 15 minutes per invocation instead of failing fast.

Find functions with timeouts over 5 minutes:

aws lambda list-functions \
  --query 'Functions[?Timeout>`300`].{Name:FunctionName,Timeout:Timeout,Runtime:Runtime}' \
  --output table

Check actual duration for these functions:

aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time $(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 86400 \
  --statistics Maximum p99 Average \
  --query 'sort_by(Datapoints, &Timestamp)[-7:].{Date:Timestamp,Avg:Average,P99:ExtendedStatistics,Max:Maximum}'

If a function’s P99 duration is 3 seconds but timeout is 900 seconds, set the timeout to 15-30 seconds. This:

  • Reduces maximum cost per failed invocation by 30-60x
  • Surfaces errors faster (fail at 15s instead of 15m)
  • Prevents cascading failures in downstream services

Functions with long timeouts that also run long (average duration > 60 seconds) are architectural candidates for ECS/Fargate or Step Functions. Lambda charges per millisecond, and at high invocation volumes, a function averaging 5 minutes of execution is almost certainly cheaper as a Fargate task.

CostPatrol’s LAM-O004 rule flags functions with timeouts exceeding 300 seconds where average duration also exceeds 60 seconds, suggesting an architectural review.

6. Watch for Cost Spikes (LAM-A001, LAM-002)

Even with good hygiene, Lambda costs can spike unexpectedly:

  • Recursive invocation loops (S3 event triggers Lambda, Lambda writes to same S3 bucket, triggers again)
  • DDoS or bot traffic hitting API Gateway-backed functions
  • Retry storms from SQS dead letter queues or failed event source mappings
  • Cold start waves after deployment

Set up a CloudWatch alarm for Lambda cost anomalies:

# Create an alarm when daily Lambda spend exceeds $50
aws cloudwatch put-metric-alarm \
  --alarm-name lambda-daily-spend-alarm \
  --metric-name EstimatedCharges \
  --namespace AWS/Billing \
  --statistic Maximum \
  --period 86400 \
  --threshold 50 \
  --comparison-operator GreaterThanThreshold \
  --dimensions Name=ServiceName,Value=AWSLambda \
  --evaluation-periods 1 \
  --alarm-actions arn:aws:sns:us-east-1:ACCOUNT_ID:billing-alerts

CostPatrol’s anomaly detection rules LAM-A001 (duration spike) and LAM-002 (invocation spike) continuously monitor Lambda cost patterns and alert on deviations from baseline. LAM-A001 triggers when daily Lambda costs exceed 3x the 14-day rolling average. LAM-002 triggers when invocations spike to 5x the 7-day baseline, catching potential recursive loops early.

Optimization Checklist

Here’s a quick checklist you can run through for any AWS account:

  • List all Lambda functions with zero invocations in 30 days
  • Check memory utilization vs allocation for top-cost functions
  • Identify x86_64 functions on ARM-compatible runtimes
  • Set retention policies on all Lambda log groups
  • Review functions with timeouts > 300 seconds
  • Set up billing alarms for Lambda spend
  • Check for functions using deprecated runtimes (Node.js 16.x, Python 3.8)

What Savings to Expect

OptimizationTypical SavingsEffort
Remove idle functions$0-50/monthLow
Right-size memory10-40% of function costMedium
Migrate to ARM6420% of function costLow-Medium
Fix log retention50-80% of log costsLow
Fix timeoutsRisk reduction (prevents $$$)Low

For a team spending $1,000/month on Lambda, these optimizations typically reduce costs by $200-$500/month. The ARM64 migration alone saves $200/month with minimal effort.

CostPatrol scans for all of these issues automatically. Run a free scan to see which Lambda optimizations apply to your account, with specific savings estimates for each function.

See what CostPatrol finds in your AWS account

Free scan shows your total savings. Upgrade to Pro for full findings, fix commands, and daily Slack alerts.