In React Native's Fabric renderer, which thread runs render, commit and mount, and when can the whole pipeline run synchronously on the UI thread?
answer
- immutable trees make threads interchangeable
- JS renders, UI mounts
- commit includes Yoga layout
- discrete events jump the queue
- C++ state skips render
basics
~20 sNormally render and commit (including Yoga layout) run on the JavaScript thread and mount runs on the UI thread. For a high-priority discrete event originating on the UI thread, Fabric can run render, commit and mount synchronously on the UI thread.
solid answer
~50 sIn Fabric the **render** phase builds an immutable C++ shadow tree on the **JS thread**, **commit** runs Yoga layout and promotes that tree off the UI thread (normally right after render on the JS thread), and **mount** diffs and applies view mutations on the **UI thread**, the only thread that can touch host views. Because shadow trees are immutable and cloned on update, the renderer can also run phases elsewhere: for a high-priority **discrete** event from the UI thread it can interrupt an in-progress render, merge the event's state and run the pipeline synchronously on the UI thread, while a **continuous** event is merged and rendering carries on on the JS thread. **C++ state updates**, such as `ScrollView`'s content offset, skip render and can commit from any thread. The catch: a synchronous UI-thread render has to wait for the JS thread to yield the runtime.
go deeper
Remember the shape: React renders on the JS thread, and the UI thread is where views are finally created and updated.
Place each phase on its thread, including that commit runs Yoga layout off the UI thread, and explain why immutable shadow trees make that safe.
Explain the discrete versus continuous interruption paths, what C++ state is for, and why a synchronous UI-thread render still waits on a busy JS thread.
Weigh what synchronous rendering buys for interaction latency against the coupling it creates between the UI thread and JavaScript's longest task.
## The three phases and where they run React Native's **Fabric renderer** turns React updates into native views in three phases. Each phase has a default thread: | Phase | What happens | Default thread | |---|---|---| | **Render** | React calls your components; the renderer creates or clones immutable C++ **shadow nodes** into a new shadow tree | JavaScript thread | | **Commit** | **Yoga** computes the size and position of every node, then the new tree is promoted to the "next tree" to mount | Off the UI thread; in the common case the JS thread, straight after render | | **Mount** | The next tree is diffed against the tree on screen and the resulting mutations are applied to **host views** (`UIView`, `android.view.View`) | UI (main) thread, on its next tick | The **UI thread** is the only thread that can manipulate host views, which is why mount always ends there. When commit ran on another thread, mount is scheduled for the UI thread's next tick; when commit ran on the UI thread itself, mount runs synchronously on the same thread. ## Why the work can move between threads The renderer is designed to be **thread-safe**, and it gets there through immutability rather than by locking every view: - shadow nodes and shadow trees are **immutable**, enforced with C++ const-correctness; - an update never edits a tree in place; it **clones** the nodes on the path that changed and shares the rest (structural sharing); - because no thread can see a half-modified tree, the renderer can expose **synchronous** APIs to React and run a phase on whichever thread needs it. ## The scenarios the renderer supports 1. **Render on the JS thread**: the common case above, used for ordinary `setState` updates. 2. **Render on the UI thread**: for a high-priority event that originates on the UI thread, the renderer can execute the whole pipeline (render, commit and mount) synchronously on the UI thread, so the result does not wait for a later tick. 3. **Continuous-event interruption**: a low-priority, continuous event from the UI thread (a stream of scroll or move updates, for example) can interrupt an in-progress render; React merges its state, and rendering **continues on the JS thread**. 4. **Discrete-event interruption**: a high-priority, discrete event interrupts an in-progress render; React merges its state, and the render phase then runs **synchronously on the UI thread**. A **discrete event** is a single, intentional input where the user expects an immediate answer, such as a tap; a **continuous event** is one of many rapid samples, where finishing the latest one matters more than any single one. Which native events take the synchronous path is decided inside the renderer and the host components; ordinary app code never picks a thread. ## C++ state updates Most data in the shadow tree has one owner: React. The exception is **C++ state**, data that a host component keeps in the shadow tree without JavaScript being its source of truth. `ScrollView` is the standard example: its host view reports the current **content offset** into C++ state, so a later `measure` call can account for scrolling. A C++ state update differs from a React state update in two ways: - it **skips the render phase**, because React is not involved; - it can **originate on any thread, including the main thread**. Its commit is optimistic: the renderer takes the latest committed version of the node, clones it with the new state and tries to commit. If React or another C++ state update committed in the meantime, the attempt fails and the renderer **retries** until a commit succeeds, which avoids two sources of truth racing each other. Mount then proceeds exactly as for a React update: layout, diff, apply. Most app developers never write C++ state; it matters when authoring a complex host component. ## The catch: synchronous means waiting Synchronous access to the JavaScript runtime from the UI thread is not free. The runtime is used by one thread at a time, so the UI thread **blocks** until the JS thread reaches a point where it can hand the runtime over, usually the end of its current task. If JavaScript is busy with a long synchronous task, a UI-thread render has to wait for it, and the UI thread waits with it. The synchronous path makes high-priority updates land sooner when JavaScript is responsive; it does not make a blocked JS thread harmless. ## Summary - Default: render and commit off the UI thread (normally the JS thread), mount on the UI thread. - High-priority discrete events: the renderer can run the pipeline synchronously on the UI thread. - C++ state updates: no render phase, any thread, commit retried on conflict. - All of it rests on immutable, cloned shadow trees. Fabric has been the only renderer since React Native 0.82, so this model describes every 0.87 app.
- In React Native's Fabric renderer, why can a synchronous UI-thread render still hitch when JavaScript is busy?The JavaScript runtime is used by one thread at a time. To render synchronously, the UI thread has to acquire the runtime, and it blocks until the JS thread finishes its current task and hands it over. A long synchronous JavaScript task therefore delays the high-priority render and stalls the UI thread while it waits.
- In React Native's Fabric renderer, what happens when a C++ state update races with a React commit?The C++ state update clones the latest committed version of its shadow node with the new state and tries to commit. If React or another C++ state update committed first, that attempt fails and the renderer retries against the newer tree until a commit succeeds. This keeps one source of truth instead of letting the two updates overwrite each other.
saying these in an interview costs you the question
- Fabric makes every update render synchronously on the UI thread.
- Yoga layout always runs on the UI thread, next to the views.
- Thread safety comes from locking the host views during every update.
- C++ state updates go through a React re-render like setState does.
- Synchronous rendering means a busy JS thread can no longer affect the UI.