Skip to content
Back to Blog
Performance11 min read

Serverless Architecture Patterns: 7 Designs That Scale

Seven production-tested serverless patterns—from event-driven processing to hybrid container workflows—that handle real traffic without the ops overhead.

Written by Abdul AbrorTechnical Hosting Support Engineer
Serverless Architecture Patterns: 7 Designs That Scale
On this page

Most hosting engineers I talk to want serverless for the same reason: no servers to patch at 2 AM. The promise is real—you write functions, the cloud runs them, and you pay only for execution time. But without deliberate patterns, you end up with a tangle of triggers and timeouts that nobody can debug.

I've deployed serverless stacks that handle millions of events a day and others that fell over at a few hundred requests because the pattern was wrong for the workload. Architecture matters more than the runtime. Below are seven designs that work in production, with trade-offs you need to know before you commit.

Pattern 1: Event-Driven Processing

This is the foundation. An event happens—a file lands in S3, a message hits a queue, a database row changes—and a function runs in response. No polling loops, no idle servers.

I've used this for image resizing pipelines where uploads trigger a Lambda that writes thumbnails back to S3. The function sleeps until an event arrives, so you pay nothing during quiet hours. Latency is usually under a second if the function stays warm.

The gotcha: cold starts. If a function hasn't run recently, the runtime spins up a new container and initialization can add 500ms to several seconds depending on your dependencies. For user-facing requests that's noticeable. For background jobs it's fine.

Key services: AWS Lambda with S3 events, SQS triggers, or EventBridge rules. Google Cloud Functions with Pub/Sub. Azure Functions with Event Grid.

When to use it: Background jobs, data pipelines, async workflows where a few seconds of latency is acceptable.

When to skip it: Synchronous APIs where every millisecond counts and you can't tolerate cold start variance.

Pattern 2: API Gateway + Lambda Functions

You put an API gateway in front of Lambda functions to handle HTTP requests. The gateway manages routing, throttling, auth, and SSL termination. Functions execute the business logic and return responses.

I've built REST APIs this way that serve thousands of requests per second with auto-scaling baked in. No load balancers to configure, no EC2 instances to right-size. You define routes, attach a Lambda per endpoint, and deploy.

API Gateway has two flavors: REST and HTTP. REST offers more features—request validation, caching, usage plans. HTTP is cheaper and lower latency. For most CRUD APIs, HTTP Gateway is plenty.

Cold starts hit harder here because users are waiting. Provisioned concurrency keeps functions warm at a fixed cost, trading money for consistent latency. I reserve it for endpoints that need sub-200ms p99.

Another pattern: pack multiple routes into one Lambda using a lightweight router inside the function. Reduces cold start surface and simplifies deployments, but couples code that might want independent scaling.

When to use it: RESTful services, microservices backends, mobile app APIs where traffic is spiky and you don't want to pay for idle capacity.

When to skip it: Monolithic apps where splitting into dozens of functions adds operational complexity without benefit.

Pattern 3: Scheduled Jobs (Serverless Cron)

Replace cron on a server with CloudWatch Events or EventBridge schedules that trigger Lambda. The function runs at defined intervals—every five minutes, daily at midnight, whatever you need.

I've migrated nightly backup scripts, report generators, and cleanup jobs to this pattern. No more SSH into a box to check if cron ran. Logs go to CloudWatch, failures trigger SNS alerts, and retries are configurable.

One trick: if the job takes longer than the function timeout (15 minutes max on Lambda), break it into chunks. Queue the work and have the function process a batch, then requeue remaining items. Or switch to Step Functions for orchestration.

# Example EventBridge rule (CloudFormation snippet)
ScheduledRule:
  Type: AWS::Events::Rule
  Properties:
    ScheduleExpression: "cron(0 2 * * ? *)"
    Targets:
      - Arn: !GetAtt BackupFunction.Arn
        Id: "BackupTarget"

When to use it: Periodic maintenance, data exports, metrics aggregation, any task that ran via cron.

When to skip it: Long-running batch jobs that exceed function timeouts and can't be chunked easily—consider ECS Fargate tasks instead.

Pattern 4: Stream Processing

Connect Lambda to a data stream (Kinesis, DynamoDB Streams, Kafka) and process records in small batches. Each invocation receives a batch, processes it, and checkpoints progress automatically.

I used this to replicate database changes into Elasticsearch in near real-time. DynamoDB Streams capture every insert/update/delete, Lambda transforms the record, and pushes it to the search index. Lag stays under a second most of the time.

Batch size and concurrency matter. Lambda reads from the stream in parallel shards, so throughput scales with shard count. If processing is slow, records pile up and lag grows. Tune batch size to balance latency and efficiency.

Error handling: if a function fails, Lambda retries the entire batch until success or max retries. One poison message can block the shard. Send failures to a dead-letter queue and handle them separately.

When to use it: Real-time ETL, change data capture, analytics ingestion where you need to react to every event in order.

When to skip it: When you need exactly-once semantics and can't tolerate duplicate processing—Lambda's at-least-once guarantee means retries can deliver the same batch twice.

Pattern 5: Fan-Out Workflows

One event triggers multiple independent functions in parallel. Common setup: SNS topic or EventBridge bus receives an event and fans it out to several subscribers—each a Lambda doing a different task.

