The release went out this morning. The crash dashboard just turned red — a bug in the new build, live, on users' phones, with your name on it. iOS gives you more options than most people realize, but they're scattered across App Store Connect, your own architecture, and third-party tooling, and they differ wildly in how fast they actually reach users.
The short version: pause the phased release in App Store Connect, fix server-side or flip a kill switch if you can, push an OTA code update if an SDK was already integrated, or submit a new build with expedited review — then raise a minimum-version gate once the fix is live.
Here's the full menu, fastest first, with the hard limits of each.
First, stop the bleeding: pause the phased release in App Store Connect
If you released with phased release enabled (you should, always — it's a checkbox), App Store Connect has been rolling your update out to automatic-updaters over 7 days: 1%, 2%, 5%, 10%, 20%, 50%, then 100%. The moment you suspect a bad build, pause it (you can pause for up to 30 days total, across any number of pauses).
Two things pausing does not do, and the gap matters:
It doesn't take the build back from anyone who already has it. There's no recall on iOS.
It doesn't stop manual updates — a user who opens your App Store page and taps Update gets the new build even while paused.
So a paused rollout caps the blast radius at whatever percentage you'd reached, plus manual updaters. At 1% on day one, that's a great outcome. At day six, much less so.
Two more blunt instruments live in the same console: if the build is actively harmful (data loss, a security hole), Remove from Sale stops all new downloads — at the cost of vanishing from the store entirely.
Can you roll back to the previous App Store version? No
The App Store has no rollback. You can't revert users to an earlier build; the closest move is re-submitting the old code under a new, higher version number — which goes through review and the same adoption curve as any fix.
Kill switches and remote config: fast, if you built them
If the broken code path is behind a feature flag or driven by remote config, you can turn it off without a release — every device picks up the change at its next config refresh (seconds on a streaming SDK, up to hours on default polling setups). This is the strongest argument for flagging risky launches before they ship.
The constraint is structural: flags can only select among code that already shipped. A flag can disable the new checkout flow; it cannot fix the null-dereference inside it — unless "off" maps to a complete alternate path you also shipped. If the bug is in code with no fallback path, config can't save you. (The tradeoffs deserve their own post: feature flags vs OTA updates.)
Related and underrated: if the bug is really a data or contract problem — the app mishandles a malformed API response, a bad config value, an edge-case payload — you may be able to fix it server-side without touching the client at all. Always ask "can the backend make this input impossible?" before reaching for any client-side option.
Expedited review: hours to a day, if Apple agrees
For the fix itself you'll usually submit a new build. Standard review is fast these days — Apple says that on average 90% of submissions are reviewed in less than 24 hours — but when you're watching a crash graph, you can also request an expedited review through App Store Connect support: describe the critical bug, link the evidence, submit.
What to know:
It's granted case by case, for genuinely critical issues — crashes, security problems, broken core functionality. There's no SLA, but turnarounds of a few hours to a day are commonly reported.
Apple explicitly asks developers not to lean on it routinely. Treat it as a fire extinguisher; teams that expedite every release find their requests declined.
Release the fix immediately and to everyone — don't phase a hotfix.
The adoption problem nobody budgets for
Here's the part the playbooks skip: even after the fixed build is live, your users don't have it. Automatic updates roll out over days; some users have auto-update off; some devices update only on Wi-Fi and power. Real-world adoption typically takes a week or more to reach most of your active users, and a long tail never updates at all.
Forcing users to update: the minimum-version gate
The standard countermeasure: the app checks a minimum-version endpoint at launch and, below the floor, shows an un-dismissable "please update" screen. It works, and every serious app should have one wired in before it's needed — but understand what it does: it converts "users silently running broken code" into "users staring at a wall instead of your app." That's a trade, not a fix. Use the floor for builds that are dangerous (data corruption, security), not merely embarrassing. Its one real superpower: once the fixed build is live, the gate covers every code path, including the native ones nothing else on this list can touch.
OTA code updates: minutes, within limits
The fourth lane is over-the-air code updates — shipping the corrected code directly to installed apps, no store round-trip. React Native has EAS Update (where most teams landed after CodePush retired in 2025), Flutter has Shorebird, and more recently the same pattern arrived for native Swift (Patch). All of them rest on the same underlying bargain — code executed by an interpreter shipped inside the signed binary, the pattern Apple's rules conditionally permit (the mechanics, the rules) — even though the runtimes differ (JS bundles, patched Dart snapshots, WebAssembly).
They also share the same hard limits: code that touches native OS APIs can't be patched this way, and compliance with Apple's conditions stays the developer's responsibility. That responsibility is not theoretical — Apple has enforced this boundary before, most visibly the 2017 hot-patching crackdown that ended JSPatch and Rollout — which is why the linked posts are worth reading before adopting this lane, not after.
Within those limits, the operational profile is different from everything above: the fix reaches the eligible fleet at next app launch rather than over a multi-day adoption curve, and these tools generally support staged rollouts and rollback (push to a small percentage, watch the crash rate, then widen — or pull it back), so the broken store build stops mattering for the code paths the patch covers.
The playbook, on one table
| Option | Time to users | Coverage | Prerequisite |
|---|---|---|---|
| Pause phased release | immediate | stops new automatic updates only | released with phased rollout |
| Remove from Sale | immediate | stops all new downloads (and sales) | none — but you vanish from the store |
| Server-side data/contract fix | minutes | every affected user | bug is server-influenceable |
| Kill switch / remote config | next config refresh | every user, flagged paths only | flag existed before the bug |
| OTA code update | next app launch | eligible (non-OS-API) code paths | SDK integrated before the bug |
| Expedited review + new build | hours–days, then adoption | everyone, eventually | Apple grants it |
| Forced-update gate | next launch, once the fix is live | everyone who launches (all code paths) | version-floor check shipped earlier |
The uncomfortable pattern in that table: every fast option requires a decision you made before the incident. Phased release is a checkbox at submission. Flags, OTA SDKs, and version floors have to be in the shipped binary already. The day the dashboard turns red, you're choosing from whatever you set up in calmer times — which makes the real playbook a checklist for release day, not incident day: phase every release, flag every risky path, wire a version floor, and decide deliberately whether an OTA lane belongs in your stack.
Related: Feature flags vs OTA code updates · How OTA updates work on iOS