Skip to content

7. Launch and startup

The question. What happens between the tap on the icon and the first useful screen of this app?

Launch is the one path every user takes in every use of the app. Five of the seven constraints (Chapter 2) press on it at once.

Human attention sets the budget. A user who taps an icon is waiting with nothing else to look at. Every hundred milliseconds of blank screen is noticed.

Process death sets the frequency. The operating system ends background processes freely, so a large share of launches start from nothing. The slowest path is also a common one.

Unreliable network sets the hardest rule. At launch the app knows the least it will ever know: it has not spoken to the backend, and it may not be able to. Any startup decision that needs a response is a decision that can hang.

Slow release adds the questions the app must ask before it shows anything. A version shipped two years ago still launches today. The backend may no longer support it, or may want it to behave differently, and the only moment to say so is startup.

Limited resources make the work compete. Everything the app prepares at launch runs on the same processor, in the same seconds, as the drawing of the first screen.

The result is a concern with an unusual property. The user sees every stage of it. A launch plays out as a sequence of visible states, and the order and duration of those states expose most of the design.

The central design choice is what the user sees first and where its data comes from. There are five workable strategies. They run from simplest to most capable, and each later one needs more machinery on the device.

Blocking fetch behind a splash. The app holds a splash screen until the backend has answered, then shows a complete screen. Nothing on screen is ever wrong or out of date. The cost is that launch time equals network time, and with no network the app cannot get past the splash. Teams choose this when stale data is unacceptable, when the app is small, or when nobody has revisited the first version of the startup code.

Skeleton placeholders. The app draws its real layout at once, with gray shapes or spinners where content will appear, and fills them in as responses arrive. Launch feels faster because the first frame comes early, although the content arrives no sooner. The user can already reach tabs and menus. The cost is modest: each screen needs a loading state. With no network the skeleton never resolves, or turns into an error.

Cached content first, then refresh. The app shows the content it stored on the previous run and refreshes it in the background. This is stale-while-revalidate applied to launch (Chapter 11). First content arrives at local-storage speed, and launch works offline. The cost is a cache that survives process death, and a screen that may change under the user’s eyes when the refresh lands.

Persisted last screen. The app reopens where the user left it, with that screen’s data and position, instead of at its home screen. This needs everything the previous strategy needs, plus stored navigation state (Chapter 8). Teams choose it for apps in which the user’s place is valuable: a reader, a document editor, a long conversation.

Prepared before launch. The app refreshes its local store while it is not on screen, prompted by a push hint or a background task the operating system schedules (Chapter 23). When the user launches, the stored content is already current, so the cached screen and the fresh screen are the same screen. This is the most capable design and the most expensive, because it spends battery and data on launches that may not happen, under platform rules the team does not control.

StrategyFirst frame showsTime to first contentLaunch with no networkMain riskRelative cost
Blocking fetch behind a splashSplashOne or more round tripsStuck or errorHangs on a bad networkLowest
Skeleton placeholdersLayout without dataOne or more round tripsEmpty layout or errorFeels fast, is notLow
Cached content firstStored contentLocal readWorks, content is oldContent shifts on refreshMedium
Persisted last screenThe screen the user leftLocal readWorks, content is oldRestores a screen that no longer makes senseHigh
Prepared before launchCurrent contentLocal readWorks, content is recentBackground budget wasted or refusedHighest

Strategies belong to screens, and they combine. An app commonly holds a short splash while it reads the session, shows a cached feed, and puts a skeleton in the one section that must be fresh, such as a balance. Record the strategy for the first screen and note the exceptions.

What the user calls “opening the app” is one of three different events.

KindWhat exists beforehandWhat the app must doWhat you see
Cold startNo processEverything in Figure 7.2Splash or placeholder, then content
Warm startA live process whose screens were released or are out of dateRebuild or refresh screens. No initializationNo splash. Content reloads or updates
ResumeA live process with its screens intactNothing, or a quiet refreshThe exact screen you left, at once

The glossary defines a warm start as any return to a live process. This chapter splits off the cheapest case and calls it a resume, because the two look different and reveal different things.

