Why is a React Native development build a poor place to judge jank, and what should you measure on instead?
answer
- __DEV__ is true
- extra warnings and checks at runtime
- React's development renderer build
- yarn android --mode release
- real device, not only a simulator
basics
~20 sDevelopment mode makes the JS thread do much more work - warnings, validation, error messages and dev tooling - so frame drops appear that users never see. Judge jank on a release build running on a real device.
solid answer
~40 sIn a debug build the bundle is served by Metro with `dev=true`, so `__DEV__` is true and React Native loads React's **development** renderer, which runs extra checks and produces detailed warnings; Fast Refresh, LogBox and the debugger connection add their own work. The React Native docs are blunt that JavaScript-thread performance "suffers greatly" in dev mode and that this is unavoidable. So a JS-thread stutter in debug is a hypothesis, not a finding: rebuild in release (`yarn android --mode release`, `yarn ios --mode Release`, or `npx expo run:android --variant release` / `npx expo run:ios --configuration Release` in Expo) and reproduce on a real device, ideally a low-end Android phone. Only jank that survives that is worth optimising.
code
bash · 7 lines# React Native CLI template scripts
yarn android --mode release
yarn ios --mode Release
# Expo project with native directories generated
npx expo run:android --variant release
npx expo run:ios --configuration Releasego deeper
Remember that development mode is slow on purpose and that performance is judged on a release build on a real device.
Explain what dev mode adds - DEV checks, React's development renderer, warnings and tooling - and why that overhead lands mainly on the JS thread.
Show a disciplined routine: reproduce in release on low-end hardware, separate JS from UI jank, and only then profile and change code.
Discuss making release-mode measurement the team default, for example which devices count as the reference and how performance findings are reported.
## What changes between a debug and a release build A React Native **development build** is built for fast iteration and good error messages, not for speed. When Metro serves the bundle in development mode (the bundle request carries `dev=true`), several things are different from what users run: | Aspect | Development build | Release build | |---|---|---| | `__DEV__` global | `true` | `false` | | React renderer | development build with extra checks and warnings | production build | | Bundle | served by Metro, unminified | embedded in the app, minified | | Dev tooling | Fast Refresh, LogBox, debugger connection | absent | | Error messages | detailed, with component stacks | minimal | Every one of those extras costs time on the **JS thread**. Components validate more, warnings are formatted, development-only code paths run, and the tooling listens for changes. The React Native performance guide states that JavaScript thread performance suffers greatly in dev mode and that this is unavoidable, and tells you to always test performance in release builds. ## Why that matters for jank Jank means missing the frame budget - about **16.7 ms** at 60 Hz and **8.3 ms** at 120 Hz. Development overhead can easily push a render that takes a few milliseconds in release over the deadline in debug. The result: - You may **chase jank that does not exist** for users and spend days optimising code that was fine. - You may **misjudge which thread** is at fault, because dev mode inflates JS-thread cost far more than UI-thread cost. - Your before-and-after numbers are **not comparable** with what ships, since the overhead is not constant across code paths. The reverse mistake is also real: a simulator on a fast laptop hides problems that a budget Android phone reveals. Release mode and realistic hardware belong together. ## How to get a release-like measurement 1. **Build a release variant.** In a project using the React Native CLI scripts, `yarn android --mode release` and `yarn ios --mode Release` build and install release builds. In an Expo project, `npx expo run:android --variant release` and `npx expo run:ios --configuration Release` do the same. 2. **Run it on a real device**, preferably one close to your slowest supported users. 3. **Reproduce the interaction** several times; the first run after install can include one-off work. 4. **Only then measure and profile**, using a profiler that attaches to a release-like build, since the in-app Dev Menu tools are not available in a pure release build. Note that a pure release build has no Dev Menu, so the in-app **Perf Monitor** overlay is a development tool. Use it to separate JS-thread from UI-thread jank during development, and confirm the finding on a release build with a profiler or with careful manual testing. ## Reading dev-mode signals sensibly Development builds are still useful for performance work, as long as you treat what they show correctly: - A **UI-thread** stutter in debug (scrolling or native transitions stuttering while JS is idle) is more likely to be real, because dev overhead mostly lands on the JS thread. - A **JS-thread** stutter in debug is a lead to verify, not a result. - **Relative** comparisons between two approaches measured in the same dev build can hint at direction, but confirm the winner in release. - Development-only warnings in the console are worth fixing for correctness, but their cost is not what users pay. ## Why the overhead lands on the JS thread Most of what development mode adds is JavaScript: the development renderer's extra validation, the formatting of warnings with component stacks, the LogBox machinery that collects them, and the Fast Refresh runtime that tracks modules so they can be swapped. All of that runs on the same **JS thread** as your components and handlers, and competes for the same frame budget. The native side of a debug build also carries some extra checks, but the gap users would never see is overwhelmingly a JS-thread gap. That is why a JS-thread drop in debug deserves more suspicion than a UI-thread drop. ## Common mistakes - Filing a performance bug from a simulator running a debug build. - Assuming the release build is "the same code, just minified" - the renderer and runtime checks differ too. - Leaving the debugger attached while judging smoothness. - Measuring once on a flagship phone and declaring the screen fast. The rule interviewers want to hear is short: development mode is slow on purpose, so performance claims are made on release builds on real devices.
- If dev mode inflates JS cost, why is a UI-thread stutter seen in debug still worth taking seriously?Most development overhead lands on the JS thread: extra checks, warnings and tooling. If scrolling or a native transition stutters while the JS thread is idle, the main thread is struggling to draw, and that cost is largely the same in release, so it is a likelier real problem.
- What would you check before trusting a comparison of two implementations measured in a debug build?Rebuild both in release and repeat on the same real device. Dev-mode overhead is not uniform across code paths, so a gap in debug can shrink, grow or reverse in release.
saying these in an interview costs you the question
- A debug build is just an unminified release build
- If it stutters in the simulator, users will see the same stutter
- Dev-mode overhead mostly slows the UI thread
- The Perf Monitor overlay is available in every release build
- Optimise first, then check whether release builds even have the problem