skip to content

Development Build Clients

Expo Go ships a fixed native runtime for one SDK, so custom native code needs a development build with expo-dev-client. Interviewers ask when a team outgrows Expo Go and how the build finds Metro.

part ofExpo (React Native)overview, primer and where to startread it →
on this pageshow

explore

questions

5

In Expo, what is the difference between Expo Go and a development build, and when does a project outgrow Expo Go?

level: juniorimportance: must knowfreq 68%

answer

  1. prebuilt store app versus your own binary
  2. fixed set of native libraries
  3. one SDK version at a time
  4. expo-dev-client adds the launcher
  5. remote push, links, icons need your binary

basics

~20 s

Expo Go is a prebuilt app with a fixed set of native libraries for one SDK. A development build is your own debug binary with expo-dev-client, holding your native code and config. Outgrow Expo Go when you need native code it lacks.

solid answer

~40 s

Expo Go is a sandbox app from the stores: its native side is compiled once, with a fixed set of native libraries, for a single Expo SDK version, and it runs whatever JavaScript bundle your dev server sends. A development build is a Debug build of **your** app that includes `expo-dev-client`, so it contains exactly your native modules, app name, icon, URL schemes and permissions, plus a launcher and dev menu. JavaScript still reloads from Metro in both. You outgrow Expo Go when you need a native library it does not bundle, a config plugin, your own icon or splash behaviour, remote push notifications, universal or app links, or an older SDK on an iPhone. Most production apps move to development builds early.

go deeper

for a junior

Recall that Expo Go is a prebuilt app with fixed native code for one SDK, while a development build is your own app with expo-dev-client.

for a middle

Explain the native-app versus JavaScript-bundle split and use it to predict which features fail in Expo Go and when a rebuild is needed.

for a senior

Show when you move a team off Expo Go: list the triggers, plan shared development builds, and set a rule for when native changes force a rebuild.

for a principal

Decide how development builds are produced and distributed for a team, and how that fits the release pipeline and device fleet.

