In an Expo app using expo-updates, why do users usually see a newly published EAS Update only on their second launch?
answer
- startup is never blocked by default
- check on cold launch, run cached bundle
- checkAutomatically defaults to ON_LOAD
- fallbackToCacheTimeout defaults to 0 ms
- downloaded update waits for next cold start
basics
~20 sBy default expo-updates checks on every cold launch (checkAutomatically: ON_LOAD) but waits 0 ms for the result (fallbackToCacheTimeout: 0), so the app starts on its current bundle, downloads the update in the background and runs it on the next cold start.
solid answer
~40 sWith the default `updates` config, `checkAutomatically` is `ON_LOAD` and `fallbackToCacheTimeout` is `0`. On a cold launch, expo-updates asks the update server for a newer update compatible with the build, but it does not wait: the app launches immediately on the newest update already on the device, or on the bundle embedded in the binary. If a new update exists it downloads in the background and is launched the next time the app is started from a killed state. Backgrounding and foregrounding does not apply it. You can make a new update run on the first launch by raising `fallbackToCacheTimeout`, at the cost of a slower start, or apply it mid-session with `fetchUpdateAsync()` plus `reloadAsync()`.
code
json · 10 lines{
"expo": {
"runtimeVersion": { "policy": "appVersion" },
"updates": {
"url": "https://u.expo.dev/your-project-id",
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0
}
}
}go deeper
Recall the default: check on cold launch, do not wait, run the downloaded update on the next cold start. Say that foregrounding the app does not apply it.
Explain the two keys, checkAutomatically and fallbackToCacheTimeout, with their defaults, and what a non-zero timeout does when the download finishes in time and when it does not.
Show you weigh adoption speed against start-up time on bad networks, and that you debug a missing update with updateId and isEmbeddedLaunch before touching timeouts.
Frame launch-time update policy as a product choice: which apps can tolerate a one-launch lag, and when an in-session prompt beats a slower start for every user.
## The default is deliberately non-blocking **expo-updates** is the Expo library inside a release build that downloads and launches **EAS Update** bundles: JavaScript and assets published over the air for builds with a matching runtime version and channel. Its default launch behaviour surprises almost everyone the first time: you publish a fix, open the app, and still see the old code. Close it fully, open it again, and the fix is there. That is by design. Waiting on the network at launch means a user on a slow connection stares at a splash screen, and the Expo docs recommend against blocking startup until the latest update arrives for exactly that reason. So the defaults favour a fast, predictable start over immediate adoption. ## What happens on a cold launch With no `updates` options beyond the URL, a **cold launch** (the process was not running) goes like this: 1. expo-updates picks the newest launchable update already stored on the device. On a fresh install that is the **embedded update**, the bundle compiled into the binary. 2. Because `checkAutomatically` is `ON_LOAD`, it asks the update server whether a newer compatible update exists. 3. Because `fallbackToCacheTimeout` is `0`, it does not wait for the answer: the app launches right away on the bundle from step 1. 4. If the server has a newer update, the manifest and assets download in the background while the user is in the app. 5. The next cold launch finds the downloaded update in local storage and runs it. Resuming the app from the background is not a launch at all, so nothing is applied there. ## The two config keys that control it Both live in the `updates` object of the app config (**app.json** or **app.config.ts**). | Key | Default | What it changes | |---|---|---| | `checkAutomatically` | `ON_LOAD` | Whether the launch-time check runs: `ON_LOAD` (every launch), `WIFI_ONLY` (launch with Wi-Fi), `ON_ERROR_RECOVERY` (only after error recovery), `NEVER` (only through the JS API) | | `fallbackToCacheTimeout` | `0` | How many milliseconds launch waits for a new update before falling back to the one already on the device | If `fallbackToCacheTimeout` is, say, `5000`, the launch waits up to five seconds. When the manifest and every required asset arrive inside that window, the new update runs on this launch. When they do not, the app launches on the cached bundle and keeps downloading in the background, so the update still runs next time. The `useUpdates()` hook exposes `isStartupProcedureRunning`, which stays `true` while that startup work outlives the timeout. ## Getting an update in front of users sooner There are three levers, each with a cost: - **Raise `fallbackToCacheTimeout`**: faster adoption, slower and less predictable starts on poor networks. - **Apply in-session**: call `checkForUpdateAsync()`, then `fetchUpdateAsync()`, then `reloadAsync()` (or show a prompt and let the user restart). - **Check later instead of at launch**: set `checkAutomatically` to `ON_ERROR_RECOVERY` or `NEVER` and drive checks from your own code at moments that suit the app. The JS API methods reject in development (`npx expo start`), so this behaviour is tested in a release build such as `npx expo run:ios --configuration Release`. ## Misreadings interviewers listen for - **"It updates when I reopen the app."** Only if reopening means a cold start. Switching back from another app resumes the same JavaScript runtime, which keeps running the bundle it launched with. - **"The first launch after install gets the latest update."** With the defaults it gets the embedded bundle; the update downloads during that session and runs on the second cold launch. - **"A longer timeout is always safer."** A long `fallbackToCacheTimeout` turns every launch on a weak network into a wait, including launches where no update exists, because the check itself has to answer first. - **"`NEVER` means updates are off."** It means the launch-time check is off; the JavaScript API still checks and downloads on demand. ## Telling which bundle is running When debugging "my update did not arrive", read the constants on the `expo-updates` module: `Updates.updateId` (the running update's UUID, `null` when expo-updates is disabled), `Updates.isEmbeddedLaunch` (`true` when the binary's own bundle is running) and `Updates.createdAt`. A build still reporting `isEmbeddedLaunch: true` after two cold launches usually never matched the update at all, which is a channel or runtime-version question rather than a launch-timing one.
- What would you change so an urgent fix runs on the first launch after it is published?Raise `fallbackToCacheTimeout` to a few seconds, so launch waits for the manifest and assets and runs the new update if they arrive in time; otherwise the app still starts on the cached bundle. The alternative is to keep a non-blocking start and call `checkForUpdateAsync()`, `fetchUpdateAsync()` and `reloadAsync()` early in the session, which applies the fix without slowing every launch.
- Does setting checkAutomatically to NEVER stop the app from ever receiving updates?No. `NEVER` only switches off the automatic check at launch. `checkForUpdateAsync()` and `fetchUpdateAsync()` still work from JavaScript, so the app receives updates exactly when your code asks for them. Error recovery also skips its own check under `NEVER`, which is one reason teams prefer `ON_ERROR_RECOVERY` when they take over checking manually.
A newspaper delivered to the doorstep after you have left for work: it arrived today, but you read it tomorrow morning. Asking the carrier to wait at the door (a timeout) gets it to you today, at the price of leaving late.
saying these in an interview costs you the question
- Believes an update is applied when the app returns from the background
- Thinks expo-updates blocks the splash screen until the latest update downloads
- Expects the update to appear during npx expo start in development
- Says checkAutomatically NEVER makes the build unable to receive any update
- Thinks expo-updates checks the server every time the app is foregrounded