# Vibe Coding Architecture: Matching Your App to Its User Count

*Published 2026-10-01 · Vibe Coding, Architecture, Scaling, AI*

> When you vibe code, the agent picks your architecture for you — usually wrong. How to match structure to your real user count, keep the app stateless, and scale by measured bottleneck.

Source: https://www.yashthakur.com/blog/architecture-by-user-scale

---

**The short answer:** when you vibe code, the AI agent silently picks your architecture — and it gets scale wrong in both directions. So this is the first thing to keep in mind while vibe coding: architecture should match the user count you actually have, not the one the agent imagines. For most apps that's a single structured monolith plus a managed database — it handles thousands of users fine. You scale by *measured* bottleneck, not by milestone: add instances behind a load balancer, add a cache, add read replicas, and only split into services when the pain is organizational, not technical. Over-architecting for scale you don't have is the most expensive mistake a vibe-coded project makes.

This is the one an AI agent gets wrong in both directions. Ask for "a scalable app" and it'll hand you Kubernetes, a message queue, and five services for your to-do list. Ask without the word "scalable" and it'll store sessions in a local variable that evaporates the moment you run a second instance. Neither is right. Scale is a dial, and you have to set it on purpose.

## The tiers, honestly

Here's how I think about it, bucketed by rough concurrent-user scale. These numbers are deliberately fuzzy — your mileage depends entirely on what each request does — but the *shape* of each tier is the point.

| Scale | Shape that fits | What to avoid |
|-------|-----------------|---------------|
| **~0–1k users** | One app server (or serverless functions) + one managed database. Monolith. Deploy to a PaaS. | Microservices, self-hosted databases, Kubernetes, anything with "orchestration" in the name |
| **~1k–50k users** | Same monolith, now **stateless** and run as multiple instances behind a load balancer. Managed DB with a read replica. Redis for sessions + cache. CDN for static assets. | Splitting into services; premature sharding |
| **~50k–500k users** | Horizontal app tier, DB read replicas, aggressive caching, background jobs moved to a queue + workers, CDN everywhere. | Rewriting into microservices unless team structure demands it |
| **500k+ / many teams** | Now service boundaries can earn their keep — but driven by *team autonomy*, not traffic. Partitioned/sharded data, dedicated caching tiers, observability as a first-class concern. | Treating this tier's patterns as the starting template for tier 1 |

The single most important line here: **you move between tiers by measuring a bottleneck, not by hitting a user number.** A simple CRUD app can serve 50k users from a tier-1 setup; a chat app with live presence might strain a tier-2 setup at 5k. The user count tells you roughly where to *look*; the metrics tell you what to actually do.

## The non-negotiable that makes every later tier possible

If you do exactly one thing right at tier 1, make your app **stateless**. No in-memory sessions, no "this counter lives in a module variable," no uploaded files written to the local disk. Everything that must persist goes to the database, the cache, or object storage.

```TS
// ❌ Breaks the moment you run a second instance.
// Instance A sets it; the load balancer sends the next request to B, which has no idea.
const sessions = new Map<string, User>();

// ✅ State lives in a shared store every instance can reach.
// Now you can run 1 instance or 50 — the app doesn't care.
await redis.set(`session:${id}`, JSON.stringify(user), "EX", 3600);
```

A stateless app is one you scale by changing a number ("run 4 instances instead of 1"). A stateful one requires a rewrite. This single decision is the difference between tier 1 being a dead end and being a launchpad — and an AI agent will almost always generate the stateful version unless you tell it not to.

## Scale the database before you scale the app

When an app gets slow, the instinct is "add more servers." Usually wrong. App servers are cheap, stateless, and trivial to duplicate. The database is the part that's hard to scale and almost always the real ceiling. In order:

1. **Add the missing indexes.** The single highest-leverage fix. An unindexed query that table-scans is the most common "the app got slow at scale" cause there is.
2. **Kill N+1 queries.** The agent-generated loop that runs one query per item will murder you at scale. Batch it.
3. **Add a read replica** and send reads to it. Most apps are read-heavy.
4. **Cache expensive reads** in Redis with a sane TTL.
5. *Then*, if the app tier is genuinely the bottleneck, add instances.

This is the same measure-first discipline I apply to [finding the root cause of React performance problems](/blog/react-performance-root-cause) — profile to find where the time actually goes, change one thing, verify it moved. Guessing at scale is as wasteful as guessing at a slow render.

## When microservices actually earn it

Almost never because of traffic. A monolith running 50 instances serves an enormous amount of load. You split into services when **multiple teams need to deploy independently** and the shared monolith has become a coordination bottleneck — a release train everyone's stuck on. That's an org-chart problem wearing an architecture costume, and I wrote about exactly this trap in [micro-frontends: when they help and when they hurt](/blog/micro-frontends). The same logic applies on the backend: start as a modular monolith with hard internal seams, and *those seams* are where you split later — if you ever need to.

## Use agents and skills deliberately

- **An API / interface-design skill** when you're drawing module or service boundaries — getting the contract stable early is what makes a future split a refactor instead of a rewrite.
- **A performance-optimization skill** aimed specifically at your database layer (indexes, query plans, N+1 detection), since that's where the real scaling ceiling lives.
- **A backend-architecture-patterns skill** to sanity-check that the tier your agent proposed matches your actual numbers — ask it directly: "what breaks in this design at 100x the users?"

Point one specialized agent at the question "what's the first bottleneck here under real load?" before you accept a generalist agent's architecture. It'll flag the in-memory state and the unindexed query the generalist happily shipped.

Right-sizing architecture is the highest-leverage decision in a vibe-coded project because it's the hardest to undo. Build for the scale you have with a clear migration path to the next tier, keep the app stateless, and scale the database first. That beats a tier-4 cathedral serving twelve users every single time.

This is one of four vibe-coding deep-dives — the others cover [choosing a backend](/blog/how-to-choose-a-backend), [security and networking](/blog/security-networking-precautions), and [the frontend](/blog/frontend-development-precautions), all gathered in [the vibe-coding precautions overview](/blog/vibe-coding-precautions).
