Serverless Architecture Patterns
Design scalable applications with serverless computing. Covers function composition, event-driven architectures, cold start optimization, serverless API design, fan-out/fan-in patterns, and the patterns that make serverless production-ready.
Serverless computing eliminates infrastructure management: no servers to provision, no capacity to plan, no patches to apply. Functions run on demand, scale automatically, and you pay only for execution time. But serverless introduces its own complexity: cold starts, distributed state, vendor lock-in, and debugging challenges.
When Serverless Fits
Good fit:
☑ Event-driven workloads (webhooks, notifications)
☑ Variable traffic with unpredictable spikes
☑ Low-traffic APIs (pay-per-use saves money)
☑ Data processing pipelines (file uploads, ETL)
☑ Scheduled tasks (cron jobs)
Poor fit:
☒ Long-running processes (>15 min on AWS Lambda)
☒ High-throughput, steady-state workloads (containers are cheaper)
☒ Low-latency requirements (<50ms, cold starts are a problem)
☒ Stateful applications (WebSockets, gaming)
☒ Heavy compute (ML training, video encoding)
Patterns
Fan-Out/Fan-In:
S3 Upload → Lambda splits file into chunks
→ Lambda processes chunk 1
→ Lambda processes chunk 2
→ Lambda processes chunk 3
→ SQS collects results → Lambda aggregates
API Composition:
API Gateway → Lambda: /users → DynamoDB
→ Lambda: /orders → DynamoDB
→ Lambda: /payments → Stripe API
→ Lambda: /reports → S3
Event Processing:
Kinesis Stream → Lambda filters → DynamoDB
→ Lambda enriches → ElasticSearch
→ Lambda archives → S3
Saga (Distributed Transaction):
Step Function:
1. Lambda: Reserve inventory → Success → Continue
2. Lambda: Charge payment → Success → Continue
3. Lambda: Create shipment → Failure → Compensate
Compensate:
3c. Lambda: Refund payment
2c. Lambda: Release inventory
Cold Start Optimization
# Cold start: First invocation after idle period
# AWS Lambda cold start: 100ms-10s depending on runtime and package size
# BEFORE: Slow cold start (5+ seconds)
import boto3
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
def handler(event, context):
# All imports loaded every cold start
...
# AFTER: Optimized cold start
# 1. Use lighter runtime (Node.js/Python > Java/C#)
# 2. Minimize package size
# 3. Lazy load heavy dependencies
import json # Light import at module level
def handler(event, context):
# Only import heavy libraries when actually needed
if event.get("needs_ml"):
import sklearn # Lazy load
# Reuse connections between invocations
# (module-level = persists across warm invocations)
...
# Module-level initialization (runs once per cold start)
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('users') # Connection reused across invocations
# 4. Provisioned concurrency for critical functions
# AWS: Keep N instances warm at all times
# Cost: ~$0.000004463 per GB-second provisioned
Cost Model
Lambda pricing (AWS):
Per request: $0.20 per 1M requests
Per compute: $0.0000166667 per GB-second
Example: API with 10M requests/month, 256MB, 200ms avg
Requests: 10M × $0.20/1M = $2.00
Compute: 10M × 0.2s × 0.25GB × $0.0000166667 = $8.33
Total: $10.33/month
Same workload on EC2 (t3.medium, always-on):
Monthly: $30.37
Serverless is 66% cheaper for this workload!
But: At 100M requests/month (steady traffic):
Lambda: $103.33/month
EC2: $30.37/month (can handle the load)
Containers become cheaper at steady, high-volume traffic.
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Monolith in a Lambda | Huge package, slow cold start | Small, focused functions |
| Synchronous chains | Fragile, cascading timeouts | Event-driven, async communication |
| No timeout configuration | Function runs until platform max | Set timeout per function based on expected duration |
| Shared mutable state | Race conditions, data corruption | External state store (DynamoDB, Redis) |
| No dead letter queue | Failed events silently lost | DLQ for every async invocation source |
Serverless is not about eliminating servers — it is about eliminating server management. The trade-off: less infrastructure work, more application architecture complexity. Choose serverless when the trade-off works in your favor.