No Servers? No, Just Other People's Servers.
Serverless: You write code (Function). Cloud Provider manages infrastructure (OS, patching, scaling).
Key Traits:
- Event-Driven: Code sleeps until triggered (HTTP, S3 upload, Queue message).
- Stateless: Functions are ephemeral. No local disk persistence.
- Scale to Zero: If no traffic, you pay $0.
Architecture Patterns
1. Web API (API Gateway + Lambda)
Replaces EC2 + Nginx.
2. Async Processing (S3 + Lambda)
Image resizing pipeline.
- User uploads
photo.jpgto S3 Bucketraw-images. - S3 sends event
ObjectCreatedto Lambda. - Lambda resizes image and saves to
processed-images.
3. Fan-Out (SNS + multiple Lambdas)
- User registers.
- Publish "UserCreated" to SNS Topic.
- Lambda A (Email Service): Sends Welcome Email.
- Lambda B (Analytics): Updates Dashboard.
The Cold Start Problem ❄️
The biggest drawback.
- Request arrives: AWS finds no running container.
- Download Code: Pulls your zip file (50MB) from S3.
- Start Container: Spins up Firecracker MicroVM.
- Init Runtime: Starts Python/Node/Java process.
- Execute Handler: Runs your code.
Total Latency: 200ms (Node.js) to 10s (Java Spring Boot).
Mitigation:
- Keep Warming: Ping function every 5 mins.
- Provisioned Concurrency: Pay to keep instances warm.
- Micro-Frameworks: Don't use heavy frameworks (Spring/Django). Use lightweight ones (Flask/Express/Go).
Limitations
| Feature | Serverless (Lambda) | Containers (Fargate/K8s) | Virtual Machines (EC2) |
|---|---|---|---|
| Max Runtime | 15 minutes (Hard limit) | Unlimited | Unlimited |
| Disk Space | 512MB - 10GB (Ephemeral) | Persistent Volumes | Infinite (EBS) |
| Connection Limits | Massive concurrency kills DBs (Connection Pooling needed) | Controlled scaling | Controlled scaling |
| Cost | Expensive at high sustained load | Cheaper at scale | Cheapest (Reserved Instances) |
Code Example: AWS Lambda (Python)
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')
def lambda_handler(event, context):
"""
Triggered by API Gateway.
event: { "body": "{\"name\": \"Alice\"}" }
"""
print(f"Received event: {event}")
try:
# Parse Input
body = json.loads(event.get('body', '{}'))
name = body.get('name')
if not name:
return {"statusCode": 400, "body": "Missing name"}
# Business Logic
user_id = save_user(name)
return {
"statusCode": 200,
"body": json.dumps({"message": "Success", "id": user_id})
}
except Exception as e:
print(f"Error: {e}")
return {"statusCode": 500, "body": "Internal Error"}
def save_user(name):
item = {"pk": name, "status": "active"}
table.put_item(Item=item)
return name
The Economics: When Does Serverless Actually Save Money?
The billing model is the real architectural decision. Lambda charges per request plus GB-seconds of execution; a VM charges for wall-clock existence. Run the numbers on a 512MB function taking 100ms per request:
1 million requests/month: Lambda: ~$1.05 (compute + requests) Smallest useful VM: ~$15-30/month, mostly idle 500 million requests/month (steady ~190 req/s): Lambda: ~$500+ Two modest VMs behind a load balancer: ~$60-120
The crossover is dramatic. The rule that falls out:
- Spiky, low-average traffic (webhooks, cron jobs, internal tools, early-stage products): serverless wins by an order of magnitude, and scaling to zero means your staging environment is nearly free.
- Sustained high throughput: containers or VMs win, often by 5–10x. At constant load you are paying Lambda's premium for elasticity you never use.
- The hybrid reality: mature teams run the steady base load on containers and use functions for the bursty edges (event processing, glue code, infrequent jobs) — matching each workload to the billing model it exploits best.
Watch for the hidden line items: API Gateway per-request pricing can exceed Lambda's own cost, chatty functions pay for time spent waiting on downstream calls, and cross-AZ data transfer between functions and databases quietly accumulates.
The Database Connection Problem
The sharpest operational edge in serverless is impedance mismatch with relational databases. Every concurrent Lambda instance opens its own database connection — and Lambda will happily scale to 1,000 concurrent instances during a spike. PostgreSQL with max_connections = 100 collapses immediately, not from query load but from connection exhaustion.
Solutions, in rough order of adoption:
- Connection proxies (RDS Proxy, PgBouncer): functions connect to the proxy, which multiplexes thousands of function connections onto a small pool of real ones.
- HTTP-native databases (DynamoDB, Aurora Data API, serverless Postgres providers): no persistent connections at all — every query is a stateless HTTP call, which matches the function model perfectly.
- Concurrency caps: set a maximum concurrency on database-touching functions, converting a database outage risk into managed queueing upstream.
This is why serverless architectures gravitate to DynamoDB in practice — not because NoSQL is inherently better, but because its access model matches ephemeral compute. See SQL vs NoSQL for the broader trade-offs.
Orchestration: Beyond Single Functions
Real workflows are rarely one function. "Process an order" means: validate → charge card → reserve inventory → send confirmation — with retries, timeouts, and compensation when a middle step fails. Two patterns dominate:
- Choreography: functions communicate through events (SNS/EventBridge/SQS). Loosely coupled and scales organically, but the workflow logic exists nowhere explicitly — debugging "why did order 981 stall?" means archaeology across five functions' logs.
- Orchestration (Step Functions, Durable Functions): a state machine explicitly defines the flow, handles retries with backoff, and keeps an inspectable execution history. Costs more per transition, but failed multi-step workflows become visible instead of mysterious.
A useful heuristic: choreography between domains (order service tells the world "order placed"), orchestration within a domain (the payment workflow's five steps run under one state machine). This mirrors the same tension found in Event Sourcing & CQRS.
Interview Tips 💡
- "When NOT to use Serverless?" — Long-running tasks (>15m), WebSocket servers (need stateful connections), Heavy GPU tasks (Training), High-frequency trading (latency variance).
- "Idempotency" — Lambda guarantees "At Least Once" delivery. Your function might run twice for the same event. Make it idempotent! (Check DB before writing).
- "Vendor Lock-in" — Moving Lambda logic to Google Cloud Functions requires rewriting infrastructure code (Terraform helps, but logic is tied to SDKs).
- "Cost crossover" — Show you know serverless is cheap at low/spiky volume and expensive at sustained scale; name the hybrid pattern (containers for base load, functions for bursts).
- "Connection pooling" — Bringing up RDS Proxy or an HTTP-native database unprompted signals real production experience.
Related Concepts
- Microservices
- Distributed Tracing (X-Ray)
- Message Queues (SQS)
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
Caching Strategies
A breakdown of where to place your cache and how to keep it in sync with your database.
Database Sharding
How to split a massive database across multiple servers. Horizontal scaling strategies, challenges (Joins, ACID), and real-world algorithms used by Instagram, Vitess, and CockroachDB.
System Design: Dropbox (Google Drive)
Designing a file synchronization service like Dropbox or Google Drive. Key concepts: Block-level Deduplication, Delta Sync, and Strong Consistency.