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

  1. No timeout configured — default 3 seconds causes silent failures on slow calls
  2. Hardcoded credentials — use IAM roles and Secrets Manager
  3. Large deployment packages — use layers, tree-shake dependencies
  4. Ignoring idempotency — async retries can duplicate side effects
  5. VPC without endpoints — Lambda can’t reach AWS APIs without NAT/endpoints
  6. 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.