All posts Mobile Development

Release Management on Mobile: Staged Rollout, Rollback and Forced Updates

On the web you roll back a bad deploy; on mobile you can’t — the version on the user’s phone is out of your hands. Here is the release management setup I built around that fact.

DT
Demir Taşdemir Mobile App & Web Developer
— min read

When you ship a bad deploy on the web, you know exactly what to do: you go back to the previous version, and within a few minutes everyone sees the fixed one. On mobile there is no such button. The version on the user’s phone stays there until that user decides to update. Even if you pull it from the store, nothing changes for the people who already downloaded it.

I learned this for the first time during a live incident: a change I made on the server side didn’t match a piece of parsing code in an older version, and one screen stopped working for everyone on that version. Fixing the app and shipping a new version took four days — app store review included. For those four days, the only thing I could do was revert the server side to its old behavior.

Since that day, the setup I use rests on a single assumption: every version I release will keep running in the field for at least another year.

Staged rollout: showing the bug to a few people instead of everyone

On Google Play you can start a release at one percent of users and increase it step by step. On the App Store side there is a phased release option spread over seven days. I now use both without exception.

The rhythm I follow looks like this:

StageShareWhat I look for
Day 11–5%Is the crash rate on par with the previous version
Day 210%Has a new crash signature appeared, what does the support inbox say
Days 3–425–50%Does it hold up on different devices and OS versions
Day 5+100%If nothing is wrong, I open it up to everyone

The critical part is this: for a staged rollout to mean anything, you have to actually look during those stages. Leaving it at one percent and pushing it to a hundred the next day is not a staged rollout — it is just a delayed release.

Read the crash-free rate relatively, not absolutely

Is “99.2% crash-free” good or bad? On its own it means nothing. What means something is the comparison with the previous version’s value at the same number of days in. If the new version is lower than the old one, something is broken no matter what the number says. That comparison is what my decision to halt a rollout is based on.

Server-side switches: the decisions I keep out of the app

Because there is no rollback button on mobile, I move every decision that needs to be reversible outside the app. In practice that means a small configuration object I fetch from the server at app launch:

{
  "asgariSurum": "3.2.0",
  "onerilenSurum": "3.6.1",
  "bakimModu": false,
  "bakimMesaji": "",
  "ozellikler": {
    "yeniOdemeAkisi": true,
    "haritaKumelemesi": true,
    "sohbet": false
  },
  "apiTabani": "https://api.ornek.com/v3"
}

What this object gives me:

  • I can turn a new feature off. If something goes wrong in the payment flow, I fall back to the old flow without waiting for a new release.
  • I can announce a maintenance window. If there is planned downtime on the backend, the user sees an explanatory message instead of an error screen.
  • I can change the endpoint address. When the server moves, the app doesn’t need an update.

Two things matter when fetching this configuration: I cache the response locally (so the app doesn’t refuse to open if the request fails) and I bake the default values into the app itself. A user who can’t reach the server on first launch should see an app running on sensible defaults, not a blank screen.

Forced updates: necessary but dangerous

The asgariSurum field above is the app’s “stop running anything older than this” threshold. If the user’s version is below it, I show a screen that can’t be dismissed and send them to the store.

But this tool is very easy to abuse. Throwing the user out of the app is the harshest intervention you have. The rule I set for myself: forced updates only in cases where the old version cannot possibly keep working. That is:

  • a security hole has been closed,
  • the backend contract changed in a backward-incompatible way,
  • the old version is corrupting data.

“Let them see the new design” is not a reason for a forced update. The onerilenSurum field exists for that: I show a dismissible, polite reminder and the user can continue whenever they want.

Think about the forced-update screen offline too

If you tie the version check solely to the server’s response and never define what happens when that request fails, you end up with one of two bad outcomes: you block everyone, or you block no one. My preference is not to block when the request fails — throwing a user out of the app because they lost their connection is worse than letting them keep going a while longer on an older version.

The backend contract: not breaking older versions

Most of the bugs that hurt me the most on mobile were actually “small” changes made on the server side. After I set these three rules, that class of problem went away:

  • I don’t delete fields, I only add them. If I need to remove a field, I first stop using it, and delete it once the versions in the field have dropped low enough.
  • I don’t change a field’s type. Turning a numeric field into a string can go as far as a crash in an older parser. If I need to, I add a new field under a new name.
  • If a breaking change is unavoidable, I open a new version path. /v3 stays live while /v4 goes up; apps migrate at their own pace.

I also play defense on the client: if the incoming JSON has a field I didn’t expect, I ignore it; if a field I expected is missing, I fall back to the default. Strict parsing means fragility on mobile.

Version numbering and release notes

I keep the version number meaningful — major change, feature, fix, in that order. The real benefit isn’t for the user, it’s for me: when a message lands in the support inbox saying “it doesn’t work on 3.4.2,” I can find out in a second when that version shipped and what was in it.

I stopped writing “bug fixes and improvements” in release notes. When a user reads that note, they should understand what changed. Being concrete has one more unexpected benefit: I myself remember what I fixed by the time the next release comes around.

Pre-release checklist

I go through the same list for every release. It’s boring, but a single forgotten item can cost you a four-day delay:

  • Have the version and build numbers been incremented
  • Was it compiled with the release configuration — are debug logs off
  • Do the endpoint addresses point at production
  • If a new permission was added, has its description text been written
  • Was it tested by updating from the previous version (not a clean install — the update path)
  • If the local database schema changed, was the migration tested
  • Have the release notes been written
  • Is the staged rollout enabled

The most frequently skipped item on this list is the fifth. A version that works on a clean install can crash when it lands on top of old data — and almost every user in the field arrives through the update path.

Summary

All of mobile release management fits in a single sentence: you are shipping something you can’t take back, so keep everything that is reversible outside of it. A staged rollout limits how many people a bug reaches, server-side switches let you turn that bug off without waiting for a new release, and a backward-compatible backend contract keeps older versions from breaking because of you. With all three in place, release day stops being a stressful day.

  • Mobile Development
  • Release Process
  • App Store
  • Google Play
  • Architecture
Share: LinkedIn X WhatsApp
DT

Demir Taşdemir

Mobile App & Web Developer

I have been building software since 2018. I have published 11 apps on the App Store and Google Play; right now I am working on 6 mobile apps, 1 e-commerce platform and 1 desktop game.

Does your app’s release process keep you up at night?

Setting up staged rollout, remote switches and forced updates is a one-time job, but it changes a lot afterwards. We can review your current process together — you can write to me from the contact page.