What shipping mobile apps taught me about LLM systems
Mobile engineers spent a decade learning to ship software they couldn't take back. LLM teams are relearning the same four constraints, usually the hard way.
Before any of this was about retrieval and agents, I shipped native apps — Swift and Kotlin, booking and payments, through App Store and Play review, live on both stores. Then I moved into full-stack, then into AI systems.
The odd thing about that path is how much of it repeated. Not the tooling — none of that transferred. The constraints did. Four things mobile engineering forces you to internalise are the same four things I keep watching LLM teams discover late, and the mobile version of each has a decade of answers behind it.

You cannot force an upgrade
The first thing mobile teaches you is that shipping a fix is not the same as the fix being applied. Someone is running the build from eighteen months ago. They will keep running it. Your server talks to that version whether you like it or not, so every API you ship is a contract you are still honouring long after you'd like to have moved on.
The habit that comes out of it: version everything at the boundary, never change the meaning of a field, and add rather than repurpose.
LLM systems have exactly this problem and mostly don't treat it as one. A prompt is an interface. So is a tool schema, and so is the JSON shape you promised the model would return. Change any of them and something downstream — a cached response, a stored conversation, an integration written against last quarter's output — is now operating on a contract you quietly broke. A prompt edit deployed everywhere at once is a forced upgrade, and mobile taught us those don't exist.
The mobile answer applies unchanged: version the prompt, keep the old one serving until nothing references it, and add fields rather than renaming them.
The network is not there
Mobile is where you learn that offline isn't an error state, it's Tuesday. Lifts, trains, bad signal, airplane mode, an expired token at the worst moment. You design assuming the request fails, because a meaningful share of them do — so you queue, you retry with backoff, you show the user something useful, and you make the failure survivable rather than trying to prevent it.
Then people build on a model provider's API and treat a 200 as the normal case and everything else as an exception to handle later. It isn't. Rate limits, timeouts, capacity, a model deprecated on a schedule you don't control — provider APIs fail more often than a mobile network does, and unlike a mobile network you cannot tell the user to move nearer a window.
What mobile does here that LLM systems mostly don't: decide in advance what a degraded answer looks like. Cached response, smaller model, retrieval without generation, or an honest "I can't answer that right now." The failure path is a product decision, and if you don't make it deliberately you've made it accidentally.
Latency is perceptual, not numerical
Mobile teams argue about milliseconds because they've watched users abandon a screen that looked frozen for 400ms. The lesson is not that fast is good — it's that users judge responsiveness, not duration. A skeleton screen at 100ms beats a blank one that finishes sooner.
This is why token streaming matters far more than total generation time, and why teams optimising mean end-to-end latency are often optimising the wrong number. Time to first token is the one users feel. A response that starts in 300ms and takes eight seconds reads as fast; one that appears complete after four seconds of nothing reads as broken.
Retrieval sits in front of that, which makes it the part worth budgeting hardest — the same instinct as keeping work off the main thread.
Review is a gate, and gates are useful
Every mobile engineer has had a release sit in review while a bug sat in production. It teaches an uncomfortable discipline: you cannot hotfix your way out, so you get it right before you submit, and you keep a remote kill switch for the things you can't.
LLM teams have no App Store, and so mostly have no gate. A prompt change goes straight out. That feels like an advantage right up to the first silent regression, and it's why I'd argue for an artificial one — a labelled eval set the change has to clear before it ships. Not because review is pleasant. Because a forcing function that makes you check is worth more than the freedom to skip checking.
The kill switch transfers too. Mobile calls it a feature flag; for an LLM feature it's the ability to fall back to the previous prompt, the previous model, or no AI at all, without a deploy.
The transfer is the constraints, not the code
None of the code came with me. Swift and Kotlin have nothing to say about retrieval pipelines. What transferred was a set of instincts about shipping software you can't take back — version the interface, plan the failure, optimise what's felt, keep a gate and a switch.
LLM engineering is roughly where mobile was in 2013: capable, moving quickly, and mostly deployed by people who haven't yet been burned in the specific ways the platform will burn them. The advantage of having done a previous round of that is not that you're smarter. It's that you already know which corners come back.
If you're carrying an app and an AI roadmap at the same time — I do both, and they're less separate than they look.