Lambda Best Practices
Optimize AWS Lambda functions — cold starts, memory tuning, dependency management, monitoring, and cost optimization.
Writing Lambda functions is easy. Running them efficiently in production requires attention to performance, cost, and reliability.
Minimize Cold Starts
A cold start occurs when Lambda initializes a new execution environment. It adds latency (100ms–3s+).
Keep Packages Small
# BAD — entire AWS SDK (already in Lambda runtime)
pip install boto3 -t package/
# GOOD — only what you need
pip install requests -t package/
# boto3 is pre-installed in Lambda
Remove unnecessary files from deployment packages:
find package/ -name "*.pyc" -delete
find package/ -name "tests" -type d -exec rm -rf {} +
find package/ -name "__pycache__" -type d -exec rm -rf {} +
Use Lambda Layers
Share heavy dependencies across functions:
pip install numpy pandas -t python/lib/python3.12/site-packages/
zip -r data-layer.zip python/
aws lambda publish-layer-version --layer-name data-science --zip-file fileb://data-layer.zip
Provisioned Concurrency
Eliminate cold starts for latency-sensitive endpoints:
aws lambda put-provisioned-concurrency-config \
--function-name my-api \
--provisioned-concurrent-executions 5
Costs more but guarantees warm instances.
Memory and CPU Tuning
Memory affects CPU proportionally. More memory = faster execution (sometimes cheaper overall):
# Test different memory settings
aws lambda update-function-configuration --function-name my-func --memory-size 512
aws lambda update-function-configuration --function-name my-func --memory-size 1024
Use AWS Lambda Power Tuning tool to find the optimal memory/cost ratio.
| Workload | Recommended Memory |
|---|---|
| Simple API handler | 128–256 MB |
| Data processing | 512–1024 MB |
| ML inference | 1024–3008 MB |
| Image processing | 512–1024 MB |
Initialize Outside the Handler
Reuse connections across invocations:
import boto3
# Created once — reused across warm invocations
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("MyTable")
http_client = boto3.client("secretsmanager")
def lambda_handler(event, context):
# Fast — connection already established
result = table.get_item(Key={"id": event["id"]})
return result
# BAD — creates new connection every invocation
def lambda_handler(event, context):
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("MyTable")
...
Set Appropriate Timeouts
# Default is 3 seconds — often too short
# API handlers: 10–30 seconds
# Data processing: 60–300 seconds
# Max: 900 seconds (15 minutes)
Error Handling and Retries
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
try:
result = process(event)
return {"statusCode": 200, "body": json.dumps(result)}
except ValueError as e:
logger.warning("Validation error: %s", e)
return {"statusCode": 400, "body": json.dumps({"error": str(e)})}
except Exception:
logger.exception("Unhandled error")
raise # Lambda retries on unhandled exceptions
Configure Dead Letter Queue (DLQ) for failed invocations:
# SAM template
DeadLetterQueue:
Type: SQS
TargetArn: !GetAtt DLQQueue.Arn
Environment Variables
import os
DEBUG = os.environ.get("DEBUG", "false") == "true"
TABLE_NAME = os.environ["TABLE_NAME"] # required — fail fast if missing
Use AWS Secrets Manager or Parameter Store for sensitive values — not environment variables in production.
Monitoring
Structured Logging
import json
import logging
logger = logging.getLogger()
def lambda_handler(event, context):
logger.info(json.dumps({
"request_id": context.aws_request_id,
"function": context.function_name,
"memory_limit": context.memory_limit_in_mb,
"event_type": event.get("httpMethod", "direct"),
}))
CloudWatch Metrics
Key metrics to watch:
- Duration — execution time
- Errors — failed invocations
- Throttles — concurrency limit hit
- ConcurrentExecutions — current load
- IteratorAge — stream processing lag
X-Ray Tracing
from aws_xray_sdk.core import xray_recorder
@xray_recorder.capture("process_order")
def process_order(order_id):
...
Enable active tracing in Lambda configuration.
Cost Optimization
- Right-size memory — don’t over-provision
- Reduce invocations — batch events, use SQS buffering
- Avoid provisioned concurrency unless latency requires it
- Use Graviton (ARM) — 20% cheaper:
--architectures arm64 - Set reserved concurrency to prevent runaway costs
Security Checklist
- Least-privilege IAM role
- Secrets in Secrets Manager, not env vars
- VPC only when accessing private resources
- Enable AWS CloudTrail logging
- Validate all input in the handler
- Keep runtime updated (Python 3.12+)
Related Chapters
These practices keep Lambda functions fast, reliable, and cost-effective at scale.