React Under the Hood: How React Actually Works

Hi, I’m a software engineer with a strong focus on frontend development and open-source contributions. I enjoy solving real-world problems, improving developer workflows, and building systems that scale.
I believe in taking ownership of my work and paying close attention to quality and detail.
I'm here to document my learnings, ideas, and practical insights from building and shipping real products. If anything here resonates with you, feel free to reach out. I’m always happy to connect and learn from others.
When we first start learning React, we write components, use useState, see things magically update on the screen, and think “this is cool!“ But have you ever wondered what’s actually happening behind the scenes?
Understanding how React works internally will make us better developers. We will be able to write more efficient code, debug faster, and truly understand why React does things the way it does.
So, let’s dive deep into React’s internals in a simple and friendly way.
The Problem React Solves
Before React, updating the UI meant directly manipulating the DOM (Document Object Model). Let’s understand it with the code below:
// The old way - directly manipulating DOM
document.getElementById('counter').innerText = count;
document.getElementById('username').innerText = name;
Problems with this approach:
Slow: Every DOM manipulation causes the browser to recalculate styles, layout, and repaint
Error-prone: Easy to create bugs when manually tracking what needs updating
Hard to maintain: Complex UIs become a nightmare of DOM manipulation code
React's solution:
"Tell me what the UI should look like, and I'll figure out the most efficient way to update it."
What is the Virtual DOM?
Think of the Virtual DOM like a blueprint of your house, while the Real DOM is the actual house.
Real DOM
The actual HTML elements you see in the browser
Heavy and expensive to manipulate
Causes reflows and repaints
Virtual DOM
A lightweight JavaScript object that represents the Real DOM
Fast to create and manipulate
Lives in memory, not in the browser
Let’s see how it looks:
Your React Component:
function Welcome() {
return (
<div className="container">
<h1>Hello, React!</h1>
<p>Welcome to my app</p>
</div>
);
}
Virtual DOM Representation (simplified):
{
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: {
children: 'Hello, React!'
}
},
{
type: 'p',
props: {
children: 'Welcome to my app'
}
}
]
}
}
It's just a plain JavaScript object! Super lightweight and fast to work with.
Initial Render: Painting the Screen for the First Time
Let's walk through what happens when your React app loads for the first time. Let’s go step by step.
Step 1: You write JSX
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Step 2: JSX gets transformed to JavaScript
Babel converts the JSX to React.createElement() calls:
// What the code actually becomes
function App() {
const [count, setCount] = useState(0);
return React.createElement(
'div',
null,
React.createElement('h1', null, 'Counter: ', count),
React.createElement(
'button',
{ onClick: () => setCount(count + 1) },
'Increment'
)
);
}
Step 3: React creates the Virtual DOM Tree
React executes the component function and builds a tree of objects like below:
{
type: 'div',
props: {
children: [
{
type: 'h1',
props: { children: ['Counter: ', 0] }
},
{
type: 'button',
props: {
onClick: [Function],
children: 'Increment'
}
}
]
}
}
Step 4: React Renderer creates Real DOM
React takes this Virtual DOM and creates actual DOM elements:
// React internally does something like this:
const div = document.createElement('div');
const h1 = document.createElement('h1');
h1.textContent = 'Counter: 0';
const button = document.createElement('button');
button.textContent = 'Increment';
button.addEventListener('click', yourClickHandler);
div.appendChild(h1);
div.appendChild(button);
document.getElementById('root').appendChild(div);
Step 5: The browser paints the screen
The browser displays the Real DOM on your screen.
Visual Flow:

