What Breaks First When a Bunch of People Use My App at Once?
6 min read
If a lot of people suddenly used your app at the same time, the first things to break are usually third-party rate limits and costs, then shared state, then the database, not your server "running out of power." Most vibe-coded apps fall over long before they run out of compute, because the failure is a limit someone else set, not a machine you can make bigger.
Why this happens
You built and tested with one user: you. One user makes one request at a time, waits for it, and moves on. A crowd does not. Fifty people hit the same endpoint in the same second, each one triggering an OpenAI call, a database write, and an email. The code is identical. The conditions are not.
An AI agent builds what you asked for, which was "make it work." It does not add the things you did not ask for: a queue, a per-user rate limit, a spending cap, a retry. Those are invisible in a demo and essential in a crowd, so they are exactly what is missing.
What fails first, in order
- Third-party rate limits. OpenAI, Resend, Twilio, and Stripe all cap requests per minute. Hit the cap and calls start returning errors for everyone, not just the person over the line.
- Your bill. One abusive or looping user can run your OpenAI or SMS spend into real money in minutes. There is usually no cap stopping them.
- Shared or global state. A variable defined once at the top of a file, reused across requests, gets overwritten when two users arrive together. This is why one person sometimes sees another person's data.
- Database connections. Serverless functions each open their own connection. A burst can exhaust the pool, and every query starts timing out at once.
- Slow queries. A query that is fine for 10 rows crawls at 100,000, and under load the slow ones stack up and block the fast ones.
How to check
- Read your third-party limits. In each provider's dashboard, find the requests-per-minute cap. Divide it by how many calls one user makes. That is roughly how many simultaneous users it takes to trip it.
- Look for a spending cap. In your OpenAI and Twilio accounts, check for a hard monthly limit. If there is none, you have no floor under your bill.
- Grep for shared state. Search your server code for module-level
letvariables that a request handler writes to. Anything mutable and global is a collision waiting to happen. - Load-test one endpoint. Run a simple burst against your busiest route:
npx autocannon -c 50 -d 20 https://your-app.com/api/your-route
Watch for a spike in errors and response time. That is your first crack.
The fix
- Add a per-user rate limit on any route that calls a paid API. Cap it well below your provider's limit so one user can never consume everyone's budget.
- Set a hard spending cap in every provider that offers one. Treat it as a circuit breaker, not a suggestion.
- Remove shared mutable state. Move per-request data inside the handler. Nothing that changes should live at module scope.
- Use a connection pooler. On Supabase, point serverless functions at the pooled connection string, not the direct one.
- Retry with backoff on rate-limited calls so a brief spike degrades instead of erroring out.
The trap to avoid
Do not reach for a bigger server. Under a crowd, the bottleneck is almost never your CPU. Upgrading the instance costs money and fixes nothing, because the wall you hit is a rate limit, a spending cap, or a shared variable. More power does not raise someone else's limit.
Where this fits
"Works for me" and "works for a crowd" are different claims, and the gap between them is where launch-day incidents live. The free Readiness Report runs the checks above against your real project and tells you which limit you hit first. If you would rather have the caps, pooling, and rate limits put in for you in order, that is the Finishing Pass. It pairs well with why does my app break for other people and demo works but not launch ready.