You cannot see whether a process exists. You can only infer it from what the launch shows. A splash means a cold start. The absence of a splash means a live process. This matters for every probe in this chapter: before you time a cold start, make sure it is one, by force-quitting the app or restarting the device.

Figure 7.1 shows the parts that take part in a cold start. The startup coordinator is the piece of domain logic that decides which screen comes first. In a small app it is a few lines in the entry point. In a large one it is a dependency graph of initialization tasks.

Figure 7.1. The components of a cold start, drawn for the cached-content-first strategy.

The figure shows the network as a dotted line, after the first screen. That is the property that separates the strategies in Section 7.2. In a blocking design the same arrow is solid and sits before the first screen.

A cold start passes through five phases. Each ends at a moment the user can see.

  1. Process creation. The operating system creates the process and loads the app’s code. The app’s own code is not running yet. The operating system draws a placeholder so that the tap gets an immediate response. The team controls this phase only indirectly, through the size of the app and the amount of code it links.
  2. App initialization. The app’s own setup runs: it builds its object graph, opens the local store, and starts the libraries it depends on. This is the phase teams most often let grow, because each addition is small.
  3. First frame. The app draws something of its own. Depending on the strategy this is a splash, a skeleton or real content.
  4. First content. Real data is visible. In a blocking design, first frame and first content are far apart. In a cached design they are almost the same moment.
  5. Interactive. The app responds to touch. This can trail first content, because work postponed from the earlier phases is now running on the thread that handles input.

Figure 7.2. A cold start that shows cached content first.

From outside, you can separate four moments with nothing more than attention: the placeholder appears, the app’s own drawing replaces it, real content appears, and the first touch is honored. The boundary between the operating system’s placeholder and the app’s own splash is usually invisible, because teams design the two to match.

Before the first screen, the startup coordinator answers four questions. The design of each answer decides whether launch depends on the network.

function chooseFirstScreen(launch):
session = localStore.readSession() // answerable with no network
rules = localStore.readCachedConfig() // last known version rules and flags
refreshConfigInBackground() // result applies later, see text
if rules.minimumVersion > appVersion: return UpdateGate
if rules.maintenance: return MaintenanceScreen
if session is none: return SignIn(pendingLink = launch.link)
if launch.link exists: return route(launch.link)
if savedScreen exists and savedScreen.age < restoreLimit:
return savedScreen
return Home

The client stores a credential from the last sign-in. Reading it is a local operation. Checking that the backend still accepts it is a network operation. There are two designs.

  • Trust, then verify. The app treats a stored credential as a session and shows signed-in screens at once. If the backend later refuses the credential, the app sends the user to sign-in at that moment. Launch does not wait.
  • Verify, then show. The app holds the splash until the backend confirms the session. Launch waits on the network, and with no network the app either blocks or falls back to the first design.

Chapter 20 covers sessions in full. For this chapter the point is narrow: a launch in airplane mode shows which design the app uses.

The backend can tell an app that its version is too old, or that the service is unavailable. Section 7.6 covers these gates. The startup question is where the rule comes from at launch: a cached copy of the last answer, or a fresh request.

Remote config decides which features exist in this launch. The app must choose between showing the right features and showing something soon. Three designs exist.

DesignHow it worksVisible behavior
Fetch, then startHold the splash until config arrives, usually with a timeoutLaunch time follows network speed. A timeout shows as a splash of fixed maximum length on a bad network
Cached, applied on the next launchStart with the values fetched last time, fetch new ones in the background, and use them next timeLaunch is fast. A change reaches the user one launch late
Applied liveStart with cached values and switch when new ones arriveLaunch is fast. A section or control can appear, move or vanish seconds after launch

The first launch after install has no cached config. An app of the second or third kind uses defaults shipped inside the app, so a feature may be absent on the first launch and present on the second. Chapter 30 covers remote config in depth.

The landing screen depends on how the launch began and on what the app remembers.

  • A launch from the icon lands on the home screen, or on the last screen if the app persists it. Many apps apply a time limit: after a short absence they restore the last screen, and after a long one they return to home, on the reasoning that the old context no longer matters to the user.
  • A launch from a notification or a link lands on the target screen. The startup coordinator must carry the target through the session check and any gate. A simpler design starts the app normally and navigates afterwards, which shows as a flash of the home screen before the target.
  • A first launch after install lands on onboarding or sign-in, or on browsable content if the app supports use without an account.

