React Rendering Interview Questions & Detailed Answers
1. What is the difference between Legacy Mode and Concurrent Mode in React?
Answer:
Legacy Mode (Synchronous):
Uses
ReactDOM.render()
Renders synchronously in one blocking operation
Cannot be interrupted once started
Can freeze the UI during large updates
All updates have equal priority
Concurrent Mode (Asynchronous):
Uses
ReactDOM.createRoot()
Breaks rendering into interruptible chunks
Can pause and resume work based on priority
Maintains UI responsiveness
Uses priority-based scheduling
Key Code Difference:
// Legacy Mode
ReactDOM.render(<App />, document.getElementById('root'));
// Concurrent Mode
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Impact: Concurrent mode prevents the main thread from being blocked, allowing for smoother user interactions and animations.
2. Explain React's Fiber architecture and why it was introduced.
Answer:
What is Fiber: Fiber is React's reconciliation algorithm introduced in React 16. It's a complete rewrite of React's core algorithm.
Key Features:
Incremental Rendering: Break work into chunks
Ability to Pause and Resume: Work can be interrupted
Priority Assignment: Different updates can have different priorities
Better Error Boundaries: Improved error handling
Fiber Node Structure:
const fiberNode = {
type: 'div', // Component type
props: {...}, // Component props
child: fiberNode, // First child
sibling: fiberNode, // Next sibling
return: fiberNode, // Parent fiber
alternate: fiberNode, // Previous version (for comparison)
effectTag: 'UPDATE', // What needs to be done
// ... more properties
}
Why Fiber was Needed:
Problem: React 15's stack reconciler was synchronous and blocking
Solution: Fiber allows React to:
Pause expensive operations
Prioritize urgent updates (user input)
Improve perceived performance
3. What are the two main phases of React rendering? Explain each.
Answer:
React rendering has two distinct phases:
1. Render Phase (Reconciliation)
Purpose: Figure out what needs to change
Characteristics:
Can be interrupted in Concurrent Mode
Pure (no side effects)
Can be paused, aborted, or restarted
What happens:
Create/update fiber tree
Compare new tree with previous (diffing)
Mark nodes with effects (UPDATE, DELETE, PLACEMENT)
// Render phase functions (can be called multiple times)
function MyComponent() {
console.log('Render phase - might run multiple times');
return <div>Hello</div>;
}
2. Commit Phase
Purpose: Apply changes to the DOM
Characteristics:
Always synchronous (never interrupted)
Has side effects
Runs exactly once per update
What happens:
Execute all DOM mutations
Run lifecycle methods (useEffect, componentDidMount)
Update refs
useEffect(() => {
// Commit phase - runs exactly once after DOM updates
console.log('Commit phase - DOM is updated');
}, []);
Visual Flow:
State Change → Render Phase → Commit Phase → DOM Updated
↓ ↓
(Interruptible) (Synchronous)
4. How does React's priority system work? What are lanes?
Answer:
Lanes System: React uses a "lanes" model for priority scheduling, where each lane represents a priority level using binary flags.
Lane Priorities (from highest to lowest):javascript
// Simplified lane values
const SyncLane = 0b0000000000000000000000000000001; // 1
const InputContinuousLane = 0b0000000000000000000000000100; // 4
const DefaultLane = 0b0000000000000000000001000000; // 64
const TransitionLane = 0b0000000000000010000000000000; // 8192
const IdleLane = 0b0100000000000000000000000000000000; // 1073741824
Priority Levels Explained:
PriorityUse CaseExampleInterruptionSyncCritical updatesUser input, focusNeverInputContinuousContinuous inputDrag, hoverRarelyDefaultNormal updatessetState callsYesTransitionLarge updatesRoute changesYesIdleBackground workAnalyticsAlways
Real-world Example:
function App() {
const [urgent, setUrgent] = useState('');
const [heavy, setHeavy] = useState([]);
// High priority - user typing
const handleInput = (e) => {
setUrgent(e.target.value); // Sync priority
};
// Low priority - expensive operation
const handleHeavyWork = () => {
startTransition(() => { // Transition priority
setHeavy(generateLargeList());
});
};
return (
<div>
<input onChange={handleInput} value={urgent} />
<button onClick={handleHeavyWork}>Heavy Work</button>
<ExpensiveList data={heavy} />
</div>
);
}
5. What is time slicing in React? How does it work?
Answer:
Time Slicing Definition: Time slicing is React's ability to break rendering work into small chunks and spread them across multiple frames, yielding control back to the browser between chunks.
How It Works:
Frame Budget: React aims to keep each chunk under 5ms
Yielding: After each chunk, React checks if it should yield to the browser
Resumption: Work continues in the next available frame
Visual Representation:
Without Time Slicing (Legacy):
Frame 1: [████████████████████████████████] (32ms - BLOCKS)
Frame 2: [ ] (Browser catches up)
With Time Slicing (Concurrent):
Frame 1: [█████] React (5ms) + [███████████] Browser (11ms)
Frame 2: [█████] React (5ms) + [███████████] Browser (11ms)
Frame 3: [█████] React (5ms) + [███████████] Browser (11ms)
Implementation Details:
function workLoopConcurrent() {
// Continue working while there's work and time budget
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function shouldYield() {
// Check if we've exceeded our time budget (5ms)
return getCurrentTime() >= deadline;
}
Benefits:
Smooth animations (60fps maintained)
Responsive user interactions
No "frozen" UI during large updates
Better perceived performance
6. Explain React's reconciliation process.
Answer:
Reconciliation Definition: Reconciliation is the process where React compares the new element tree with the previous one and determines what changes need to be made to the DOM.
The Diffing Algorithm:
1. Element Type Comparison:
// Different types → destroy and recreate
<div>Content</div> → <span>Content</span>
// Result: Destroy div, create new span
// Same type → update props
<div className="old">Content</div> → <div className="new">Content</div>
// Result: Update className only
2. Component Comparison:
// Same component type → update props
<MyComponent name="old" /> → <MyComponent name="new" />
// Result: Re-render MyComponent with new props
// Different component type → unmount and mount
<ComponentA /> → <ComponentB />
// Result: Unmount A, mount B
3. Keys for List Reconciliation:
// Without keys (inefficient)
['A', 'B', 'C'] → ['A', 'X', 'B', 'C']
// React thinks: B→X, C→B, add C (3 operations)
// With keys (efficient)
[
{key: 'a', text: 'A'},
{key: 'b', text: 'B'},
{key: 'c', text: 'C'}
] → [
{key: 'a', text: 'A'},
{key: 'x', text: 'X'},
{key: 'b', text: 'B'},
{key: 'c', text: 'C'}
]
// React knows: Insert X, keep others (1 operation)
Reconciliation Steps:
Tree Comparison: Compare element trees
Effect Marking: Mark fibers with required changes
Effect Collection: Collect all effects in lists
DOM Application: Apply effects during commit phase
7. What are React's scheduling priorities and when are they used?
Answer:
Priority Levels and Use Cases:
1. Immediate/Sync Priority
When: Discrete user events (click, keypress, focus)
Behavior: Never interrupted, executes immediately
Example:
// Button click - sync priority
<button onClick={() => setCount(count + 1)}>
Click me
</button>
2. User Blocking Priority
When: Continuous user events (drag, scroll, hover)
Behavior: High priority, rarely interrupted
Example:
// Mouse move - user blocking priority
<div onMouseMove={(e) => setMousePos({x: e.clientX, y: e.clientY})}>
Drag me
</div>
3. Normal Priority
When: Network responses, timers, default updates
Behavior: Normal priority, can be interrupted
Example:
// Timer update - normal priority
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
4. Low Priority
When: Data fetching, analytics, non-urgent updates
Behavior: Can be interrupted by higher priorities
Example:
// Data fetch response - low priority
useEffect(() => {
fetchData().then(data => {
// This update has low priority
setData(data);
});
}, []);
5. Idle Priority
When: Background work, prefetching, cleanup
Behavior: Only runs when browser is idle
Example:
// Background analytics - idle priority
scheduler.unstable_scheduleCallback(
scheduler.unstable_IdlePriority,
() => {
sendAnalytics(userBehaviorData);
}
);
Priority Override Example:
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// High priority - user typing
const handleInput = (e) => {
setQuery(e.target.value);
};
// Low priority - search results
const handleSearch = (query) => {
startTransition(() => {
const searchResults = expensiveSearch(query);
setResults(searchResults);
});
};
return (
<div>
<input onChange={handleInput} value={query} />
<SearchResults results={results} />
</div>
);
}
8. How does startTransition
work and when should you use it?
Answer:
What is startTransition: startTransition
is a React 18 API that marks updates as non-urgent, allowing React to prioritize more important updates.
How It Works:
import { startTransition } from 'react';
function App() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const handleChange = (e) => {
// Urgent: User typing - high priority
setInput(e.target.value);
// Non-urgent: Expensive filtering - low priority
startTransition(() => {
const filtered = expensiveFilter(data, e.target.value);
setList(filtered);
});
};
return (
<div>
<input value={input} onChange={handleChange} />
<ExpensiveList data={list} />
</div>
);
}
What Happens:
Input Update: Executes immediately (high priority)
Transition Update: Queued with low priority
If New Input: Transition update is interrupted and restarted
Result: Input feels responsive, list updates when possible
When to Use startTransition:
✅ Good Use Cases:
Large List Filtering: Expensive search/filter operations
Route Transitions: Navigation between pages
Heavy Calculations: CPU-intensive computations
Non-critical UI Updates: Charts, analytics dashboards
❌ Don't Use For:
User Input: Text inputs, form fields
Hover/Focus Effects: Immediate feedback required
Critical Animations: Loading spinners, progress bars
useTransition Hook:
import { useTransition } from 'react';
function SearchComponent() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (newQuery) => {
setQuery(newQuery); // Immediate
startTransition(() => {
// This will show isPending as true
setResults(performExpensiveSearch(newQuery));
});
};
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
{isPending && <div>Searching...</div>}
<SearchResults results={results} />
</div>
);
}
9. What is the difference between ReactDOM.render
and ReactDOM.createRoot
?
Answer:
ReactDOM.render (Legacy Mode):
// Legacy API
ReactDOM.render(<App />, document.getElementById('root'));
// Characteristics:
// - Synchronous rendering
// - Blocking updates
// - No concurrent features
// - Immediate DOM updates
ReactDOM.createRoot (Concurrent Mode):
// Modern API
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// Characteristics:
// - Asynchronous rendering
// - Interruptible updates
// - Priority-based scheduling
// - Time slicing enabled
Key Differences:
Migration Example:
// Before (Legacy)
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(c => c + 1);
setCount(c => c + 1); // Two separate renders
};
return <button onClick={handleClick}>{count}</button>;
}
ReactDOM.render(<App />, root);
// After (Concurrent)
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(c => c + 1);
setCount(c => c + 1); // Automatically batched!
};
return <button onClick={handleClick}>{count}</button>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
10. Explain React's commit phase in detail.
Answer:
Commit Phase Overview: The commit phase is where React applies all changes to the DOM. It's always synchronous and cannot be interrupted.
Three Sub-phases of Commit:
1. Before Mutation Phase:
Purpose: Prepare for DOM changes
What happens:
Capture snapshots (getSnapshotBeforeUpdate)
Schedule effects
Prepare focus management
class MyComponent extends Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// Called before DOM is updated
// Return value passed to componentDidUpdate
return { scrollPosition: window.scrollY };
}
}
2. Mutation Phase:
Purpose: Apply DOM changes
What happens:
Insert, update, delete DOM nodes
Update refs
Handle focus/blur
// DOM operations performed:
// - appendChild() for new elements
// - removeChild() for deleted elements
// - setAttribute() for prop changes
// - textContent updates for text changes
3. Layout Phase:
Purpose: Run side effects after DOM updates
What happens:
componentDidMount/componentDidUpdate
useLayoutEffect callbacks
Ref callbacks
useLayoutEffect(() => {
// Runs synchronously after DOM mutations
// but before browser paint
const rect = elementRef.current.getBoundingClientRect();
setDimensions({ width: rect.width, height: rect.height });
}, []);
Commit Process Flow:
Render Phase Complete
↓
Before Mutation
↓
DOM Mutations ← (appendChild, removeChild, etc.)
↓
Layout Effects ← (useLayoutEffect, componentDidMount)
↓
Passive Effects ← (useEffect - scheduled for next tick)
Effect Types:
function MyComponent() {
// Layout Effect (synchronous)
useLayoutEffect(() => {
// Runs before browser paint
// Use for DOM measurements
}, []);
// Passive Effect (asynchronous)
useEffect(() => {
// Runs after browser paint
// Use for side effects, data fetching
}, []);
return <div>Content</div>;
}
Why Commit is Synchronous:
Consistency: Ensures DOM is updated atomically
User Experience: Prevents partial updates from being visible
Browser Behavior: Matches browser's rendering expectations
Bonus: Performance Optimization Questions
Q: How do you identify rendering performance issues in React?
Answer:
React DevTools Profiler: Identify slow components
Browser DevTools: Check frame rates, main thread blocking
Performance Metrics: Measure FCP, LCP, FID
Code Analysis: Look for unnecessary re-renders
Q: What are some best practices for React rendering performance?
Answer:
Use React.memo(): Prevent unnecessary re-renders
Optimize useEffect dependencies: Avoid infinite loops
Use startTransition: For non-urgent updates
Code splitting: Lazy load components
Virtualization: For large lists
Proper key usage: In lists for efficient reconciliation