In React Native, what work runs on the JavaScript thread and what runs on the UI (main) thread?
answer
- one runtime, one task at a time
- host views belong to one thread
- render on JS, mount on UI
- ScrollView scrolls without JavaScript
- mqt_v_js next to main in a trace
basics
~20 sThe JavaScript thread runs your bundle: React rendering, state updates, event handlers, timers and fetch callbacks. The UI (main) thread is the only one that creates and mutates native views, and it handles raw touches, native scrolling and native animations.
solid answer
~40 sReact Native has two threads that matter to app code. The **JS thread** hosts the Hermes runtime and executes everything in your bundle: component functions, hooks, `onPress` handlers, `setTimeout` callbacks, promise continuations. The **UI thread**, the platform main thread, is the only thread allowed to manipulate host views (`android.view.View`, `UIView`); it receives raw touches, runs native scrolling, native stack transitions and natively driven animations, and mounts the changes React produced. The Fabric renderer connects them: React renders a shadow tree on the JS thread, layout is computed off the UI thread, and the mount step applies the diff on the UI thread. Because the JS thread runs one task at a time, a long task delays every JS-driven update and handler, while purely native work such as scrolling keeps going.
go deeper
Name the two threads and what each owns: your JavaScript and React on the JS thread, native views, touches and scrolling on the UI thread.
Walk through render, commit and mount and say which thread each lands on, then predict what freezes and what keeps working when the JS thread is busy.
Use the split to diagnose: read the Perf Monitor's two frame rates or a trace's mqt_v_js and main threads, and decide whether the fix belongs in JavaScript or native code.
Frame the split as an architectural budget: decide which interactions must survive a busy JS thread and push those to native-driven paths when choosing libraries and patterns.
## The two threads that matter A running React Native app owns many operating-system threads (networking, image decoding, garbage collection, background work), but two of them explain almost every question about why an app feels responsive or frozen. | Thread | Other names | What it runs | |---|---|---| | **JavaScript thread** | JS thread; `mqt_v_js` in an Android trace, `com.facebook.react.runtime.JavaScript` on iOS | The Hermes runtime and your entire bundle: component functions, hooks, `setState`, handlers such as `onPress`, `setTimeout` and `setInterval` callbacks, and the code after `await fetch(...)` | | **UI thread** | main thread | Every **host view** (`android.view.View`, `UIView`): creating, updating and removing them, receiving raw touches, native scrolling, native stack transitions, animations whose frames are produced natively | A **host view** is the real platform view object that a React Native component such as `View` or `Text` becomes on screen. React Native's renderer documentation defines the UI thread as *the only thread that can manipulate host views*, so your JavaScript never touches a `UIView` or an Android `View` directly. The JS thread runs **one task at a time**. There is one JavaScript runtime per React Native instance, and while it is executing a function, nothing else in JavaScript can run: not a timer callback, not a press handler, not a re-render. ## How a state change crosses the threads The **Fabric renderer**, written in C++, sits between the two threads. A typical update after `setState` goes through three phases: 1. **Render**: on the JS thread, React calls your components and the renderer builds a new, immutable C++ **shadow tree** describing what should be on screen. 2. **Commit**: Yoga computes the size and position of every node, and the renderer marks the new tree as the next one to mount. This does not happen on the UI thread; in the common case it runs on the JS thread straight after render. 3. **Mount**: on the UI thread, the renderer diffs the new tree against the one currently on screen and applies the resulting create, update and delete operations to the host views. So JavaScript *describes* the UI and the UI thread *applies* it. For a high-priority event the renderer can also run all three phases synchronously on the UI thread, but the everyday path is the split above. ## What stalls when each thread is busy When the **JS thread** is stuck in a long task: - state updates do not render, so text, counters and lists keep their old values; - `onPress` and other handlers, and any press feedback computed in JavaScript, wait until the task ends; - timer callbacks and `fetch` continuations run late; - animations whose every frame is computed in JavaScript freeze. What keeps working, because the UI thread owns it: - scrolling a `ScrollView` or a list through rows that are already rendered; - native stack screen transitions; - animations whose frames are produced natively; - the last mounted UI, which stays on screen and redraws normally. When the **UI thread** is busy, the result is broader: scrolling, touches and native animations stall even if JavaScript is idle, and nothing JavaScript renders can be mounted until the UI thread is free again. ## Seeing the threads for yourself - The **Perf Monitor** overlay in the Dev Menu shows separate frame rates for JavaScript and for the UI, which tells you at a glance which side is starved. - In a native trace, find the JS thread by name: `mqt_v_js` on Android in current bridgeless builds (older documentation shows `mqt_js`), and `com.facebook.react.runtime.JavaScript` on iOS, next to the platform main thread. - A JS thread that stays busy across many frames points at your JavaScript; a main thread busy creating or drawing views points at native work. ## Misconceptions to avoid - **"JavaScript runs on the UI thread."** It does not; Hermes runs on its own thread, and the UI thread only receives the result of rendering. - **"React Native renders into a WebView."** Components become real platform views; there is no DOM. - **"An async function runs in the background."** `async`/`await` only splits work into later steps on the same JS thread; a CPU-heavy function still blocks it. - **"The New Architecture made JavaScript multi-threaded."** It made the renderer thread-safe and made synchronous calls into native code possible; your JavaScript still runs on one thread. The division matters in interviews because nearly every performance answer (why a list stutters, why a tap feels slow, why a transition stays smooth) starts with naming the thread the work lands on.
- In React Native, if the JS thread is idle but the UI thread is blocked, what does the user see?Everything visible freezes: scrolling, touch feedback, native transitions and natively driven animations stop, because all of them live on the UI thread. JavaScript can keep rendering and committing new trees, but nothing reaches the screen until the UI thread is free to run the mount phase. It is rarer than a busy JS thread and usually comes from heavy native work, such as creating a very large number of views at once.
- When a React Native app calls fetch, which parts of the request touch the JS thread?The HTTP request itself is performed by React Native's native networking module, off the JS thread. Only the JavaScript around it runs on the JS thread: building the request, the promise continuations after `await fetch(...)`, and `response.json()`, which ends in a `JSON.parse` of the whole body. A huge response therefore costs JS-thread time at parse time, not while it downloads.
The JS thread is a single cashier and the UI thread is the shop floor. While the cashier is stuck on one long receipt, customers can still walk the aisles (scrolling, native transitions), but nobody can check out or get an answer (handlers, re-renders) until the cashier is free.
saying these in an interview costs you the question
- JavaScript and view rendering share one main thread, as in a browser tab.
- Any thread can update a native view directly if it holds a ref.
- Marking a function async makes it run on a background thread.
- A busy JS thread also stops a ScrollView from scrolling.
- The New Architecture runs your JavaScript on several threads in parallel.