On this page
Lambda — Serverless
AWS Lambda runs your code without provisioning servers. Upload a function, configure triggers, and AWS handles scaling from zero to thousands of concurrent executions. Pay only for compute time consumed — ideal for APIs, event processing, and scheduled tasks.
How Lambda Works
Trigger (API Gateway, S3, SQS, EventBridge)
↓
Lambda Function (your code in a runtime)
↓
Execution environment (ephemeral container)
↓
Response / Side effects (DynamoDB, S3, SNS)
| Property | Limit |
|---|---|
| Max execution time | 15 minutes |
| Memory | 128 MB – 10,240 MB |
| Deployment package | 50 MB (zipped), 250 MB (unzipped) |
| /tmp storage | 512 MB – 10,240 MB |
| Concurrent executions | 1,000 default (increasable) |
Create a Lambda Function
# Create deployment package
mkdir lambda-hello && cd lambda-hello
cat > index.mjs << 'EOF'
export const handler = async (event) => {
const name = event.queryStringParameters?.name || 'World';
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: `Hello, ${name}!` })
};
};
EOF
zip function.zip index.mjs
# Create function
aws lambda create-function \
--function-name hello-api \
--runtime nodejs20.x \
--role arn:aws:iam::123456789012:role/LambdaBasicExecution \
--handler index.handler \
--zip-file fileb://function.zip \
--memory-size 256 \
--timeout 10 \
--environment Variables={LOG_LEVEL=info}
Python Example
# lambda_function.py
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Orders')
def handler(event, context):
order_id = event['pathParameters']['orderId']
response = table.get_item(Key={'orderId': order_id})
item = response.get('Item')
if not item:
return {'statusCode': 404, 'body': json.dumps({'error': 'Not found'})}
return {'statusCode': 200, 'body': json.dumps(item, default=str)}
Triggers and Event Sources
| Trigger | Use Case | Invocation Type |
|---|---|---|
| API Gateway | REST/HTTP APIs | Synchronous |
| S3 | File upload processing | Asynchronous |
| SQS | Queue consumer | Poll-based |
| DynamoDB Streams | Change data capture | Poll-based |
| EventBridge | Scheduled cron, event bus | Asynchronous |
| SNS | Pub/sub notifications | Asynchronous |
# Schedule: run every day at 8 AM UTC
aws events put-rule \
--name daily-cleanup \
--schedule-expression "cron(0 8 * * ? *)"
aws lambda add-permission \
--function-name cleanup-function \
--statement-id eventbridge-invoke \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn arn:aws:events:us-east-1:123:rule/daily-cleanup
aws events put-targets \
--rule daily-cleanup \
--targets "Id=1,Arn=arn:aws:lambda:us-east-1:123:function:cleanup-function"
API Gateway Integration
# Create HTTP API (cheaper than REST API)
aws apigatewayv2 create-api \
--name hello-api \
--protocol-type HTTP \
--target arn:aws:lambda:us-east-1:123:function:hello-api
For production APIs, use Lambda Function URLs (simple) or API Gateway (auth, throttling, WAF integration).
Environment Variables and Secrets
# Plain environment variables
aws lambda update-function-configuration \
--function-name hello-api \
--environment Variables={STAGE=production,LOG_LEVEL=warn}
# Secrets from Secrets Manager (recommended)
# Attach policy: secretsmanager:GetSecretValue
# Reference in code:
# secret = boto3.client('secretsmanager').get_secret_value(SecretId='prod/api/key')
Never hardcode API keys in environment variables visible in the console — use Secrets Manager or SSM Parameter Store (SecureString).
Lambda Layers
Share dependencies across functions:
mkdir -p layer/python
pip install requests -t layer/python/
cd layer && zip -r ../requests-layer.zip python/
aws lambda publish-layer-version \
--layer-name python-requests \
--zip-file fileb://requests-layer.zip \
--compatible-runtimes python3.12
aws lambda update-function-configuration \
--function-name my-function \
--layers arn:aws:lambda:us-east-1:123:layer:python-requests:1
VPC Configuration
Lambda in a VPC can access RDS and private resources:
aws lambda update-function-configuration \
--function-name db-query-function \
--vpc-config SubnetIds=subnet-private-1a,subnet-private-1b,SecurityGroupIds=sg-lambda
Trade-off: VPC-attached Lambda may experience longer cold starts and requires NAT Gateway or VPC endpoints for AWS API calls.
Cold Starts and Performance
| Factor | Impact on Cold Start |
|---|---|
| Runtime | Node.js/Python faster; Java/.NET slower |
| Memory | More memory = more CPU = faster init |
| Package size | Smaller zip = faster download |
| VPC | Adds 1–10 seconds to cold start |
| Provisioned Concurrency | Eliminates cold starts (costs when idle) |
# Provisioned concurrency for latency-sensitive APIs
aws lambda put-provisioned-concurrency-config \
--function-name hello-api \
--qualifier live \
--provisioned-concurrent-executions 5
Error Handling and Retries
| Invocation Type | Retry Behavior |
|---|---|
| Synchronous (API GW) | Caller handles errors |
| Asynchronous | 2 retries, then DLQ |
| Stream-based (SQS) | Returns to queue on failure |
# Configure Dead Letter Queue
aws lambda update-function-configuration \
--function-name process-upload \
--dead-letter-config TargetArn=arn:aws:sqs:us-east-1:123:lambda-dlq
Real-World Scenario: Image Processing Pipeline
S3 Upload → Lambda (resize thumbnails) → S3 processed/
↓
DynamoDB (metadata update)
↓
SNS (notify user)
| Function | Memory | Timeout | Trigger |
|---|---|---|---|
| thumbnail-generator | 1024 MB | 60s | S3 ObjectCreated |
| metadata-updater | 256 MB | 10s | S3 ObjectCreated |
| notification-sender | 128 MB | 5s | SNS |
Lambda vs EC2 vs ECS
| Criteria | Lambda | EC2 | ECS Fargate |
|---|---|---|---|
| Management | None | Full OS | Container only |
| Scaling | Automatic | Manual/ASG | Automatic |
| Max duration | 15 min | Unlimited | Unlimited |
| Cost model | Per invocation | Per hour | Per vCPU-hour |
| Best for | Events, APIs | Long-running, custom | Containerized apps |
Common Mistakes
- No timeout configured — default 3 seconds causes silent failures on slow calls
- Hardcoded credentials — use IAM roles and Secrets Manager
- Large deployment packages — use layers, tree-shake dependencies
- Ignoring idempotency — async retries can duplicate side effects
- VPC without endpoints — Lambda can’t reach AWS APIs without NAT/endpoints
- No CloudWatch alarms — monitor errors, duration, throttles
Troubleshooting
| Issue | Diagnosis | Fix |
|---|---|---|
Task timed out |
Slow downstream call | Increase timeout; optimize code |
Runtime.ImportModuleError |
Missing dependency | Add to package or layer |
| Throttled | Concurrency limit hit | Request limit increase; use SQS buffer |
| Can’t connect to RDS | VPC/SG misconfiguration | Lambda SG must allow outbound; RDS SG must allow Lambda SG |
| Cold start latency | Large package, VPC | Provisioned concurrency; reduce package size |
Best Practices
- Keep functions single-purpose (one job per function)
- Set memory and timeout based on profiling, not defaults
- Use IAM roles with least-privilege per function
- Implement structured logging (JSON) for CloudWatch Logs Insights
- Use X-Ray for distributed tracing
- Deploy with SAM, Serverless Framework, or CDK — not manual zip uploads
- Configure DLQs for all asynchronous invocations
Next: CloudWatch — Monitoring.