Serverless Architecture Patterns: A Complete Guide to Building Without Servers
Serverless architecture lets you build and run applications without managing infrastructure. You write functions, deploy them, and the cloud provider handles scaling, patching, and availability. But "serverless" doesn't mean no servers — it means servers are no longer your problem.
What Is Serverless?#
Serverless computing is an execution model where the cloud provider dynamically allocates resources. You pay only for the compute time your code consumes, not for idle servers.
There are two main categories:
- FaaS (Function as a Service) — Execute individual functions in response to events. Examples: AWS Lambda, Google Cloud Functions, Cloudflare Workers.
- BaaS (Backend as a Service) — Managed services that replace custom backend logic. Examples: Firebase Auth, AWS Cognito, Stripe.
FaaS is what most people mean when they say "serverless." BaaS complements it by offloading authentication, storage, and payments.
Event-Driven Architecture Patterns#
Serverless thrives on events. Here are the core patterns:
1. API Gateway + Lambda#
The most common pattern. An HTTP request hits an API gateway, which invokes a Lambda function.
Client → API Gateway → Lambda → DynamoDB
// AWS Lambda handler
export const handler = async (event) => {
const { httpMethod, pathParameters, body } = event;
if (httpMethod === "GET") {
const item = await dynamodb.get({
TableName: "Products",
Key: { id: pathParameters.id },
}).promise();
return { statusCode: 200, body: JSON.stringify(item.Item) };
}
if (httpMethod === "POST") {
const data = JSON.parse(body);
await dynamodb.put({
TableName: "Products",
Item: { id: crypto.randomUUID(), ...data },
}).promise();
return { statusCode: 201, body: JSON.stringify({ success: true }) };
}
};
2. Event Streaming#
Functions triggered by queue or stream events — decoupled producers and consumers.
S3 Upload → EventBridge → Lambda (resize) → S3 (thumbnails)
→ Lambda (metadata) → DynamoDB
3. Choreography (Event Bus)#
Multiple services react independently to the same event, with no central orchestrator.
Order Placed Event
→ Inventory Service (reserve stock)
→ Payment Service (charge card)
→ Notification Service (send email)
4. Fan-Out / Fan-In#
One event triggers multiple parallel functions, then results aggregate.
// Fan-out: process chunks in parallel
export const handler = async (event) => {
const chunks = splitData(event.payload, 10);
const promises = chunks.map((chunk) =>
lambda.invoke({
FunctionName: "processChunk",
Payload: JSON.stringify({ chunk }),
}).promise()
);
const results = await Promise.all(promises);
return mergeResults(results);
};
Cold Starts: The Real Cost#
A cold start occurs when a function is invoked after being idle. The provider must allocate a container, load the runtime, and initialize your code. This adds latency — typically 100ms to several seconds.
Mitigation Strategies#
| Strategy | How It Works | Tradeoff |
|---|---|---|
| Provisioned concurrency | Pre-warms N instances | Higher cost |
| Smaller bundles | Less code to load | Requires tree-shaking discipline |
| Lighter runtimes | Use Node.js or Python over Java/C# | Language constraints |
| Keep functions warm | Scheduled pings every 5 min | Adds invocation cost |
| Cloudflare Workers | V8 isolates, near-zero cold starts | Limited runtime APIs |
// Optimize: initialize outside the handler (reused across invocations)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({}); // initialized once per container
export const handler = async (event) => {
// client is reused on warm invocations
return await client.send(/* ... */);
};
Serverless vs Containers#
This is not an either/or decision. Each excels in different contexts.
| Factor | Serverless | Containers |
|---|---|---|
| Scaling | Automatic, per-request | Manual or auto-scaling policies |
| Cold starts | Yes, variable latency | No (always running) |
| Long-running tasks | Limited (15 min on Lambda) | Unlimited |
| Cost at low traffic | Near zero | Base cost for idle containers |
| Cost at high traffic | Can be expensive | More predictable |
| Vendor lock-in | Higher | Lower (portable) |
| Debugging | Harder (distributed) | Easier (local parity) |
Choose serverless when: traffic is spiky, workloads are short-lived, or you want zero ops overhead.
Choose containers when: you need long-running processes, predictable latency, or high sustained throughput.
The Cost Model: Pay-Per-Invocation#
Serverless pricing has three components:
- Invocations — per request (e.g., $0.20 per 1M requests on Lambda)
- Duration — per GB-second of compute
- Data transfer — outbound bandwidth
At low scale, serverless is nearly free. At high scale, the math changes. A function running 1B times/month at 200ms each costs significantly more than equivalent containers.
Cost optimization tips:
- Right-size memory allocation (Lambda scales CPU with memory)
- Minimize execution time — return early, cache aggressively
- Use reserved concurrency to prevent runaway scaling
- Batch operations where possible
The Serverless Toolbox#
Compute#
- AWS Lambda — The most mature FaaS platform. Supports Node.js, Python, Go, Rust, Java.
- Google Cloud Functions — Tight integration with Firebase and GCP services.
- Vercel Functions — Optimized for frontend frameworks. Deploys from Git.
- Cloudflare Workers — V8 isolates at the edge. Sub-millisecond cold starts. Great for latency-sensitive workloads.
Serverless Databases#
- DynamoDB — AWS-native key-value store with single-digit ms latency. Pay per read/write unit.
- PlanetScale — Serverless MySQL with branching. Schema changes without downtime.
- Neon — Serverless Postgres. Scales to zero. Branching for dev/preview environments.
Orchestration#
- AWS Step Functions — State machines for coordinating Lambda functions.
- Temporal — Durable execution for complex workflows (self-hosted or cloud).
Anti-Patterns to Avoid#
1. The Monolithic Lambda#
Cramming your entire API into one function defeats the purpose. Each function should do one thing.
// Bad: monolithic handler
export const handler = async (event) => {
switch (event.path) {
case "/users": return handleUsers(event);
case "/orders": return handleOrders(event);
case "/products": return handleProducts(event);
// 50 more routes...
}
};
// Good: one function per concern
// functions/getUser.js
// functions/createOrder.js
// functions/listProducts.js
2. Recursive Invocations#
A Lambda that triggers itself creates an infinite loop and a massive bill.
// DANGEROUS: this will run forever and drain your budget
export const handler = async (event) => {
await processItem(event);
await lambda.invoke({
FunctionName: "thisFunction", // recursive call
Payload: JSON.stringify(nextEvent),
}).promise();
};
Always set concurrency limits and use dead-letter queues as circuit breakers.
3. Ignoring Timeouts#
Lambda has a 15-minute maximum. If your task might exceed that, use Step Functions or break it into smaller units.
4. Over-Coupling to a Provider#
Using provider-specific SDKs everywhere makes migration painful. Isolate cloud-specific code behind interfaces when possible.
Architecture Decision Framework#
Ask these questions before going serverless:
- Is the workload event-driven? Serverless excels at request/response and event processing.
- Are execution times short? Under 15 minutes, ideally under 30 seconds.
- Is traffic unpredictable? Serverless handles spikes without pre-provisioning.
- Can you tolerate cold starts? If not, consider provisioned concurrency or containers.
- Is the team small? Serverless reduces ops burden, letting small teams ship faster.
Getting Started#
A minimal serverless API with Vercel Functions:
// api/hello.ts
export default function handler(req, res) {
const { name } = req.query;
res.status(200).json({ message: `Hello, ${name || "world"}` });
}
Deploy with vercel --prod. No Dockerfiles, no Kubernetes manifests, no nginx configs.
Serverless architecture is not a silver bullet, but it is a powerful default for many workloads. Start with functions for new APIs, use containers for what doesn't fit, and measure costs as you scale.
Build smarter systems with less infrastructure. Explore more at codelit.io.
141 articles on system design at codelit.io/blog.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
Related articles
Try these templates
Netflix Video Streaming Architecture
Global video streaming platform with adaptive bitrate, CDN distribution, and recommendation engine.
10 componentsSearch Engine Architecture
Web-scale search with crawling, indexing, ranking, and sub-second query serving.
8 componentsGoogle Search Engine Architecture
Web-scale search with crawling, indexing, PageRank, query processing, ads, and knowledge graph.
10 componentsBuild this architecture
Generate an interactive Serverless Architecture Patterns in seconds.
Try it in Codelit →
Comments