## Two halves of every Expo app An Expo app running in development has two parts: - the **native app** installed on the device, which contains all native code and native configuration; - the **JavaScript bundle**, served by Metro through `npx expo start` and reloaded as you edit. JavaScript can only call native APIs that were compiled into the native app. That single fact explains every difference between Expo Go and a development build. ## Expo Go: a shared, prebuilt native app **Expo Go** is a native app published to the App Store and Play Store. Its native side was built by Expo with a **fixed set of native libraries**, and it cannot be changed after installation. - It supports **one Expo SDK version at a time**; a new SDK release replaces the store version. - On Android devices, emulators and the iOS Simulator you can install an older Expo Go; on an **iPhone** you cannot, because Apple does not allow side-loading older app versions. - It shows a generic splash and cannot test your icon, app name or native splash options. - It runs only the New Architecture. ## A development build: your own native app A **development build** is a Debug build of your project that includes **`expo-dev-client`**. Because you compile it, it contains: - every native module your `package.json` installs; - the native configuration from your app config and config plugins: name, icon, bundle identifier, URL schemes, entitlements, permission strings; - the dev-client **launcher** (choose a dev server or a published update) and an extended **dev menu**. You build it locally with `npx expo run:ios` / `npx expo run:android`, or in the cloud with EAS Build. | | Expo Go | Development build | |---|---|---| | Native code | fixed set chosen by Expo | whatever your project installs | | SDK versions | one, the current one | the one your project uses | | App identity | Expo Go's | yours: name, icon, bundle ID | | Remote push | not available | available with your credentials | | Universal / app links | not possible | configured in your binary | | Needs a native build | no | yes, once per native change | ## Signals that a project has outgrown Expo Go 1. **A native library Expo Go does not include**: the JavaScript loads, then fails as soon as it calls native code that is not there. 2. **A config plugin** that must change `Info.plist`, `AndroidManifest.xml` or entitlements. 3. **Remote push notifications**, which belong to your own push credentials; on Android they were removed from Expo Go in SDK 53. 4. **Universal Links or Android App Links**, which require your domain in the native app. 5. **Testing app identity**: icon, name, splash options. 6. **An older SDK on an iPhone**, which Expo Go cannot provide. ## What stays the same The inner loop is identical: edit JavaScript, Fast Refresh, same Metro. A development build only needs rebuilding when **native** inputs change, such as adding a native library or editing native config, not for JavaScript changes. That is why teams build it once, share it, and iterate on JavaScript for days. ## Where Expo Go still fits - **Learning and prototypes** that use only Expo SDK modules bundled in Expo Go. - **Quick demos** on a colleague's phone without installing a custom build. - **Libraries with JavaScript fallbacks**, which Expo Go can load even when native code is absent. For a production app, the development build is the normal environment, because it behaves like the binary you will ship. ## A typical migration path 1. Install `expo-dev-client` with `npx expo install expo-dev-client`. 2. Produce the first development build, locally with `npx expo run:ios` / `run:android` or on EAS. 3. Install it on every simulator, emulator and device the team uses. 4. Keep running `npx expo start`; with the library installed, its QR code now opens the development build. 5. Agree when the build must be rebuilt (any native change) and how the new build reaches everyone. The JavaScript does not change at all during this move. What changes is where the native half comes from: Expo's shared app before, your own compiled app after.

  • Why does a native library not bundled in Expo Go fail only when its code runs, not when the bundle loads?
    Metro bundles the library's JavaScript like any other module, so the bundle loads. The failure comes when that JavaScript asks for its native module, which was never compiled into Expo Go. Only a native app built with the library contains it.
  • Does a development build need rebuilding every time JavaScript changes?
    No. JavaScript is served by Metro and reloaded with Fast Refresh, exactly as in Expo Go. Rebuild only when native inputs change: a new or updated native library, a config plugin, or native app config such as the name, icon or permissions.
  • Can you install an older Expo Go on an iPhone to open an SDK 56 project?
    No. Expo Go on the App Store supports only the current SDK, and iOS does not allow side-loading older versions. On Android devices, emulators and the iOS Simulator an older Expo Go can be installed; on an iPhone, use a development build.

Expo Go is a rental car: you can drive anywhere immediately, but you cannot fit your own tow bar. A development build is your own car, built to your spec, which takes time to build but carries every part you need.

saying these in an interview costs you the question

  • Expo Go can load any native library once it is in package.json
  • A development build must be rebuilt for every JavaScript change
  • Expo Go supports every past SDK version on an iPhone
  • A development build is the same thing as a release build
  • Expo Go cannot show any notifications at all
open as a page

In an Expo project, what do npx expo run:ios and npx expo run:android do, and when do you need to run them again?

level: middleimportance: must knowfreq 50%

basics

~20 s

They compile the native app locally with Xcode or Gradle, install it on a simulator, emulator or device, and start Metro. Run them again only after native changes: a native library, a config plugin, or native app config.

open as a page

In an Expo project, what does expo-dev-client add to a debug build, and how does that development build find the Metro dev server?

level: middleimportance: should knowfreq 38%

basics

~20 s

expo-dev-client adds a launcher screen, an extended dev menu and the ability to load published updates. The build finds Metro through the launcher's list of dev servers on the network, or through a QR code deep link carrying the server URL.

open as a page

An Expo app's push-notification feature fails in Expo Go on Android and only warns on iOS; why, and what do you build instead?

level: middleimportance: should knowfreq 45%

basics

~20 s

Remote push depends on the app's own push credentials, which Expo Go cannot carry, and Android push was removed from Expo Go in SDK 53, so expo-notifications throws there. Build a development build with expo-dev-client and test push in it.

open as a page

After a teammate adds a native library, your Expo development build fails with "Cannot find native module"; how do you diagnose it and stop it recurring?

level: seniorimportance: should knowfreq 34%

basics

~20 s

The installed development build predates the library, so its JavaScript asks for native code the binary lacks. Rebuild the development build, and stop recurrences by detecting native changes, for example with a fingerprint check that says when a new build is needed.

open as a page