Everything done before the first frame delays it. A startup design therefore sorts each piece of setup into one of three groups.

GroupWhen it runsTypical contents
Before first frameDuring app initializationCrash reporting, the session read, the cached config read, whatever the first screen draws with
DeferredAfter first frame, without being askedAnalytics, push registration, the persistent connection, warming of caches, cleanup of old files
LazyOn first use of a featurePayment, maps, media playback, the camera, rarely visited sections

Both postponements leave marks you can see.

Deferred work competes with the first seconds of use. The first content is on screen while the deferred group runs. If the first scroll after a cold start stutters and the same scroll ten seconds later is smooth, deferred work is running on the thread that draws. If taps are ignored for a moment after content appears, the interactive phase is trailing first content.

Lazy work makes the first use slow once. Open a heavy feature after a cold start, leave it, and open it again. A delay on the first opening that is absent on the second, and that returns after a force-quit, is lazy initialization. A delay that is absent after a force-quit and present only after a fresh install is a one-time download or setup, which is a different thing.

Some work happens before the tap. The operating system may keep a process alive after a notification arrives, or start one for a scheduled background task. A launch from the icon then finds a live process and is a warm start, although the user did nothing to cause it. This is why a launch after a device restart is the cleanest cold start you can produce: nothing has had the chance to run.

A gate is a screen that replaces the app until a condition changes. Gates exist because of slow release. The team cannot recall a version, so it ships every version with a way to be told to stop.

The forced-update gate. The backend publishes a minimum supported version. A client below it shows a screen with one action, a link to the store. A softer form shows a dismissible prompt and lets the user continue.

The maintenance screen. The backend states that the service, or a part of it, is unavailable. The client shows a message instead of a stream of errors. The same mechanism serves as a kill switch for a single feature.

Two design choices decide how a gate behaves.

Where the rule is checked. A rule checked only at startup arrives with config. A rule checked on every response arrives as a specific error that any request can return, and the gate can then appear in the middle of use. The second design is more robust, because it also catches a process that has stayed alive for days.

What happens with no network. The app cannot learn a new rule offline. If a gate appears in airplane mode, the rule was stored on the device on an earlier launch. If an app that was gated online opens normally offline, the rule is not stored, and the gate is only as strong as the network connection. For apps that work offline this is a real gap, and some teams accept it.

A gate also has a failure of its own. A blocking gate with a wrong rule locks every user out, and a release cannot fix it, because a release takes days. A team that understands this serves the rule from a system separate from the one it guards. You cannot see that from outside, so any claim about it is Speculative.

7.7 Startup with no network, and after an update

Section titled “7.7 Startup with no network, and after an update”

A cold start in airplane mode has four possible results, and each one states a design.

ResultWhat it tells you
The normal first screen, with contentSession, config and content are all read from the device. Nothing at startup blocks on the network
The app’s layout, with an error or empty sectionsStartup decisions are local. Content is not cached, or only held in memory
The splash never ends, or a full-screen error with a retry controlAt least one startup step blocks on a response
The sign-in screenThe session check needs the network and treats no answer as no session

The fourth result is a defect in most products, since the user is signed in and has lost access to cached content because of a missing connection. It is still common. The offline levels of Chapter 12 describe what the app can do once it is open. This probe asks the earlier question of whether it opens at all.

A new version finds a local store written by the old one. It has three options.

  • Migrate. Convert the stored data to the new shape. A small migration is invisible. A large one shows as a single long launch, sometimes with a message such as “updating” or “optimizing”.
  • Discard and refetch. Delete the local data and load it again from the backend. This shows as an empty app that refills, or as a full initial load, and it tells you the backend holds everything the device held.
  • Fail. A migration that throws an error on some users’ data produces an app that closes at every launch until it is reinstalled. Reinstalling works because it removes the old data.

