Engineering for the Next Billion Users
Cold starts, timeout budgets and cheap Android, from Lagos
5 min read

Most performance advice is written from a place where the network is assumed and only its speed is in question. Fast connection, slow connection, maybe a throttled profile in dev tools to represent the bad case.
That is not the environment I ship into. Here the network is a variable that goes to zero, comes back, and goes to zero again while a user is halfway through a form. Data costs real money per megabyte, which means every asset you send is a small charge to someone's balance. Devices are mid range Android with a fraction of the CPU your laptop has and a browser holding eleven other tabs.
The advice that comes out of that environment is different, and I would argue it is better for everyone, because a product that works on a bad connection also works on a good one.
The failure that is not a failure
My mobile sign up was reporting timeouts. Nothing was broken.
My hosting spins containers down when they go idle. The first request after a quiet period took twenty four seconds to wake the container and respond. The HTTP client had a twenty second timeout, so it gave up four seconds before the answer arrived and told the user the request had failed.
I had picked twenty seconds because it felt generous. I never measured. That is the whole lesson, and it is embarrassing in a useful way.
A timeout is not a preference. It is a claim about your infrastructure's worst case, and if the claim is wrong, your client manufactures failures against a backend that is working correctly.
Two changes there. The ceiling now sits above the measured cold start, and retries back off rather than hammering, because on a flaky mobile network the second attempt one second later usually fails for the same reason the first one did.
The order matters too. Measure first, then set the timeout, then treat reducing the cold start as a separate piece of work with its own priority. Raising the timeout is not a fix for slowness. It stops you lying to the user about what is happening while you deal with the actual cause.
Bytes are money
There is a category of frontend decision that is invisible in a market with unmetered data and very visible here.
A twelve megabyte bundle is not just slow. On a metered connection it is a charge, and users notice apps that cost them money. So does an autoplaying video, an uncompressed hero image, a font family loaded in six weights when the design uses two, and an analytics stack that ships more code than the feature it measures.
The discipline I hold is a budget rather than a checklist. Initial route under a couple of hundred kilobytes of compressed JavaScript, images sized to their display width and served in a modern format, fonts subset and limited, third party scripts justified individually. Every dependency that ships to the client has to earn its size, and the honest question is not whether it is useful but whether it is worth what it costs the user to download.
Rendering matters for a related reason. On a cheap device, a heavy first render is seconds of unresponsive interface. Server rendering the content-heavy screens and keeping the interactive parts small is not a framework preference here. It is the difference between a page that appears and a page that eventually appears.
Design for the network dropping mid action
The scenario that breaks products is not a slow request. It is a request that starts, and then the network disappears, and the user has no idea what happened to it.
Three things make that survivable.
The first is telling the truth about state, especially where money is involved. A timeout on a payment must say whether the transaction went through, and if that genuinely cannot be determined yet, it must say that plainly and give the user a way to check. Ambiguity here does not make users patient. It makes them retry, and retries on payment endpoints produce duplicates unless every one of those endpoints is idempotent, which is why the two topics are inseparable.
The second is retrying the right things. Reads retry freely. Writes only retry when the endpoint is idempotent, and then with backoff. That distinction has to be encoded in the client, not left to a global interceptor that treats every request the same.
The third is not losing the user's work. A form that clears itself when a submission fails is a small cruelty on a connection that fails often. Keep the input, keep the draft, let them retry into the same state they were in.
What this discipline is worth elsewhere
None of this is charity work for a difficult market. The constraints just make ordinary engineering mistakes visible faster.
An application that survives a network dropping mid request is one where the failure paths are actually implemented. An application with a byte budget is one where nobody added a dependency casually. An application whose timeouts are measured is one where somebody knows the real latency profile of their own infrastructure.
Those are properties every team claims to want. They are the ones that quietly go untested when the developer's connection is fast enough to hide their absence.
If you are shipping to markets like mine, or thinking about it, the starting point is unglamorous. Measure your actual cold start, on your actual hosting, at the time of day your users are quietest. Then open your product on a mid range phone on mobile data and try to complete the main flow.
Whatever annoys you in that ten minutes is your roadmap, and it will be more useful than any performance article, including this one.