Example: user signs up, one function sends a welcome email, another provisions resources, a third logs to analytics. They run concurrently and don't block each other. If email fails, provisioning still succeeds.

SNS is simple but dumb—it delivers to all subscribers with no conditional logic. EventBridge lets you route based on event content, so different event types go to different functions without branching inside code.

{
  "source": "app.users",
  "detail-type": "UserSignup",
  "detail": {
    "userId": "12345",
    "email": "[email protected]"
  }
}

Each subscriber sees the same event. If ordering matters or you need to collect results, fan-out alone isn't enough—combine it with Step Functions or a scatter-gather pattern.

When to use it: Decoupling microservices, parallel processing of independent tasks, event broadcasting.

When to skip it: When tasks must run in a specific sequence or you need to aggregate results before proceeding.

Orchestration vs. Choreography

Two ways to coordinate multi-step workflows. Orchestration uses a central controller (AWS Step Functions, Azure Durable Functions) that calls functions in sequence, handles retries, and manages state. Choreography has no central brain—functions listen to events, do their work, and emit new events that trigger the next step.

I've shipped both. Orchestration is easier to reason about because the workflow is explicit in one place. You see the DAG, the error paths, the timeouts. Step Functions visualizes execution in the console, which helps during debugging.

Choreography scales better and is more resilient. No single point of failure, and you can add steps by subscribing to events without touching existing functions. But the flow is implicit—you have to trace events across logs to understand what happened.

A hybrid works well: use Step Functions for critical paths where you need transactional guarantees, and event-driven choreography for loosely coupled background tasks.

Orchestration wins when: You need visibility, complex branching logic, human approval steps, or strict ordering.

Choreography wins when: You have many independent services, high throughput, and want loose coupling.

Pattern 7: Hybrid Serverless + Containers

Not everything fits in a 15-minute function. Some workloads—ML inference, video encoding, legacy binaries—need more CPU, memory, or runtime than Lambda provides. Run those in containers (ECS Fargate, Cloud Run, Azure Container Instances) and trigger them from serverless functions.

I've done this for PDF generation: API Gateway receives a request, Lambda validates input and writes a job to SQS, Fargate task polls the queue and generates the PDF using a heavyweight library, then uploads the result. Lambda handles the fast API layer, containers do the heavy lifting.

Another pattern: Lambda for HTTP endpoints, but offload compute to Fargate for batch jobs. You get serverless simplicity where it fits and container flexibility where it doesn't.

Cost is higher than pure Lambda but lower than keeping containers running 24/7. Fargate bills per second, so a task that runs for three minutes costs a few cents.

When to use it: Workloads that exceed Lambda limits, need specific OS dependencies, or run for extended periods.

When to skip it: Simple request-response logic that fits comfortably in Lambda's constraints—adding containers is overkill.

What About Cold Starts in Production?

They're real, but manageable. For APIs, provision concurrency on critical paths. For background jobs, accept the latency—users aren't waiting.

Language matters. Node and Python cold start faster than Java or .NET because the runtime is lighter. Keep dependencies slim and initialization lazy. I've seen functions drop from 3-second to 500ms cold starts just by trimming unused libraries.

If cold starts kill your SLA, you might need a different architecture. A small always-on container or a hybrid setup can handle latency-sensitive work while serverless handles the bursty load.

Common Mistakes I've Seen

Putting everything in one giant Lambda. You lose the scaling benefits and make deployments risky.

Ignoring timeouts and retries. Functions fail, queues fill up, and nobody notices until data is hours behind.

No dead-letter queues. Poison messages loop forever and burn through your budget.

Hardcoding secrets in environment variables. Use Secrets Manager or Parameter Store and fetch at runtime.

Skipping observability. Distributed serverless systems are hard to debug without structured logs, traces, and metrics. Set up X-Ray or equivalent from day one.

Picking the Right Pattern

Start with the workload, not the hype. If you have a REST API with unpredictable traffic, API Gateway + Lambda is a safe bet. Background jobs? Event-driven processing or scheduled functions. Real-time data? Stream processing.

Don't force everything serverless. Hybrid designs often make the most sense—functions for the control plane, containers for the heavy compute, managed databases for state.

The serverless tax is cold starts and distributed debugging. The payoff is zero infrastructure management and automatic scaling. If that trade-off works for your team and traffic, these seven patterns give you a solid foundation to build on.

FAQ

Can serverless handle high traffic?

Yes, if you design for it. Lambda scales to thousands of concurrent executions automatically. The bottleneck is usually downstream—databases, APIs, or rate limits—not the functions themselves.

How do I test serverless locally?

SAM CLI and LocalStack emulate AWS services. Frameworks like Serverless Framework support offline modes. For integration tests, I deploy to a staging account and run against real services.

What's the cost compared to EC2?

Depends on utilization. Serverless is cheaper for spiky or low-traffic workloads where you'd otherwise pay for idle capacity. For steady high traffic, reserved EC2 or containers can be cheaper. Do the math for your traffic pattern.

Should I use serverless for everything?

No. It fits event-driven, stateless workloads with variable load. Long-running processes, stateful apps, or things that need low-level OS control belong on VMs or containers.