One confound applies. On some platforms the operating system itself does extra work on the first launch of new code, so a slightly slower first launch after an update is not evidence of a migration. A visible message, a progress indicator or a delay of many seconds is.

Run P7.1 to P7.3 on every app. They take two minutes together and settle the first-screen strategy. The later probes need time, a notification or an update, so run them when the chance arises. Select the outcome you saw in each probe, and it is recorded in your worksheet for the teardown chosen here.

Open worksheet
Do
  1. Force-quit the app.
  2. Launch it from its icon and count the seconds until real content is visible.
  3. Go to the device's home screen, wait five seconds, and return to the app.
  4. Repeat both steps twice, so that one slow network response does not mislead you.
Watch

Whether a splash appears on each launch, and how long each takes by eye. A splash marks a cold start.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Force-quit the app.
  2. Launch it on a normal connection and do not touch the screen.
  3. Repeat on a slow connection if you can find one.
Watch

What stands between the splash and real content, and whether the content changes after it first appears.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Use the app online for a minute, so that it has had the chance to store something.
  2. Force-quit it.
  3. Turn on airplane mode.
  4. Launch the app.
Watch

How far the launch gets, and what the first screen contains.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Navigate several screens deep, away from the app's home screen.
  2. Force-quit the app and launch it again at once. Note the screen you land on.
  3. Navigate deep again and go to the device's home screen without force-quitting.
  4. Leave the app unopened until the next day, then launch it. Note the screen you land on.
Watch

Whether you land on the app's home screen or on the screen you left, in each case.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Force-quit the app.
  2. Cause a notification that points at one item, for example by sending yourself a message from a second account.
  3. Tap the notification.
Watch

Which screens appear, in order, before the target is shown.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Run P7.1 first, so that you know the ordinary cold start time.
  2. Restart the device.
  3. Wait one minute after unlocking, then launch the app.
Watch

How the time to real content compares with the ordinary cold start.

Caution. Restarting the device closes every app. Save your work in other apps first.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Install the app on a device that does not have it.
  2. Launch it and wait before touching anything.
  3. Continue to the first screen with real content, then force-quit and launch again.
Watch

What the first launch waits for, whether content can be seen before signing in, and whether anything differs on the second launch.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Leave the app unopened for at least a week. An app you rarely use is a ready-made subject.
  2. Launch it on a normal connection.
  3. Compare with the launch in P7.2.
Watch

Whether old content appears first, whether the launch waits longer than usual, and whether the app asks you to do anything.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Wait until the store offers an update for the app.
  2. Use the app once, so that it has recent local data.
  3. Install the update and launch the app.
Watch

Whether the launch takes longer than usual, whether a message about updating appears, and whether your content and session are still there.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Find a device on which the app has not been updated for a long time. A spare device with automatic updates turned off is ideal.
  2. Launch the app online.
  3. If a screen about updating appears, turn on airplane mode, force-quit and launch again.
Watch

Whether the app works, suggests an update, or refuses to continue. In airplane mode, whether the refusal is still there.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

SignalDesign it points toConfidence
A splash appears on launchCold start. No process existedObserved
The splash lasts longer on a slow connectionA startup step blocks on a responseInferred
The splash has the same maximum length on any bad connectionBlocking fetch with a timeout and a fallback to stored valuesSpeculative
The layout appears with gray placeholders that fill inSkeleton placeholders with fetch after first frameObserved
Content appears at once and changes a moment laterCached content first with a background refreshInferred
The app opens on the screen you left after a force-quitLast screen persisted by the app itselfInferred
The last screen returns after a short absence, home after a long onePersisted last screen with a time limitInferred
A cold start in airplane mode shows the normal first screenSession, config and content are read from the deviceInferred
A cold start in airplane mode shows the sign-in screenSession validated against the backend before the first screenInferred
The sign-in screen flashes before the home screenFirst frame drawn before the session read completesInferred
The home screen flashes before a notification's targetRouting runs after a normal startup instead of inside itObserved
The first scroll after launch stutters and later scrolls are smoothDeferred initialization running on the drawing threadSpeculative
A feature is slow to open the first time after each force-quit, and fast after thatLazy initialization of that featureInferred
A tab or section appears on the second launch after installRemote config cached and applied on the next launchInferred
A section appears or vanishes seconds after launchRemote config applied liveInferred
An update screen appears in the middle of useVersion rule checked on responses and not only at startupInferred
A message about updating or optimizing on the first launch after an updateLocal store migrated in placeInferred
The app closes at every launch after an update until reinstalledFailed migration of the local storeSpeculative

