
Introduction
Concurrency: is when two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they'll ever both be running at the same instant. For example, multitasking on a single-core machine.
Parallelism: is when tasks literally run at the same time, e.g., on a multicore processor.
What is the Difference Between Concurrency and Parallelism
Agenda
- Start of Concurrency in React
- What problems does concurrency solve
- Disadvantages of concurrency in React
- How does React do concurrent behavior under the hood
- Update queue
- INP web vital
- Suspense and hydration
- React Fiber
- Lanes
1. Start of Concurrency in React
React 18 was released in March 2022. This release focuses on performance improvements and updating the rendering engine.
React 18 sets the foundation for concurrent rendering APIs that future React features will be built on top of.
React also introduced many new features and deprecated old features. Some of the features introduced were new hooks:
-
useTransition
andstartTransition
function useDeferredValue
useId
useSyncExternalStore
useInsertionEffect
2. What problems does concurrency solve?
Concurrent Mode in React is a feature that can enhance the user experience by improving the responsiveness and performance of applications.
-
Once the render phase of UI components starts, nothing can stop or interrupt them until it finishes. The rendering process must be completed before moving to any other tasks. Due to which, users might experience performance lag that can frustrate them.
- If concurrent mode is not enabled in your React application, you cannot control which UI component should be rendered and when. While non-concurrent mode can be suitable for a single component but if the UI layout depends on multiple components, then it might cause problems.
- To display data within React components, the data must be retrieved beforehand. If a parent-child relation exists between components, fetching child components's data will not start until the parent's data is fetched. Hence, both parent's data and child's data must be fetched before rendering. This is also a problem as it may increase the time to load full application.
3. Disadvantages of concurrency in React
While concurrent rendering provides many benefits, it has some drawbacks, such as increased complexity in the React architecture, potentially longer times for non-urgent updates, and increased CPU usage due to the need to check and yield back control to the browser frequently.
So this overhead, it's not constant, it depends how many components you're rendering, I don't know, et cetera. It's not like it's 100 milliseconds longer every time, or 20% longer, but what you see here is because Rect has to give the control back to the browser every time, the whole update finishes later. Maybe that's okay because it's marked as non-urgent, but that's a drawback to keep in mind.
4. How does React do concurrent behavior under the hood
1. Update queue:
In React 17 and before, React was queuing all the tasks in a queue, and when the rendering process began, it would start executing them 1 by 1in a non-interruptible manner
const updateQueue = [
<NoteList />,
<NoteButton />,
<NoteButton />,
<NoteButton />,
<NoteButton />,
];
function performWorkUntilDeadline() {
while (updateQueue.length > 0) {
performUnitOfWork(updateQueue.shift());
}
}
1. Update queue:
In React 18 and after, React will pause the execution of the tasks and yield control back to the browser after 5 milliseconds
const updateQueue = [
<NoteList />,
<NoteButton />,
<NoteButton />,
<NoteButton />,
<NoteButton />,
];
function performWorkUntilDeadline() {
while (updateQueue.length > 0 && !shouldYieldToHost()) {
performUnitOfWork(updateQueue.shift());
}
if (updateQueue.length > 0) {
schedulePerformWorkUntilDeadline();
}
}
let startTime = performance.now(); // updated on every render
function shouldYieldToHost() {
return (performance.now() - startTime) > 5; // ms
}
function schedulePerformWorkUntilDeadline() {
// If setImmediate is supported (Node.js)
if (setImmediate) {
setImmediate(performWorkUntilDeadline);
} else {
// If only setTimeout is supported (old browsers)
setTimeout(performWorkUntilDeadline, 0);
}
// If MessageChannel is supported (modern browsers)
// This is a hack to avoid setTimeout()'s minimum delay.
const channel = new MessageChannel();
channel.port1.onmessage = performWorkUntilDeadline;
channel.port2.postMessage(/* no message */);
}
1. Update queue:
function shouldYieldToHost(): boolean {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// The main thread has only been blocked for a really short amount of time;
// smaller than a single frame. Don't yield yet.
return false;
}
// The main thread has been blocked for a non-negligible amount of time. We
// may want to yield control of the main thread, so the browser can perform
// high priority tasks. The main ones are painting and user input. If there's
// a pending paint or a pending input, then we should yield. But if there's
// neither, then we can yield less often while remaining responsive. We'll
// eventually yield regardless, since there could be a pending paint that
// wasn't accompanied by a call to `requestPaint`, or other main thread tasks
// like network events.
if (enableIsInputPending) {
if (needsPaint) {
// There's a pending paint (signaled by `requestPaint`). Yield now.
return true;
}
if (timeElapsed < continuousInputInterval) {
// We haven't blocked the thread for that long. Only yield if there's a
// pending discrete input (e.g. click). It's OK if there's pending
// continuous input (e.g. mouseover).
if (isInputPending !== null) {
return isInputPending();
}
} else if (timeElapsed < maxInterval) {
// Yield if there's either a pending discrete or continuous input.
if (isInputPending !== null) {
return isInputPending(continuousOptions);
}
} else {
// We've blocked the thread for a long time. Even if there's no pending
// input, there may be some other scheduled work that we don't know about,
// like a network event. Yield now.
return true;
}
}
// `isInputPending` isn't available. Yield now.
return true;
}
1. Update queue:
2. INP web vital
There’s also another reason why React concurrency is beneficial, because it will enhance your INP (Interaction to Next Paint)
Interaction to Next Paint measures how much the page lags after a click or a keypress. And before React 18, every React client I work with has their INP in the red.
useTransition() is a reliable way to make interactions cheaper. So the new concurrency API will enhance the INP metric

