App is slow. Team says they don't know why. That's almost never true. There are five things that cause 80 percent of app slowness, and checking them takes an hour.
I've watched this happen many times. Something changes, or nothing changes, and users start complaining that the app feels heavy. The team investigates for a week. They upgrade the server. It gets slightly better. Then it gets slow again. The real cause was never found.
The problem isn't that the issue is hard to find. The problem is that most teams skip straight to fixes before they diagnose.
The Five Causes of a Slow App
1. Slow database queries
This is the most common one. Your database is doing too much work because the code isn't helping it. Two specific patterns show up constantly.
The first is missing indexes. If you're filtering or sorting by a column that has no index, the database scans the entire table every time. A table with 10,000 rows barely notices. A table with 2 million rows turns that query into a 4-second wait.
The second is the N+1 problem. The code fetches a list of records, then loops through the list and fires one query per record to get related data. Twenty posts becomes twenty-one queries. A thousand user records becomes a thousand and one queries. The page hangs.
How to check: turn on query logging and load your slowest page in development. Count the queries. If you see the same query repeating with different IDs, that's N+1. If you see one or two queries taking multiple seconds, that's a missing index.
2. No caching
Some data doesn't change between requests. A product catalogue. A list of categories. A dashboard summary that updates once an hour. If your app is recalculating or re-querying that data on every single request, you're doing unnecessary work at scale.
Pages that could serve a cached response are being fully regenerated each time. The database is being hit for data that hasn't changed. The server is doing real computation for a result that already exists.
How to check: look at your response times versus your database query times. If the query is fast but the response is slow, the bottleneck is computation that could be cached. If the response is slow because the query is slow, go back to the database first.
3. Large, unoptimized assets
A 2MB image being served at full resolution to a mobile screen. A JavaScript bundle that includes every library the app has ever used, loaded upfront. CSS that's twice as large as it needs to be because nobody cleaned it up.
These don't show up in server metrics. They show up in the user's browser. The server responds fast. The page still feels slow because the browser is waiting for 4MB of assets before it can render anything.
How to check: run Lighthouse on your slowest pages. Look at the asset sizes, the time to first contentful paint, and the largest contentful paint. Lighthouse will tell you exactly which assets are the problem and how much each one is costing you.
4. Synchronous operations that should be async
The app sends a welcome email during the signup request. The user waits while the mail server responds. The app processes an uploaded file inline, holding the request open while it does. The user sees a spinner for eight seconds on an action that should feel instant.
These operations don't need to happen before the response. They need to happen eventually. Putting them in a background job or a queue takes the time cost out of the user's experience entirely.
How to check: look at every operation inside a request and ask whether the user needs it to complete before they get a response. If the answer is no, it shouldn't be synchronous. Sending emails, processing files, sending notifications, generating reports: almost all of these can run in the background.
5. Hosting that's too small or misconfigured
The server is running out of memory. The app and the database are on the same small instance, competing for resources. There aren't enough worker processes to handle concurrent requests, so they queue up.
This one is real, but it's the last thing to check, not the first. More hardware masks the other four problems without fixing them. You'll spend money and buy yourself a few more months before the same wall appears on a bigger server.
How to check: look at server metrics during your slowest periods. Memory usage, CPU, disk I/O, connection counts. If you're hitting limits, that's genuine capacity pressure. But only act on it after you've ruled out the other four causes.
The Right Order to Diagnose
Profile one slow request end-to-end before you change anything.
Pick the page or action users complain about most. Load it with logging turned up. Watch what happens: how many queries fire, how long each one takes, what the server is doing during the slow period. Write it down.
Then check database query counts and times. This is where the problem lives more often than anywhere else. If you find an N+1 or a missing index, fix that first and measure again. The improvement is usually dramatic enough that you don't need to do anything else.
Then check server resource usage during slow periods. Not idle times. Actual peak load. If the server is fine, the problem is in the application layer. If the server is maxed out, you have a capacity question, but also probably an application question underneath it.
Only then start fixing. And fix one thing at a time. If you change three things at once and the app gets faster, you don't know what worked. You'll make the same mistake again on the next project.
What Not to Do
Don't throw hardware at it before you diagnose. Bigger servers feel like action. They are not a diagnosis. They are an expensive way to defer the real fix.
Don't rewrite before profiling. I've watched teams decide that the codebase is the problem and spend three months rewriting it, only to reproduce the exact same performance issues in clean code. The issue was never the code structure. It was an unindexed column and a missing cache layer.
Slow apps are diagnosable. The question is whether you want to diagnose or whether you want to feel like you're doing something.
Don't fix symptoms without finding the root cause. A page is slow. You add a spinner so it feels less painful. Users still wait. You've improved the experience of a broken thing, not fixed the broken thing.
Find the root cause. Fix that. Everything else is noise.
Here's a Real Example
Here's a real example. A client came to us with "mysterious" slowness. The app had been live for a year. Some pages took 6 to 8 seconds. The team had already upgraded the server once. It didn't help much.
We turned on query logging and loaded the slowest page. One column used in a filter on every page load had no index. The database was scanning the entire table on every request. The fix took 20 minutes: one migration, one index, done.
Response times dropped from 7 seconds to under 400 milliseconds. The server upgrade they had already paid for became unnecessary. The problem had been sitting there, undiagnosed, for a year.
That's what skipping the diagnostic step does.
When the Problem Is Deeper
Sometimes the performance issues are a symptom of something bigger. An architecture that made sense at launch but doesn't fit how the app is being used now. A data model that worked for a thousand users but falls apart at fifty thousand. Technical debt that's accumulated to the point where every change is expensive and every new load adds strain.
When the five checks above don't fully explain the slowness, or when fixing them surfaces deeper structural issues, that's when a proper audit is the right next step. Our software rescue and scale work is built exactly for this: diagnosing what's actually wrong, fixing what can be fixed quickly, and planning what needs to be rebuilt properly.
Most apps don't need a full rescue. But every app needs someone to look at the right things first.
Common questions
How slow is too slow?
If a page takes more than 2 seconds to load, users notice. If it takes more than 3, a significant percentage leave. For APIs and internal tools the bar is tighter: under 500ms is good, under 200ms is where you want to be for anything user-facing. Anything over 1 second on an API call is a flag worth investigating.
What is the fastest fix for a slow app?
Adding a missing database index. It takes minutes to add and can cut query times from seconds to milliseconds. After that, caching frequently read data that almost never changes is the next highest-leverage fix. Neither requires a rewrite.
Should I upgrade my hosting or fix the code first?
Fix the code first. Bigger hardware makes a slow app faster, but it doesn’t fix the underlying problem. You’ll hit the same ceiling again, just on a more expensive server. Profile the app, find the actual bottleneck, fix that. Only upgrade hosting if profiling shows you’re genuinely resource-constrained and the code is already clean.
What tool should I use to profile a slow app?
Start with what you already have. Most frameworks include query logging and request timing out of the box. For frontend assets, Lighthouse in Chrome DevTools is free and takes 30 seconds to run. For backend profiling, tools like New Relic, Datadog, or even simple slow-query logs in your database can surface the problem quickly. You don’t need a paid tool to find the issue in most cases.
Can a slow app be fixed without a rewrite?
Almost always. Missing indexes, no caching, unoptimized assets, blocking operations, and undersized hosting are all fixable without changing the architecture. A rewrite is rarely the answer to a performance problem. It’s expensive, risky, and usually reproduces the same issues in new code.
How do I check if my database queries are the problem?
Turn on query logging and load your slowest page in development. Count the number of queries that fire and how long each one takes. If you see dozens of similar queries for a single page load, you have an N+1 problem. If you see one or two queries taking 2 or 3 seconds, you have a missing index. Both are findable in under an hour.
App slow and you’re not sure where to start?
At Asteroid Studio, we diagnose before we fix. Every engagement starts with a clear picture of what’s actually wrong, not a guess.
See the rescue and scale service