What is Serverless Architecture?
Serverless architecture allows you to build and run applications without managing servers. The cloud provider automatically provisions, scales, and manages the infrastructure. You write functions that execute in response to events and pay only for the compute time you consume. Despite the name, servers still exist — you just do not manage them.
Serverless Components
- FaaS (Functions as a Service): AWS Lambda, Azure Functions, Google Cloud Functions — run code in response to events
- BaaS (Backend as a Service): Managed databases (DynamoDB), auth (Cognito), storage (S3)
- API Gateway: AWS API Gateway, Azure API Management — HTTP endpoints that trigger functions
- Event Sources: S3 uploads, SQS messages, DynamoDB streams, scheduled events (cron)
Lambda Function Patterns
// AWS Lambda handler for API Gateway
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
export const createOrder = async (
event: APIGatewayProxyEvent
): Promise => {
try {
const body = JSON.parse(event.body || "{}");
const userId = event.requestContext.authorizer?.claims?.sub;
if (!userId) {
return { statusCode: 401, body: JSON.stringify({ error: "Unauthorized" }) };
}
const order = await orderService.create({
userId,
items: body.items,
shippingAddress: body.shippingAddress,
});
return {
statusCode: 201,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(order),
};
} catch (error) {
console.error("Create order error:", error);
return {
statusCode: 500,
body: JSON.stringify({ error: "Internal server error" }),
};
}
};
// Lambda handler for SQS events
import { SQSEvent } from "aws-lambda";
export const processOrderQueue = async (event: SQSEvent): Promise => {
for (const record of event.Records) {
const order = JSON.parse(record.body);
try {
await fulfillmentService.processOrder(order);
} catch (error) {
console.error(`Failed to process order ${order.id}:`, error);
throw error; // SQS will retry or send to DLQ
}
}
};
// Lambda handler for S3 events
import { S3Event } from "aws-lambda";
export const processUpload = async (event: S3Event): Promise => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
console.log(`Processing upload: s3://${bucket}/${key}`);
await imageProcessor.resize(bucket, key, [800, 400, 200]);
}
};
Cold Starts and Optimization
Cold starts occur when a serverless function is invoked after being idle. The platform must provision a new container, load the runtime, and initialize your code. This can add 100ms to several seconds of latency. Mitigation strategies include keeping functions warm, minimizing bundle size, and using provisioned concurrency.
// Optimization: Initialize outside the handler (reused across invocations)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
// These are initialized ONCE per container (warm start reuses them)
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
// Handler runs on every invocation
export const handler = async (event: APIGatewayProxyEvent) => {
// docClient is already initialized — no cold start penalty here
const result = await docClient.send(/* ... */);
return { statusCode: 200, body: JSON.stringify(result) };
};
// Optimization: Lazy initialization for rarely-used dependencies
let heavyDependency: HeavyLibrary | null = null;
function getHeavyDependency(): HeavyLibrary {
if (!heavyDependency) {
heavyDependency = new HeavyLibrary(); // Only initialized when needed
}
return heavyDependency;
}
When to Use Serverless
| Good Fit | Poor Fit |
|---|---|
| Event-driven processing (file uploads, webhooks) | Long-running processes (>15 min) |
| Sporadic or unpredictable traffic | Constant high-throughput workloads |
| REST APIs with variable load | WebSocket or long-lived connections |
| Scheduled tasks (cron jobs) | Applications needing local state |
| MVPs and prototypes | Latency-sensitive applications |
Serverless Anti-Patterns
- Lambda monolith: Putting your entire application into one function — defeats the purpose of serverless
- Synchronous chains: Lambda calling Lambda calling Lambda — use queues (SQS) or Step Functions
- Ignoring cold starts: Not accounting for initialization time in latency-sensitive paths
- Vendor lock-in: Deeply coupling to provider-specific APIs — use abstraction layers where practical