Serverless & Lambda
Lambda at scale, cold starts, concurrency, and serverless patterns beyond hello world
Serverless & Lambda
Master serverless computing with AWS Lambda using free flashcards and spaced repetition practice. This lesson covers Lambda functions, event-driven architecture, and scaling patternsβessential concepts for building modern cloud applications without managing servers.
Welcome to Serverless Computing π
Imagine running your code without ever provisioning, configuring, or managing a single server. That's the promise of serverless computing. AWS Lambda revolutionized cloud architecture by letting you execute code in response to eventsβand you only pay for the compute time you consume, measured in milliseconds.
In this lesson, you'll learn how to leverage Lambda for real-world applications, understand execution models, and avoid common pitfalls that catch even experienced developers.
Core Concepts
What is Serverless? π‘
Serverless doesn't mean "no servers"βit means you don't manage them. AWS handles provisioning, scaling, patching, and availability. You focus entirely on your code.
Key characteristics:
- Event-driven execution: Code runs in response to triggers (HTTP requests, file uploads, database changes)
- Automatic scaling: From zero to thousands of concurrent executions
- Pay-per-use: Charged only for actual execution time (billed in 1ms increments)
- Stateless: Each invocation is independent; state must be stored externally
Traditional vs Serverless Architecture βββββββββββββββββββββββββββββββββββββββββββββββββββ β TRADITIONAL SERVER β βββββββββββββββββββββββββββββββββββββββββββββββββββ€ β π₯οΈ Server running 24/7 β β π° Pay for idle time β β π Manual scaling β β π§ OS patching & management β β β οΈ Over/under provisioning risk β βββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββ β SERVERLESS (LAMBDA) β βββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β‘ Runs only when triggered β β π° Pay only for execution time β β π Auto-scales to demand β β π§ Zero infrastructure management β β β Perfect capacity every time β βββββββββββββββββββββββββββββββββββββββββββββββββββ
AWS Lambda Fundamentals πΊ
A Lambda function is a stateless compute service that executes your code in response to events.
Core components:
| Component | Description | Example |
|---|---|---|
| Handler | Entry point function AWS invokes | lambda_handler(event, context) |
| Event | JSON input containing trigger data | S3 object details, API request body |
| Context | Runtime information (request ID, timeout, memory) | context.get_remaining_time_in_millis() |
| Runtime | Execution environment (language version) | Python 3.11, Node.js 18.x, Java 17 |
| Execution Role | IAM permissions for AWS service access | Read S3, write DynamoDB |
Basic Python Lambda function:
import json
def lambda_handler(event, context):
# Extract data from event
name = event.get('name', 'World')
# Business logic
message = f'Hello, {name}!'
# Return response
return {
'statusCode': 200,
'body': json.dumps({'message': message})
}
Node.js Lambda function:
exports.handler = async (event) => {
const name = event.name || 'World';
const response = {
statusCode: 200,
body: JSON.stringify({
message: `Hello, ${name}!`
})
};
return response;
};
π‘ Tip: Lambda supports multiple languages: Python, Node.js, Java, Go, Ruby, .NET, and custom runtimes.
Lambda Execution Model β‘
Understanding the lifecycle of a Lambda invocation is crucial for optimization:
LAMBDA INVOCATION LIFECYCLE ββββββββββββββββββββββββββββββββββββββββββββββββββ β COLD START (first invocation or after idle) β ββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β 1οΈβ£ Download code package β β β (from S3) β β β β β 2οΈβ£ Start execution environment β β β (container, runtime) β β β β β 3οΈβ£ Initialize (run code outside handler) β β β (imports, connections, config) β β β β β 4οΈβ£ Invoke handler function β β β (your business logic) β β β β β 5οΈβ£ Return response β β β β β±οΈ Total: 100-1000ms depending on setup β ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββββββ β WARM START (reused environment) β ββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β β Environment already initialized β β β β β β β β 4οΈβ£ Invoke handler function β β β (your business logic) β β β β β 5οΈβ£ Return response β β β β β±οΈ Total: 1-10ms (much faster!) β ββββββββββββββββββββββββββββββββββββββββββββββββββ
Cold start optimization strategies:
- Keep functions small: Smaller deployment packages download faster
- Minimize initialization: Move one-time setup outside handler but keep it lightweight
- Use Provisioned Concurrency: Pre-warm execution environments for consistent latency
- Choose compiled languages: Java/C# have longer cold starts than Python/Node.js
Example: Optimized initialization
import boto3
import os
## Initialize OUTSIDE handler (runs once per container)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
def lambda_handler(event, context):
# Handler runs on every invocation (use pre-initialized resources)
user_id = event['userId']
response = table.get_item(Key={'userId': user_id})
return {
'statusCode': 200,
'body': json.dumps(response.get('Item', {}))
}
β οΈ Common mistake: Initializing resources inside the handler causes redundant work on every invocation.
Event Sources & Triggers π―
Lambda integrates with 50+ AWS services as event sources:
| Event Source | Use Case | Invocation Type |
|---|---|---|
| API Gateway | REST/HTTP APIs, webhooks | Synchronous |
| S3 | Process uploaded files (images, logs) | Asynchronous |
| DynamoDB Streams | React to database changes | Stream (poll-based) |
| SQS | Queue processing, background jobs | Stream (poll-based) |
| EventBridge | Scheduled tasks, custom events | Asynchronous |
| Kinesis | Real-time data streaming | Stream (poll-based) |
| SNS | Pub/sub notifications | Asynchronous |
| ALB | Application Load Balancer targets | Synchronous |
Invocation types:
- Synchronous: Caller waits for response (API Gateway, ALB)
- Asynchronous: Lambda queues event, returns immediately (S3, SNS)
- Stream-based: Lambda polls source and processes batches (SQS, Kinesis, DynamoDB Streams)
EVENT-DRIVEN ARCHITECTURE FLOW
βββββββββββββββ
β User β
β (Mobile) β
ββββββββ¬βββββββ
β 1. Upload photo
β
βββββββββββββββ 2. S3 Event ββββββββββββββββ
β S3 ββββββββββββββββββββββββββββββββ Lambda β
β Bucket β β (Resize) β
βββββββββββββββ ββββββββ¬ββββββββ
β 3. Store metadata
β
ββββββββββββββββ
β DynamoDB β
β Table β
ββββββββ¬ββββββββ
β 4. Stream change
β
ββββββββββββββββ
β Lambda β
β (Notify) β
ββββββββ¬ββββββββ
β 5. Send notification
β
ββββββββββββββββ
β SNS β
β Topic β
ββββββββββββββββ
Memory, Timeout & Concurrency Configuration π
Memory allocation (128 MB to 10,240 MB):
- Determines CPU power proportionally
- More memory = faster execution = potentially lower cost
- Testing optimal memory is crucial for cost/performance
## Example: Memory impacts execution time
## 512 MB: 5 seconds = $0.000000833 Γ 5000ms = $0.00417
## 1024 MB: 2.5 seconds = $0.000001667 Γ 2500ms = $0.00417
## Same cost, but 2Γ faster response! β‘
Timeout (1 second to 15 minutes maximum):
- Default: 3 seconds
- Set based on expected execution time
- Function terminates if timeout exceeded
β οΈ Critical: API Gateway has 29-second timeoutβLambda timeout must be less!
Concurrency:
- Account limit: 1,000 concurrent executions per region (default, can increase)
- Reserved concurrency: Guarantee capacity for critical functions
- Provisioned concurrency: Keep functions warm (eliminates cold starts)
CONCURRENCY MODEL Scenario: 1,000 requests/second, 100ms execution time ββββββββββββββββββββββββββββββββββββββββββββββββββ β Required Concurrency = RPS Γ Duration β β = 1,000 req/s Γ 0.1s = 100 concurrent β ββββββββββββββββββββββββββββββββββββββββββββββββββ Request Flow: T=0s 100 invocations start T=0.1s 100 complete, next 100 start T=0.2s 100 complete, next 100 start ... Steady state: 100 concurrent executions
Practical Examples
Example 1: API Backend with API Gateway π
Building a serverless REST API for a task management application.
Architecture:
Client β API Gateway β Lambda β DynamoDB
Lambda function (Python):
import json
import boto3
import uuid
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')
def lambda_handler(event, context):
http_method = event['httpMethod']
if http_method == 'GET':
# List all tasks
response = table.scan()
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(response['Items'])
}
elif http_method == 'POST':
# Create new task
body = json.loads(event['body'])
task = {
'taskId': str(uuid.uuid4()),
'title': body['title'],
'status': 'pending',
'createdAt': datetime.utcnow().isoformat()
}
table.put_item(Item=task)
return {
'statusCode': 201,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(task)
}
elif http_method == 'PUT':
# Update task status
body = json.loads(event['body'])
task_id = event['pathParameters']['taskId']
table.update_item(
Key={'taskId': task_id},
UpdateExpression='SET #status = :status',
ExpressionAttributeNames={'#status': 'status'},
ExpressionAttributeValues={':status': body['status']}
)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Task updated'})
}
else:
return {
'statusCode': 405,
'body': json.dumps({'error': 'Method not allowed'})
}
Key patterns:
- Single Lambda handling multiple HTTP methods (monolithic approach)
- Alternative: One Lambda per endpoint (microservices approach)
- DynamoDB for sub-10ms latency
- Proper HTTP status codes and headers
π‘ Production tip: Use API Gateway request validation to reject invalid requests before Lambda invocation (saves cost).
Example 2: S3 Image Processing Pipeline πΈ
Automatic thumbnail generation when images are uploaded.
Workflow:
User uploads image.jpg β S3 Bucket
β (S3 Event)
Lambda Function
β
Resize to 200x200
β
Save to S3 /thumbnails/
Lambda function (Python with Pillow):
import boto3
import os
from PIL import Image
from io import BytesIO
s3 = boto3.client('s3')
def lambda_handler(event, context):
# Extract S3 event information
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# Skip if already a thumbnail
if key.startswith('thumbnails/'):
return {'statusCode': 200, 'body': 'Already a thumbnail'}
# Download original image
response = s3.get_object(Bucket=bucket, Key=key)
image_content = response['Body'].read()
# Resize image
image = Image.open(BytesIO(image_content))
image.thumbnail((200, 200), Image.LANCZOS)
# Save to buffer
buffer = BytesIO()
image.save(buffer, format=image.format)
buffer.seek(0)
# Upload thumbnail
thumbnail_key = f'thumbnails/{key}'
s3.put_object(
Bucket=bucket,
Key=thumbnail_key,
Body=buffer,
ContentType=response['ContentType']
)
return {
'statusCode': 200,
'body': f'Thumbnail created: {thumbnail_key}'
}
Deployment considerations:
- Layer usage: Package Pillow as a Lambda Layer (reduces deployment size)
- Memory: Image processing needs 512-1024 MB for decent performance
- Timeout: Set 1-2 minutes for large images
- Error handling: Handle corrupted images gracefully
β οΈ Watch out: S3 events are asynchronousβLambda retries failures automatically. Make operations idempotent!
Example 3: Scheduled Data Processing with EventBridge β°
Daily report generation running at 6 AM UTC.
EventBridge Rule:
{
"schedule": "cron(0 6 * * ? *)",
"target": "arn:aws:lambda:us-east-1:123456789012:function:DailyReport"
}
Lambda function (Node.js):
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const ses = new AWS.SES();
exports.handler = async (event) => {
try {
// Query yesterday's orders
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dateKey = yesterday.toISOString().split('T')[0];
const params = {
TableName: 'Orders',
IndexName: 'DateIndex',
KeyConditionExpression: 'orderDate = :date',
ExpressionAttributeValues: {
':date': dateKey
}
};
const result = await dynamodb.query(params).promise();
// Calculate metrics
const totalOrders = result.Items.length;
const totalRevenue = result.Items.reduce(
(sum, item) => sum + item.amount, 0
);
// Generate report email
const emailParams = {
Source: 'reports@example.com',
Destination: {
ToAddresses: ['management@example.com']
},
Message: {
Subject: {
Data: `Daily Report - ${dateKey}`
},
Body: {
Text: {
Data: `Total Orders: ${totalOrders}\nRevenue: $${totalRevenue.toFixed(2)}`
}
}
}
};
await ses.sendEmail(emailParams).promise();
return {
statusCode: 200,
body: JSON.stringify({
message: 'Report sent successfully',
metrics: { totalOrders, totalRevenue }
})
};
} catch (error) {
console.error('Report generation failed:', error);
throw error;
}
};
Cron expression breakdown:
cron(0 6 * * ? *)
β β β β β β
β β β β β ββ Year (not required)
β β β β ββββ Day of week (? = any)
β β β ββββββ Month (1-12)
β β ββββββββ Day of month (1-31)
β ββββββββββ Hour (0-23) - 6 AM
ββββββββββββ Minute (0-59) - 0 minutes
π‘ Pro tip: Use CloudWatch Logs Insights to analyze execution patterns and identify optimization opportunities.
Example 4: DynamoDB Stream Processing π
Real-time analytics on database changes.
Use case: Track user activity score as items are added/updated/deleted.
import boto3
from decimal import Decimal
dynamodb = boto3.resource('dynamodb')
scores_table = dynamodb.Table('UserScores')
def lambda_handler(event, context):
for record in event['Records']:
event_name = record['eventName'] # INSERT, MODIFY, REMOVE
if event_name in ['INSERT', 'MODIFY']:
# Get new image
new_item = record['dynamodb']['NewImage']
user_id = new_item['userId']['S']
action = new_item['action']['S']
# Calculate points
points = {
'post_created': 10,
'comment_added': 5,
'like_given': 1
}.get(action, 0)
# Update user score atomically
scores_table.update_item(
Key={'userId': user_id},
UpdateExpression='ADD score :points',
ExpressionAttributeValues={':points': Decimal(points)}
)
elif event_name == 'REMOVE':
# Handle deletions if needed
pass
return {
'statusCode': 200,
'body': f'Processed {len(event["Records"])} records'
}
Stream processing characteristics:
- Batch processing: Lambda receives multiple records (1-10,000)
- At-least-once delivery: Design for idempotency
- Ordered processing: Within a shard, records are ordered
- Automatic retries: Failed batches are retried
β οΈ Critical pattern: Use ADD operation in DynamoDB for atomic counters (handles duplicates gracefully).
Common Mistakes β οΈ
1. Ignoring Cold Starts in Latency-Sensitive Apps
Problem: API endpoints with 2-second cold starts frustrate users.
Solution:
- Use Provisioned Concurrency for critical endpoints
- Keep deployment packages under 50 MB
- Choose interpreted languages (Python, Node.js) for faster starts
- Consider Lambda@Edge for global distribution
2. Not Setting Appropriate Timeouts
Problem: Default 3-second timeout causes premature function termination.
Example:
## β BAD: No timeout consideration
def lambda_handler(event, context):
# This might take 5 seconds!
result = heavy_computation()
return result # Function terminated at 3 seconds
## β
GOOD: Check remaining time
import time
def lambda_handler(event, context):
remaining = context.get_remaining_time_in_millis()
if remaining < 5000: # Need at least 5 seconds
return {'error': 'Insufficient time remaining'}
result = heavy_computation()
return result
Fix: Set timeout to 2Γ expected execution time with monitoring.
3. Creating Resources Inside Handler
Problem: Initializing AWS clients on every invocation wastes time and money.
## β BAD: Initialize every time
def lambda_handler(event, context):
s3 = boto3.client('s3') # Repeated work!
response = s3.get_object(Bucket='my-bucket', Key='file.txt')
return response
## β
GOOD: Initialize once
import boto3
s3 = boto3.client('s3') # Reused across invocations
def lambda_handler(event, context):
response = s3.get_object(Bucket='my-bucket', Key='file.txt')
return response
4. Insufficient IAM Permissions (or Too Many)
Problem: Following the principle of least privilege is often overlooked.
// β BAD: Overly permissive
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
// β
GOOD: Specific permissions
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
5. Not Handling Async Errors Properly
Problem: S3/SNS/EventBridge invocations retry automaticallyβfailures can cause infinite loops.
Solution:
- Configure Dead Letter Queue (DLQ) for failed events
- Set maximum retry attempts (default is 2)
- Implement idempotency checks
## β
Idempotent S3 processing
import hashlib
processed_cache = {} # In production, use DynamoDB
def lambda_handler(event, context):
key = event['Records'][0]['s3']['object']['key']
etag = event['Records'][0]['s3']['object']['eTag']
# Create unique identifier
cache_key = hashlib.md5(f'{key}:{etag}'.encode()).hexdigest()
if cache_key in processed_cache:
return {'statusCode': 200, 'body': 'Already processed'}
# Process file
process_file(key)
processed_cache[cache_key] = True
return {'statusCode': 200}
6. Deploying Large Dependencies
Problem: 250 MB deployment limit includes dependencies.
Solution:
- Use Lambda Layers for shared libraries
- Remove unused packages
- Use lightweight alternatives (e.g.,
boto3already included in Python runtime)
## Check package size before deployment
du -sh ./deployment-package
## If > 50MB, consider layers or trimming
7. Not Monitoring Costs
Problem: Inefficient functions with high memory/long duration cost more than expected.
Monitoring:
- AWS Cost Explorer: Track Lambda spending
- CloudWatch Logs Insights: Analyze duration patterns
- Lambda Power Tuning: Find optimal memory configuration
## Power Tuning finds sweet spot
128 MB: 5000ms execution = $0.00042
512 MB: 1500ms execution = $0.00025 β Optimal!
1024 MB: 1000ms execution = $0.00033
π‘ Did you know? AWS Lambda Power Tuning is an open-source tool that automatically tests different memory configurations to find the most cost-effective setting for your function.
Key Takeaways π―
β Serverless means no infrastructure managementβfocus on code, AWS handles everything else
β Cold starts matterβoptimize initialization, use Provisioned Concurrency for critical paths
β Event-driven architectureβLambda excels at responding to S3, DynamoDB, API Gateway, and 50+ other triggers
β Pay only for execution timeβmeasured in milliseconds, making it cost-effective for sporadic workloads
β Memory = CPUβmore memory means proportionally more CPU; test to find optimal cost/performance
β Stateless by designβuse S3, DynamoDB, ElastiCache for state persistence
β Concurrent execution scales automaticallyβfrom 0 to 1,000s, but understand account limits
β Idempotency is criticalβespecially for async/retry scenarios (S3, DynamoDB Streams)
β Monitoring and loggingβCloudWatch Logs/Metrics are essential for debugging and optimization
β Security firstβleast-privilege IAM roles, encrypt environment variables, use VPC when needed
π Quick Reference Card: Lambda Essentials
| Max execution time | 15 minutes |
| Memory range | 128 MB - 10,240 MB |
| Deployment package | 50 MB zipped, 250 MB unzipped |
| Concurrency limit | 1,000 (default, can increase) |
| Environment variables | 4 KB total |
| Ephemeral storage (/tmp) | 512 MB - 10 GB |
| Invocation types | Synchronous, Asynchronous, Stream-based |
| Pricing (us-east-1) | $0.20 per 1M requests + $0.0000166667 per GB-second |
| Free tier | 1M requests + 400,000 GB-seconds/month |
π§ Memory Aid: "SCALE" for Lambda Success
- Stateless designβstore state externally
- Cold startsβoptimize initialization
- Asynchronous patternsβuse DLQs and retries
- Least privilegeβminimal IAM permissions
- Events everywhereβtrigger from 50+ sources
π Further Study
AWS Lambda Developer Guide: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html - Comprehensive official documentation with best practices
Serverless Framework: https://www.serverless.com/framework/docs - Popular infrastructure-as-code tool for deploying Lambda functions
AWS Lambda Power Tuning: https://github.com/alexcasalboni/aws-lambda-power-tuning - Open-source tool to optimize memory/cost configuration
π Next Steps: Try building a simple serverless API using Lambda + API Gateway + DynamoDB. Start with the AWS Free Tierβ1 million Lambda requests per month are free forever!
Master serverless computing through practice. The best way to learn Lambda is to build real applicationsβstart small, iterate, and scale as you grow more confident with event-driven architectures.