In React Native, what does the global __DEV__ flag tell you, and why is it the wrong switch for staging versus production APIs?
answer
- bundle mode, not deployment target
- true only in a development bundle
- release bundle: literal false
- guarded branches stripped from release
- staging release sees false too
basics
~20 sDEV is true only when the JavaScript bundle was built in development mode and false in any release bundle. It says nothing about the backend: a staging build installed by testers is a release build, so it sees false exactly like production.
solid answer
~40 s`__DEV__` is a pseudo-global that reports the **bundle mode**. When a debug app loads its bundle from Metro it is `true`, which is also when the Dev Menu, LogBox and dev-only warnings exist. When a release bundle is built, Metro replaces `__DEV__` with the literal `false` and constant-folds the dead `if (__DEV__)` branches out of the code. The staging build QA installs is a release build, so a `__DEV__ ? stagingUrl : prodUrl` ternary sends staging testers to production, and nobody can run a debug build against production. Environment belongs to the build variant, carried by a per-variant config value; `__DEV__` is for development-only code.
code
typescript · 4 lines// api.ts: WRONG, a staging release build has __DEV__ === false
export const API_URL = __DEV__
? 'https://staging-api.example.com'
: 'https://api.example.com';go deeper
Recall that DEV means a development bundle: true when loaded from Metro in a debug build, false in any release build.
Explain that Metro inlines DEV as a literal and folds dead branches away, so bundle mode and environment are two independent axes.
Show you would catch the DEV API ternary in review and replace it with a per-variant value before a staging build ever reaches production data.
Treat build mode and environment as separate dimensions of the build matrix, and decide which combinations your team actually needs to ship and test.
## What `__DEV__` is `__DEV__` is a **pseudo-global boolean** that every React Native bundle can read. The React Native docs describe it as the way to guard development-only blocks of code, and say it is inlined during compilation and stripped out, together with the `if` blocks it guards, in the minified build. It answers exactly one question: **was this JavaScript bundle built in development mode?** In development mode the app gets its developer tooling — the Dev Menu, LogBox and React Native DevTools — which the docs state are disabled in release builds. React's own development-only warnings follow the same switch. ## How Metro treats it | Bundle | Built by | `__DEV__` | What happens to `if (__DEV__)` code | |---|---|---|---| | Development | Metro dev server, requested by a debug app | `true` | kept and executed | | Release, Android | the React Native Gradle Plugin's bundle task, which always passes `--dev false` | replaced by `false` | removed by constant folding | | Release, iOS | the "Bundle React Native code and images" build phase for any configuration whose name does not contain `Debug` | replaced by `false` | removed by constant folding | Metro's transform replaces `__DEV__` with a literal in non-dev bundles and then runs a **constant-folding** pass, so the guarded branch is not merely skipped at runtime — it is not in the shipped code at all. React Native also derives `process.env.NODE_ENV` from the same mode, so `NODE_ENV` carries no extra information. ## Who decides the mode for each build - **Android debug variants listed in the Gradle plugin's `debuggableVariants`** carry no bundle; the app asks Metro, which builds a development bundle. - **Every other Android variant** gets a bundle built by the plugin with `--dev false`, whatever its flavor is called. - **iOS configurations whose name contains `Debug`** get a development bundle (served by Metro on the simulator, embedded on a device). - **Every other iOS configuration**, including a `Staging` one duplicated from `Release`, gets a release bundle. None of these rules looks at which API the build is meant to call. The mode follows the native build type and the configuration name, which is exactly why it cannot double as an environment switch. ## Why it cannot choose an environment Picture a food-delivery app with a staging backend and a production backend, and a developer who writes the API URL as a `__DEV__` ternary: 1. On the developer's machine the debug build loads from Metro, `__DEV__` is `true`, and the app calls staging. Everything looks right. 2. QA installs the **staging build** — a release-mode build, because testers need realistic performance and no Dev Menu. `__DEV__` is `false`. 3. The staging build now calls **production**, with test accounts and test orders. 4. Conversely, nobody can run a debug build against production to reproduce a production-only bug without editing code. The two axes are independent: - **bundle mode**: development or release — `__DEV__`; - **deployment target**: local, staging, production — something the build has to carry. A staging release, a production release and a debug build pointed at staging are all legitimate combinations, and a boolean with two values cannot express three environments anyway. ## What to use instead - A per-environment value inlined at build time or read from native build config (see the build-variant approaches: Android product flavors, iOS build configurations and schemes, and a config library that reads a per-variant `.env`). - A single `config.ts` module that exports the API URL from that source, so no screen decides the environment itself. ## Legitimate uses of `__DEV__` - Development-only logging, assertions and debug screens that must never ship. - Registering dev tooling that should vanish from release bundles. - Loosening checks that would be noisy while iterating locally. The rule of thumb: `__DEV__` decides **how** the code is built; the build variant decides **where** it talks to.
- Is process.env.NODE_ENV a better switch than __DEV__ for choosing the backend?No. React Native derives `NODE_ENV` from `__DEV__`, and Metro inlines it as `'production'` in every release bundle. It carries the same bundle-mode signal, so a staging release still reads `'production'`.
- Does code inside if (__DEV__) cost anything in a release build?No. Metro replaces `__DEV__` with `false` in a non-dev bundle and a constant-folding pass removes the branch, so it is absent from the shipped bundle rather than skipped at runtime.
saying these in an interview costs you the question
- __DEV__ is false only in the production build
- __DEV__ tells you which backend environment the build targets
- if (__DEV__) branches ship in release and are skipped at runtime
- process.env.NODE_ENV can distinguish staging from production
- A staging build for testers should be a debug build so __DEV__ is true