3. Suspense and hydration:
Hydration is one of the most expensive operations in React, so using suspense can enhance the hydration processes by telling react that these components has a lower priority so React can render them in a none blocking manner

3. Suspense and hydration:
If Suspense is so powerful why not wrap the whole app with it ?

4. React Fiber
React updates the DOM by doing a process called reconciliation, which consists of 2 main operations:
1. Generating the new virtual DOM
2. Comparing the old virtual DOM with the new one (Diffing algorithm)
This process was:
1. Blocking the main thread
2. Impossible to split work into chunks to fit in render frames
3. Impossible to pause
4. Impossible to react on user events rapidly
4. React Fiber
Before React Fiber
Old reconciler (the Stack reconciler): Stack reconciler was synchronous, and it has this name because it worked like a stack. You could add items, and remove items, but it had to work until the stack was empty. It couldn't be interrupted. Let's think of an example that uses the stack reconciler.
4. React Fiber
Fiber tree

PerformUnitOfWork:
Interruptible:
1. Begin step (beginWork)
2. Complete Step (completeWork)
Not Interruptible:
3.Commit phase (commitRoot)
4. React Fiber
What is react fiber
type Fiber = {
// linking
return: Fiber | null;
child: Fiber | null;
sibling: Fiber | null;
type: any; // The resolved function/class associated with this fiber.
tag: number; // FunctionComponent, ClassComponent, MemoComponent etc
memoizedState: MemoizedState; // state
updateQueue: UpdateQueue; // A queue of state updates and callbacks
// priority
lanes: number;
childLanes: number;
// props
pendingProps: any; // Current props
memoizedProps: any; // The props used to create the output.
alternate: Fiber | null; // "The same" fiber from alternative tree
};
React fiber is a complete rewrite of react that fixes a few long-standing issues and offers incredible and offers opportunities heading into the future.
5. Lanes
What are Lanes
// https://github.com/facebook/react/blob/9212d994ba939f20a04220aef
const NoLane: Lane = 0b0000000000000000000000000000000;
const SyncLane: Lane = 0b0000000000000000000000000000001;
const TransitionLanes: Lanes = 0b0000000000001111111111111111110;
const IdleLane: Lanes = 0b0100000000000000000000000000000;
const OffscreenLane: Lane = 0b1000000000000000000000000000000;
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
Lanes are bitmask flags used to efficiently schedule updates based on their priority during concurrent rendering. Each update is assigned a lane—such as SyncLane
for immediate work, TransitionLanes
for UI transitions, or IdleLane
for background tasks—and React uses bitwise operations to manage and prioritize these updates, allowing it to batch work, yield when needed, and improve UI responsiveness.
References
react/CHANGELOG.md at main · facebook/react
React 18 Concurrency, Explained | Ivan Akulov | performance.now() 2022
Deep diving on Concurrent React – Matheus Albuquerque, React Advanced London 2022
Concurrent React 18. "I might do it later" [eng] / Mykola Yenin
React 18 New Features – Concurrent Rendering, Automatic Batching, and More
Concurrent Mode in React: A Detailed Explanation | Vivasoft Ltd.
How does Concurrent Mode Help in Improving The User Experience? - GeeksforGeeks
React Concurrency, Explained by Ivan Akulov
Concurency In Ract.Js
By ahmed saeed
Concurency In Ract.Js
- 11