All posts
serverlessarchitecture

Serverless Architecture Patterns: A Practical Guide

A practical guide to serverless architecture patterns — when functions-as-a-service fit, cold starts, and common design patterns.

SR

Suhail Roushan

August 6, 2026

·
4 min read
·
0 views

"Serverless" is a misleading name — there are still servers, you just don't manage them, and that tradeoff (giving up control over the runtime environment in exchange for not operating infrastructure) shapes which architectural patterns actually fit well and which fight the model.

Serverless architecture runs code in ephemeral, event-triggered functions managed entirely by the cloud provider — you deploy code, the platform handles provisioning, scaling, and infrastructure, and you're billed based on actual invocations and execution time rather than provisioned capacity. This fits well for event-driven, bursty, or infrequent workloads, and less well for long-running, stateful, or latency-critical processes.

Why Serverless Matters (and When to Skip It)

For workloads with variable or unpredictable traffic, serverless scales automatically without capacity planning, and you pay only for actual usage rather than idle provisioned servers — this economic and operational model fits particularly well for infrequent background jobs, webhook handlers, and bursty API traffic.

Skip serverless for consistently high-traffic services where the per-invocation cost model becomes more expensive than a provisioned server, or for workloads with strict low-latency requirements where cold starts are unacceptable, or for long-running processes that exceed platform execution time limits.

Getting Started with Serverless Architecture

A typical serverless function handling an event trigger:

export async function handler(event: S3Event) {
  const { bucket, key } = event.Records[0].s3;
  const image = await downloadFromS3(bucket, key);
  const thumbnail = await generateThumbnail(image);
  await uploadToS3(bucket, `thumbnails/${key}`, thumbnail);
}

Fan-out pattern — one event triggering parallel independent processing:

export async function handler(event: SQSEvent) {
  await Promise.all(
    event.Records.map((record) => processMessage(JSON.parse(record.body)))
  );
}

Core Serverless Architecture Concepts Every Developer Should Know

Cold starts are a real, measurable latency cost for infrequently invoked functions. When a function hasn't run recently, the platform needs to provision a fresh execution environment before running your code — this adds latency (from tens of milliseconds to a few seconds depending on runtime and package size) that's invisible for "warm" functions but very visible for latency-sensitive, infrequent invocations.

Functions should be stateless between invocations, since you can't rely on in-memory state persisting from one invocation to the next (the platform may reuse a warm instance or spin up a fresh one). Any state that needs to persist belongs in an external store (a database, cache, object storage), not in function-local memory.

Event-driven triggers are the natural fit for serverless, since functions execute in response to events (HTTP requests, queue messages, storage events, scheduled triggers) rather than running continuously — architecting around this event-driven model, rather than trying to force a long-running-process pattern into functions, is core to using serverless well.

Execution time and resource limits vary by platform and matter for architecture decisions. Most serverless platforms impose a maximum execution duration and memory/package size limits — workloads that would exceed these need to be redesigned (broken into smaller steps, moved to a different compute model) rather than fought against.

Common Serverless Architecture Mistakes and How to Fix Them

Mistake 1: treating a function as if it has persistent in-memory state across invocations. This works unreliably (only when the platform happens to reuse a warm instance) and breaks unpredictably in production. Fix: store any state that needs to persist in an external store, treating each invocation as independent.

Mistake 2: using serverless for consistently high-traffic services where the economics don't favor it. Per-invocation billing can become more expensive than provisioned capacity at sustained high volume. Fix: model actual cost at expected traffic levels before committing, and consider a hybrid approach (serverless for bursty/infrequent, provisioned for steady high-volume) where it fits better.

Mistake 3: not accounting for cold start latency in latency-sensitive paths. Fix: use provisioned concurrency (keeping a set number of instances warm) for latency-critical functions, or reconsider whether serverless is the right fit for that specific path.

When Should You Use Serverless Instead of a Provisioned Server?

Use serverless for event-driven, bursty, or infrequent workloads — webhook handlers, background jobs, scheduled tasks, and APIs with unpredictable or spiky traffic — where automatic scaling and pay-per-use economics are a genuine fit. Use a provisioned server (or container-based deployment) for consistently high-traffic services, latency-critical paths sensitive to cold starts, or long-running processes that don't fit within serverless execution limits.

Serverless Architecture in Production

Design functions to be stateless and idempotent from the start, since retries and concurrent invocations are a normal part of the serverless execution model, not an edge case. Also monitor cold start frequency and latency for user-facing functions specifically, using provisioned concurrency where the cost is justified by the latency requirement.

If you have background jobs or webhook handlers currently running on always-on provisioned servers mostly sitting idle, that's a clear case where migrating to serverless would better match cost to actual usage.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch