Software Engineering·July 21, 2026·7 min read

Scalability Patterns

Scalability is a system's ability to handle growing load without a rewrite. This guide explains vertical vs horizontal scaling, statelessness, caching, queues, and when to reach for each pattern.

scalabilityscalingarchitectureengineering

Scalability is a system's ability to handle growing load — more users, more data, more requests — by adding resources rather than rewriting everything. The goal is that when you give the system more capacity, it gets meaningfully faster or handles more traffic.

If Cartara flagged this in your diff, you likely added caching, a queue, database optimisation, or changed how your app handles concurrent requests.


Scalability vs. Performance

These are different:

  • Performance is how fast one request is
  • Scalability is whether you stay fast as load grows

You can have one without the other. A query might be fast for one user but completely fall over under 100 simultaneous users.


Vertical vs. Horizontal Scaling

Vertical scaling (scale up) — give one machine more CPU or RAM. Simple, often just a dashboard slider, no code changes. Gets you surprisingly far. Limited by the biggest machine available, and a single machine is still a single point of failure.

Horizontal scaling (scale out) — add more machines and spread load across them. Near-unlimited headroom and built-in redundancy, but requires your app to be stateless (see below).

Reach for vertical first — it's free of complexity. Move to horizontal when you hit the ceiling or need redundancy.


The Foundation: Statelessness

Horizontal scaling only works if your app servers are stateless — any server can handle any request because no important state lives in one server's memory.

Sessions, uploaded files, and cached data must live in shared services (a database, Redis, object storage) — not in the app server's memory. Once servers are stateless, a load balancer can spread traffic freely and you can add or remove servers at will.

This is the single most important architectural property for scaling.


Patterns That Remove Load

Most scaling comes from removing work from your app and database rather than just adding machines:

Caching — serve repeated reads from a fast cache (Redis, CDN) so they never reach the database. Often the single highest-leverage move. See Caching Fundamentals.

Async processing — push slow work (emails, AI calls, video processing) onto a queue for background workers, keeping user-facing requests fast. See Message Queues and Async Processing.

Database optimisation — add indexes, use connection pooling, and add read replicas before reaching for anything exotic. See Database Scaling and Performance.

CDN for static assets — let a CDN serve your JS, CSS, and images globally, so requests for those never touch your server.


What You'll See in Your Code

Scalability-related changes typically appear in diffs as:

Adding a cache layer:
```ts
// Before: database hit on every request
const user = await db.users.findById(userId);

// After: check cache first
const cached = await redis.get(user:${userId});
if (cached) return JSON.parse(cached);
const user = await db.users.findById(userId);
await redis.setex(user:${userId}, 300, JSON.stringify(user));
```

Moving work to a queue:
```ts
// Before: email blocks the response
await sendEmail(user.email, welcomeTemplate);
res.json({ success: true });

// After: enqueue and respond immediately
await queue.add('welcome-email', { userId });
res.json({ success: true });
```

Adding a database index in a migration:
```sql
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_created_at ON orders(created_at);
```


A Sane Scaling Order

  1. Make app servers stateless — the prerequisite for everything else
  2. Scale up (bigger machine) — buys time with zero complexity
  3. Add caching and CDN — remove read load from the database
  4. Move slow work to async queues — keep requests fast, absorb traffic spikes
  5. Scale out app servers behind a load balancer with autoscaling
  6. Scale the database — read replicas, then partitioning if needed
  7. Decompose (microservices, CQRS) — only when the above stops being enough

Most teams live happily around steps 1–4 for a very long time. The skill is recognising which bottleneck you actually have and applying the matching pattern — not jumping to step 7 for a problem step 3 would solve.


Don't Over-Build

The most common mistake at small scale is building for imaginary scale: microservices, sharding, and elaborate queues for an app with 50 users. Complexity has a real ongoing cost.

A well-built monolith on managed infrastructure scales remarkably far with just the basic patterns. Add complexity only when real problems force you to.


Related concepts

System Design Basics
System design is how the individual parts of your app — database, cache, queue, and APIs — fit together into something that's fast, reliable, and able to grow. This guide covers the core building blocks and the decisions that shape every system.
Rate Limiting and Throttling
Rate limiting caps how many requests a client can make in a given window. It protects against abuse, runaway costs from paid APIs, and accidental self-inflicted overload.
Load Balancing
What a load balancer does, how traffic gets distributed, and what you need to know as a builder using managed platforms.

Turn shipping into understanding

Cartara measures what your team actually learns from every AI coding session.

Join the waitlist