How One Simple Change Made My App 15x Faster
3.5min read / Real advice from someone who made their app way faster
When we first launched the app, we were on fire.
It was a food ordering app for a fast-food chain, and everything was working. The design looked slick, and the backend functioned well.
We even reached number one in the food category on the iOS App Store.
There was a lot to celebrate.
... But the celebration didn’t last long.
Soon after launch, I got a message. It wasn’t a bug report or a pat on the back. It was a customer review.
“Why the f*** is this app so slow?”
That one stung.
1. When Reuse Becomes a Trap
At first, I didn’t believe it. Slow? The API worked. The endpoints were returning data. Everything was wired up properly. So I checked the logs.
And there it was.
Some of our endpoints were taking more than 8 seconds to return.
And not just occasionally ... frequently.
And if you’ve ever worked on a food product where speed matters, you know that 8 seconds is a ton. No one wants to wait that long to see a menu, especially not hungry people deciding where to eat.
How We Were Fetching Data
On paper, the job seemed easy:
Build a single API endpoint that, given a restaurant ID, returns all the information the frontend needs—menu items, prices, images, hours, and location.
The catch?
Parts of the backend were powered by 3rd party microservices. Each piece of data came from a different third-party API:
/restaurant-info → opening hours, location, name
/menu → categories, items, prices
/images → URLs for each dish
Every time someone opened the app, the backend had to make three separate HTTP requests, combine the data, and return it to the frontend.
Round 1: Optimize the Calls
My gut reaction was classic engineer panic mode: optimize everything.
I added connection pooling to our HTTP client so it wouldn’t waste time opening and closing new TCP connections. I parallelized the API calls so we weren’t waiting on one request to finish before starting the next.
And it helped. A little.
We reduced the response times to approximately 4 seconds.
But it was still inconsistent. Sometimes users got lucky. Other times, not so much. The core issue remained: we were still dependent on external services, and their response times varied wildly.
When they slowed down, so did we.
Round 2: Preload Everything
So, I tried something more “engineer-y.”
I wrote a cron job that ran every 10 minutes.
It would loop through all the restaurants, hit the third-party APIs in advance, and store the data locally in our own database. This way, when users requested the data, we could serve it instantly without touching the remote services.
At first, it felt brilliant. It was proactive. Mature. Like something you’d hear about in a conference talk.
But it backfired.
The third-party APIs changed throughout the day. Breakfast menus switched to lunch menus. The structure of the data sometimes shifted depending on the time of day.
And because we were preloading that data on a schedule, we now had stale data showing up at the worst possible times, right when users were trying to place orders.
I had solved one problem by creating another. Instead of slow responses, we now had incorrect ones.
At this point, I felt like I had tried everything. I had optimized the hell out of the requests. I had written background sync jobs.
But every “fix” just introduced more complexity or risked new issues.
Round 3: The Real Fix
That’s when it hit me. The answer wasn’t to make the requests faster or preload them smarter.
It was to avoid making the requests altogether.
I didn’t need to ask for the same data again and again. I just needed to remember it.
So I reached for Redis.
Redis is an in-memory data store that makes caching ridiculously fast.
I set it up so that on the first request of the day, the backend would perform all the usual work, calling the APIs, combining the responses, and building the whole object the frontend needed.
Then, instead of discarding that data, it saved the whole thing in Redis.
On every future request, the backend just checked Redis. If the data was available, it was returned instantly. If not, it fell back to the API calls and re-cached the result.
To maintain freshness, I added a TTL (Time to Live) of 8 hours. That way, we’d refresh the cache a couple of times per day, right around when menus were most likely to change.
The Result
The change was small. Embarrassingly small compared to the effort I had spent on all the other fixes.
But the impact was massive.
Our average response time dropped from 4 seconds to just 533 milliseconds.
The complaints stopped.
Whew.
What I Learned
I spent weeks chasing a big solution, thinking the fix had to be elaborate or clever. This is usually a red flag, meaning you are overthinking the solution.
The lesson here is that before you add more code, more features, or more complexity.
See if you can avoid doing the work at all.
Another week down. You all are amazing!
Cheers friends,
Eric Roby
Find me online: