Serverless Architecture Patterns
Serverless computing runs your code without you managing servers — the provider handles provisioning, scaling, and patching, and you pay only for the compute you actually use. It's a great fit for event-driven, spiky, or unpredictable workloads, but designing well for it means thinking differently: small focused functions, loose coupling through events, and an eye on cold starts and cost.
This guide covers the patterns that hold up in production. The recurring theme: functions are composable building blocks, and your architecture is mostly about how events flow between them.
TL;DR
- Keep functions small and single-responsibility.
- Couple services through events, not direct calls.
- Design for cold starts — initialize outside the handler, warm critical paths.
- Watch cost at scale — right-size memory and execution time.
Quick Example
A focused handler does one thing — parse, validate, save — and nothing else:
Core Concepts
Serverless characteristics
- No server management — the provider runs the infrastructure.
- Pay per execution — billed for compute time (and memory) used.
- Auto-scaling — scales to zero when idle and to thousands under load.
- Event-driven — functions are triggered by events, not long-running processes.
Common triggers
- HTTP requests (API Gateway, function URLs)
- Message queues (SQS, Pub/Sub)
- Storage events (S3, GCS object created)
- Schedules (cron)
- Database changes (DynamoDB Streams, Firestore triggers)
Cold Starts
A "cold start" is the latency penalty when the platform spins up a new execution environment. Mitigate it by doing expensive setup once, outside the handler, and keeping critical paths warm:
Event-Driven Architecture
Decouple services by publishing events rather than calling each other directly. Producers don't know or care who consumes:
Common Patterns
Fan-out
One event triggers many independent consumers, processing in parallel:
Aggregator
Combine results from several functions into one response:
Orchestration (Step Functions)
For multi-step workflows with branching and error handling, use a state machine instead of chaining functions manually:
Cost Considerations
Cost scales with invocations × duration × memory, so right-sizing matters — and more memory can be cheaper when it makes functions finish faster:
Other levers: batch processing where possible, minimize cold starts, and use reserved/provisioned capacity for predictable, steady loads.
Best Practices
- One responsibility per function — easier to test, scale, and reason about.
- Initialize outside the handler (DB clients, SDK config) to survive across warm invocations.
- Couple through events for independent scaling and failure isolation.
- Make handlers idempotent — events can be delivered more than once.
- Right-size memory and measure duration; the cheapest config is often not the smallest.
Common Mistakes
One function doing everything
Heavy setup inside the handler
FAQ
What is a cold start and how bad is it?
It's the extra latency when the platform creates a fresh execution environment for your function (loading the runtime and your code). It ranges from tens of milliseconds to a few seconds depending on runtime and package size. Mitigate with module-level initialization, smaller deployment packages, and provisioned concurrency for latency-sensitive paths.
When should I NOT use serverless?
Long-running jobs (beyond the platform timeout), workloads needing persistent connections or custom networking, very high steady-state traffic (where reserved capacity is cheaper), and anything requiring strict, low-latency database connection pooling. For those, containers or VMs often fit better — see Cloud Computing.
How do I handle database connections from serverless?
Connection exhaustion is the classic trap: thousands of concurrent functions each opening a connection overwhelms the database. Use a connection proxy/pooler (RDS Proxy, PgBouncer, serverless drivers), keep clients initialized at module scope, and cap concurrency. See Database Replication and Caching.
Should I orchestrate functions in code or with a state machine?
For simple sequential calls, code is fine. For multi-step workflows with retries, branching, parallelism, and human-readable status, use an orchestration service (Step Functions, Cloud Workflows). It externalizes the control flow, gives you visibility, and handles failure transitions declaratively.
Related Topics
- AWS Lambda — AWS serverless functions
- Google Cloud Run — Container-based serverless
- Cloudflare Workers — Edge functions
- Message Queues — Event-driven backbone
- Microservices — Service decomposition