"Serverless" is a lie (a useful one)
Every time I say "serverless" at a family dinner, my cousin asks: "So where does the code run? In the air?"
No. It runs on servers. Very real ones, humming in an AWS data center. The difference is you never see them. You don't pick an instance size, you don't patch Linux at 2 AM, and you don't pay for a machine that sits idle while everyone is asleep.
You give AWS a function. AWS promises: "Whenever someone needs this, I'll run it. However many times. You pay for what you use."
Meet the cast
Think of a serverless app like a restaurant where the staff only shows up when a customer walks in.
- Lambda: the cook. Your code, packaged as a function. It wakes up, does one job, and goes back to sleep.
- API Gateway: the front door and the receptionist. Turns an HTTPS request into a Lambda call.
- DynamoDB: the fridge. A database that scales without you thinking about servers.
- S3: the storage room. Files, images, backups, anything.
- SQS and EventBridge: the order slips. Messages that say "something happened, someone deal with it."
- Step Functions: the head chef with a checklist, for workflows with many steps.
How the pieces fit
Most serverless apps are a mix of two shapes: a classic request/response API, and an event-driven pipeline where one thing happening triggers the next.
In the top row, a user loads their orders. API Gateway receives the request, Lambda runs getOrders, reads DynamoDB, and returns JSON. Nobody provisioned a server.
In the bottom row, a user uploads a photo. S3 emits an event, EventBridge routes it, a Lambda makes a thumbnail, and an email job gets queued. Nobody even clicked anything after the upload. The events do the walking.
Here is what that getOrders Lambda can look like. That's the whole "server":
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";
const db = DynamoDBDocumentClient.from(new DynamoDBClient({})); // created once, reused while warm
export const handler = async (event: { pathParameters: { userId: string } }) => {
const { Items } = await db.send(
new QueryCommand({
TableName: "orders",
KeyConditionExpression: "userId = :u",
ExpressionAttributeValues: { ":u": event.pathParameters.userId },
}),
);
return { statusCode: 200, body: JSON.stringify(Items) };
};
The famous cold start
Serverless has one personality flaw. If your function hasn't run in a while, AWS has to get it ready first: fetch your code, start the runtime, and run your initialization. That's a cold start. After that, the function stays warm for a bit and later requests skip the setup.
Usually this is a few hundred milliseconds and nobody notices. When it matters, you can:
- Keep the package small (fewer dependencies, faster download).
- Do expensive setup outside the handler, so warm calls reuse it (like the DynamoDB client above).
- Pay for provisioned concurrency to keep some instances always warm, for latency-critical paths.
When serverless shines
- Spiky traffic. Zero users at night, ten thousand at lunch. You pay for lunch only.
- Glue and automation. "When a file lands, process it." "Every night at 2, send the report." This is serverless's happy place.
- Small teams. No ops person? No problem. Fewer things to patch and monitor.
- Prototypes. Ship on Friday, find out on Monday if anyone cares.
When it hurts
- Long jobs. A Lambda can run for at most 15 minutes. Video encoding for an hour? Use containers.
- Steady, heavy traffic. If your functions are busy 24/7, an always-on container can be cheaper.
- Chatty architectures. Twenty tiny Lambdas calling each other is not "microservices", it's a group chat that never ends. Debugging gets fun in the bad way.
- Local development. Your laptop is not AWS. Tools help, but some surprises only show up in the cloud.
A pricing gut-check
Lambda bills per request plus the compute time (memory times duration). The free tier covers 1 million requests a month, which is more than most side projects will ever see. The real bill usually comes from the stuff around it: data transfer, API Gateway, logs you forgot to expire.
Serverless won't make you a better engineer by itself. But it does give you something precious: more time to build the product, and less time babysitting machines.