Re-rendering: When Things Change
Now the interesting part comes: what happens when we click that "Increment" button?
The Re-render process starts.
Step 1: State changes
// User clicks the button
<button onClick={() => setCount(count + 1)}>
setCount(1)is calledReact marks this component as "needs update"
React schedules a re-render
Step 2: React calls the component again
function App() {
const [count, setCount] = useState(0); // count is now 1
return (
<div>
<h1>Counter: {count}</h1> {/* This will be "Counter: 1" */}
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Step 3: React creates a NEW Virtual DOM
// New Virtual DOM
{
type: 'div',
props: {
children: [
{
type: 'h1',
props: { children: ['Counter: ', 1] } // Changed from 0 to 1
},
{
type: 'button',
props: {
onClick: [Function],
children: 'Increment'
}
}
]
}
}
Step 4: The Magic - Reconciliation
React now has TWO Virtual DOM trees:
Old Virtual DOM (count: 0)
New Virtual DOM (count: 1)
React compares them and finds the difference.
Reconciliation: React's Diffing Algorithm
This is where React's genius shines. Instead of replacing the entire UI, React finds the smallest change needed.
The Diffing Process:
Let’s assume it's like a spot-the-difference game:

React's thought process:
<div>- Same type, keep it<h1>- Same type, keep it, but...Text changed from "Counter: 0" to "Counter: 1" - Update needed!
<button>- Same type, same content, keep it
React's update:
// Only this happens in the Real DOM:
document.querySelector('h1').textContent = 'Counter: 1';
This is it! No need to recreate the entire component. Super efficient!
Reconciliation Rules:
React uses these rules when comparing:
Rule 1: Different Types → Replace Everything
// Old
<div>Hello</div>
// New
<span>Hello</span>
// React destroys the <div> and creates a new <span>
Rule 2: Same Type → Update Props
// Old
<div className="old">Hello</div>
// New
<div className="new">Hello</div>
// React keeps the <div>, just updates the className
Rule 3: Lists needed Keys
// Without keys - BAD
{items.map(item => <li>{item}</li>)}
// With keys - GOOD
{items. map(item => <li key={item.id}>{item}</li>)}
Why these keys matter:
// Initial list
<ul>
<li key="1">Apple</li>
<li key="2">Banana</li>
</ul>
// You add "Cherry" at the beginning
<ul>
<li key="3">Cherry</li> ← New item
<li key="1">Apple</li> ← React knows this is the same
<li key="2">Banana</li> ← React knows this is the same
</ul>
With keys: React knows to insert one new <li> at the top.
Without keys: React thinks all three items changed and updates all of them!
Fiber Architecture: React's Brain
In 2017, React introduced Fiber, a complete rewrite of the reconciliation engine in React 16.0.
Before React 16, rendering was synchronous. Long updates could block the main thread and freeze the UI.
What is Fiber?
Think of Fiber as React's task manager. It breaks rendering work into small chunks and can handle the following:
Pause work if something more important comes up
Resume work later
Prioritize urgent updates (like user input) over less urgent ones (like data fetching)
Difference with React Fiber:

Here are some priority levels for Fiber:
// React internally prioritizes updates:
// HIGHEST PRIORITY (React updates immediately)
onClick, onInput, onKeyPress
// HIGH PRIORITY
Animations, transitions
// NORMAL PRIORITY
Data fetching, network responses
// LOW PRIORITY
Off-screen content, hidden components
// LOWEST PRIORITY
Analytics, logging
Example:
function App() {
const [search, setSearch] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (e) => {
// HIGH PRIORITY - user input feels instant
setSearch(e.target.value);
// LOW PRIORITY - can be delayed
startTransition(() => {
const filtered = hugeDataset.filter(item =>
item.includes(e.target.value)
);
setResults(filtered);
});
};
return (
<div>
<input value={search} onChange={handleSearch} />
{/* Input feels responsive even while filtering! */}
<Results data={results} />
</div>
);
}
Important note for clarity:
Fiber is an internal architecture, not a public API.
We don’t “use” Fiber directly, but features like
useTransition,Suspense, and concurrent updates are built on top of it.
Conclusion
Let’s recap what we learned:
How React Works:
JSX → JavaScript: Babel transforms your JSX to
React.createElement()callsVirtual DOM: React creates lightweight JavaScript objects representing your UI
Initial Render: React converts Virtual DOM to Real DOM and paints the screen
State Changes: When state updates, React re-runs your component
Reconciliation: React diffs old and new Virtual DOM to find minimal changes
Efficient Updates: Only the changed parts of the Real DOM are updated
Fiber: Breaks work into chunks for smooth, non-blocking updates
Key Takeaways:
Virtual DOM is a JavaScript representation of your UI
React only updates what actually changed
Fiber makes React responsive by prioritizing work
Keys help React identify elements in lists
Would you like to see this in action?
Open your browser DevTools, go to the Performance tab, and record your React app. You will see exactly when renders happen and how long they take!
Quick Resources
Happy coding!
If this article added value, feel free to share it with others.
For questions, feedback, or more insights, leave a comment or reach out to me on X or LinkedIn. I’d be happy to connect.
That’s it for today. Thanks for stopping by!