Search the signals from every chapter in the signal lookup.

Each client design needs something specific from the backend. Once you have placed the app, the following can be Inferred.

  • Blocking fetch. One or more endpoints that the first screen cannot do without. An app with a short, steady splash on a good network usually has a single startup request that returns session validity, config and landing data together. A splash that varies widely suggests several requests in sequence. You cannot count requests from outside, so the number is Speculative.
  • Cached content first. Read endpoints whose responses are safe to show when old, and a cheap way to ask whether stored content is still current (Chapter 11).
  • Prepared before launch. A way to wake the client when content changes, by push hint, and endpoints cheap enough to call on launches that never happen (Chapter 14 and Chapter 23).
  • Remote config. A config service that answers by app version, platform and user, and that answers fast, since it sits on the launch path for at least some designs.
  • Gates. A published minimum version for each platform, and either a config value or a dedicated error that every endpoint can return. A maintenance screen that appears while the rest of the backend is failing implies that the status is served from somewhere that does not share the failure.
  • Launch from a notification. A notification that carries the target, and endpoints that load one item by its ID without the screens that normally lead to it (Chapter 8).

The gateway sees every one of these requests at the same moment for every user who opens the app, for example after a widely delivered notification. A startup design that makes five requests before first content multiplies that peak by five. This is a reason backend teams push for a single startup request, and a reason client teams resist it, because one slow field then delays everything (Chapter 10).

Startup bugs are easy to notice, because they happen in front of a waiting user.

  • A splash that never ends on a weak connection. A blocking fetch with no timeout, or a timeout longer than your patience.
  • A flash of the sign-in screen before the home screen. The first frame is drawn before the session has been read. The session read is asynchronous and the screen did not wait for it.
  • A flash of the home screen before a notification’s target. The app starts normally and navigates afterwards. The startup coordinator does not carry the launch target.
  • Content that jumps a second after it appears. Cached content was replaced by a refresh whose layout differs. The design is cached-first with no reserved space for late elements.
  • A tab or section that appears on the second launch after install. Config is cached and applied on the next launch, and the first launch ran on built-in defaults.
  • A feature that vanishes while you are looking at it. Config is applied live.
  • An update gate that appears over a screen you were already using. The rule is checked on responses, not only at startup.
  • An ignored first tap. Deferred initialization is running on the thread that handles input.
  • The app closes at every launch after an update, until reinstalled. A failed migration of the local store.
  • Signed out after an update. The stored credential did not survive the change, either through a deliberate reset or a storage migration that lost it.
  • Yesterday’s content with no sign of loading, for a long time. Cached-first with a refresh that failed silently. The app has no indicator for a failed refresh.

These are the fields this chapter adds to the teardown worksheet (Chapter 6). Probe outcomes fill some of them in.

Open worksheet
  • A launch is a cold start, a warm start or a resume. A splash is the visible mark of a cold start. Force-quit or restart the device before timing one.
  • A cold start has five phases: process creation, app initialization, first frame, first content and interactive. You can separate first frame, first content and interactive by eye.
  • The first-screen strategy is a blocking fetch, skeleton placeholders, cached content first, a persisted last screen, or content prepared before launch. The position of the network relative to the first screen separates them.
  • Before the first screen the app decides whether there is a session, which version rules apply, which config is in force and where to land. Each decision is either local or blocks on the network.
  • A cold start in airplane mode is the most informative single probe. It shows which startup decisions need a response.
  • Deferred work shows as a rough first few seconds. Lazy work shows as a slow first use of a feature in each process.
  • Gates exist because a release cannot be recalled. Where the rule is checked, and whether it holds offline, are both observable.
  • The first launch after an update shows whether the local store is migrated or discarded.