Virtual DOM is a programming concept where an ideal or “virtual” representation of a user interface is kept in memory and synced with the “real” DOM. This allows optimizing UI updates by minimizing expensive operations with the real DOM.
Main reasons for using Virtual DOM:
Virtual DOM is a lightweight copy of the real DOM that is stored in memory. It’s not the actual DOM structure, but a representation of the DOM tree in the form of JavaScript objects. Virtual DOM allows comparing the current state of the interface with the new one and performing only necessary updates to the real DOM.
// Virtual DOM example in React
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Virtual DOM was created to solve key performance problems when working with real DOM:
Working with real DOM is one of the slowest operations in the browser:
// Problem: direct manipulation with real DOM
const container = document.getElementById('container');
// Each change triggers a repaint
container.innerHTML = '<p>First paragraph</p>';
container.innerHTML += '<p>Second paragraph</p>';
container.innerHTML += '<p>Third paragraph</p>';
// This triggers 3 separate repaints!
// Solution: Virtual DOM optimizes updates
function App() {
const [items, setItems] = useState(['First', 'Second', 'Third']);
const addItem = () => {
setItems([...items, `Item ${items.length + 1}`]);
};
return (
<div>
{items.map((item, index) => <p key={index}>{item}</p>)}
<button onClick={addItem}>Add item</button>
</div>
);
}
// Virtual DOM performs only one update to the real DOM
Without Virtual DOM, it’s difficult to track which parts of the interface need to be updated:
// Without Virtual DOM - complex update management
let todos = [
{ id: 1, text: 'Buy bread', completed: false },
{ id: 2, text: 'Call doctor', completed: true }
];
function updateTodo(id, completed) {
// Find element in array
const todo = todos.find(t => t.id === id);
todo.completed = completed;
// Manually update DOM
const todoElement = document.querySelector(`[data-id="${id}"]`);
if (todoElement) {
todoElement.classList.toggle('completed', completed);
}
}
// With Virtual DOM - automatic update management
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Buy bread', completed: false },
{ id: 2, text: 'Call doctor', completed: true }
]);
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<ul>
{todos.map(todo => (
<li
key={todo.id}
data-id={todo.id}
className={todo.completed ? 'completed' : ''}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</li>
))}
</ul>
);
}
Virtual DOM allows using a declarative approach to development:
// Imperative approach (without Virtual DOM)
function updateUI(user) {
// Manually update each element
document.getElementById('username').textContent = user.name;
document.getElementById('email').textContent = user.email;
document.getElementById('avatar').src = user.avatar;
// Manually change styles
if (user.isOnline) {
document.getElementById('status').className = 'online';
document.getElementById('status').textContent = 'Online';
} else {
document.getElementById('status').className = 'offline';
document.getElementById('status').textContent = 'Offline';
}
}
// Declarative approach (with Virtual DOM)
function UserProfile({ user }) {
return (
<div>
<img id="avatar" src={user.avatar} alt={user.name} />
<h2 id="username">{user.name}</h2>
<p id="email">{user.email}</p>
<span
id="status"
className={user.isOnline ? 'online' : 'offline'}
>
{user.isOnline ? 'Online' : 'Offline'}
</span>
</div>
);
}
The Virtual DOM process consists of several stages:
During the first render, React creates a Virtual DOM tree:
// Source component
function App() {
return (
<div className="container">
<h1>Header</h1>
<p>Paragraph</p>
</div>
);
}
// Virtual DOM representation (simplified)
const virtualDOM = {
type: 'div',
props: { className: 'container' },
children: [
{
type: 'h1',
props: null,
children: ['Header']
},
{
type: 'p',
props: null,
children: ['Paragraph']
}
]
};
When state changes, React creates a new Virtual DOM tree and compares it with the previous one:
// Previous state
const prevVirtualDOM = {
type: 'ul',
props: null,
children: [
{ type: 'li', props: { key: '1' }, children: ['Item 1'] },
{ type: 'li', props: { key: '2' }, children: ['Item 2'] }
]
};
// New state (after adding item)
const nextVirtualDOM = {
type: 'ul',
props: null,
children: [
{ type: 'li', props: { key: '1' }, children: ['Item 1'] },
{ type: 'li', props: { key: '2' }, children: ['Item 2'] },
{ type: 'li', props: { key: '3' }, children: ['Item 3'] }
]
};
// Virtual DOM determines that only one element needs to be added
Based on differences, Virtual DOM creates a minimal set of changes:
// Patch for updating real DOM
const patches = [
{
type: 'INSERT',
target: 'ul',
element: { type: 'li', content: 'Item 3' },
position: 'end'
}
];
Virtual DOM applies patches to the real DOM in one batch:
// One batch update instead of multiple separate operations
requestAnimationFrame(() => {
// Apply all changes in one operation
document.querySelector('ul').appendChild(newLiElement);
});
React uses a reconciliation algorithm to optimize Virtual DOM tree comparison:
React uses heuristics for fast tree comparison:
// React compares elements by type
function App({ isLoggedIn }) {
// If type changes, React removes and recreates
return isLoggedIn
? <div>Content for authorized users</div> // type: div
: <span>Content for guests</span>; // type: span
}
Keys help React efficiently update lists:
// ❌ Without keys - inefficient
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li>{todo.text}</li> // React can't track elements
))}
</ul>
);
}
// ✅ With keys - efficient
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> // React can track elements
))}
</ul>
);
}
React assumes elements with the same type and key are stable:
// React can reuse DOM elements
function App({ items }) {
return (
<div>
{items.map(item => (
<div key={item.id}>
<input value={item.text} />
<span>{item.text}</span>
</div>
))}
</div>
);
}
Virtual DOM minimizes operations with real DOM:
// Without Virtual DOM - multiple operations
for (let i = 0; i < 1000; i++) {
const element = document.createElement('div');
element.textContent = `Item ${i}`;
document.body.appendChild(element); // 1000 repaints
}
// With Virtual DOM - one batch operation
function App() {
const items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
return (
<div>
{items.map((item, index) => <div key={index}>{item}</div>)}
</div>
);
// One repaint after rendering the entire list
}
Virtual DOM hides the complexity of working with real DOM:
// Developer works with declarative code
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
// Virtual DOM takes care of optimizing updates
}
Virtual DOM allows using the same code on different platforms:
// The same component can work in browser and on mobile devices
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
// In browser - renders to HTML
// In React Native - renders to native components
Virtual DOM requires additional memory to store the virtual tree:
// Large application with many components
function LargeApp() {
return (
<div>
<Header />
<Navigation />
<MainContent />
<Sidebar />
<Footer />
{/* Each component creates virtual nodes */}
</div>
);
}
Comparing Virtual DOM trees requires computational resources:
// Complex components with many elements
function ComplexList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
<h3>{item.title}</h3>
<p>{item.description}</p>
<ul>
{item.tags.map(tag => (
<li key={tag}>{tag}</li>
))}
</ul>
</li>
))}
</ul>
);
// Comparing large trees can be costly
}
For simple applications, Virtual DOM may be overkill:
// Simple application without frequent updates
function StaticPage() {
return (
<div>
<h1>Static page</h1>
<p>This content rarely changes</p>
</div>
);
// Virtual DOM doesn't provide benefits here
}
Some frameworks use different approaches:
// Vue 3 Composition API (reactivity without Virtual DOM)
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
return { count, doubleCount, increment };
}
};
You can optimize DOM work manually:
// Using requestAnimationFrame for optimization
function optimizedUpdate(data) {
requestAnimationFrame(() => {
// Group DOM changes
const fragment = document.createDocumentFragment();
data.forEach(item => {
const element = document.createElement('div');
element.textContent = item.text;
fragment.appendChild(element);
});
document.getElementById('container').appendChild(fragment);
});
}
Virtual DOM is particularly useful in the following cases:
// Great for complex interfaces
function Dashboard({ realtimeData }) {
const [users, setUsers] = useState([]);
const [messages, setMessages] = useState([]);
const [notifications, setNotifications] = useState([]);
// Frequent data updates
useEffect(() => {
const interval = setInterval(() => {
// Update data every second
fetchData().then(data => {
setUsers(data.users);
setMessages(data.messages);
setNotifications(data.notifications);
});
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="dashboard">
<UserList users={users} />
<MessagePanel messages={messages} />
<NotificationPanel notifications={notifications} />
</div>
);
}
Virtual DOM is a powerful concept that solves key performance problems when working with real DOM:
✅ Main benefits:
✅ Key concepts:
✅ Best practices:
Virtual DOM has become a standard in modern frontend development and enables creating fast, maintainable, and scalable web applications.
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site, and improve yourself every day 💪