React Interview Questions 16-20 (Optimization, Lazy Loading, Side Effects, Routing)
Welcome to Part 4 of our React interview series. We have covered the basics, hooks, and data flow. Now, we turn our attention to two critical areas for senior-level discussions: Performance and Architecture.
React is fast by default, but as applications grow, you need specific tools to keep them responsive. We will discuss how to stop unnecessary re-renders, how to load code only when needed, how to render huge lists without crashing the browser, and how to handle navigation in a Single Page Application (SPA).
16. What are React.memo, useMemo, and useCallback?
These are all memoization tools used to optimize performance by caching results and preventing unnecessary re-computations or re-renders.
| Tool | What it memoizes | Use Case |
|---|---|---|
| React.memo | A whole Component | Prevents re-render if props haven't changed. |
| useMemo | The result of a function | Caching expensive calculations (e.g., sorting a huge list). |
| useCallback | The function definition itself | Preventing a function from being recreated on every render (useful when passing functions to child components). |
Analogy:
useMemo: Like remembering the answer to a hard math problem so you don't have to solve it again.
useCallback: Like laminating a instruction sheet so you don't have to write a new one every single day just to hand it to a worker.
// 1. React.memo (Wrap the component)
const Child = React.memo(({ name }) => {
console.log("Child Rendered");
return <div>{name}</div>;
});
const Parent = () => {
const [count, setCount] = useState(0);
// 2. useMemo (Cache a value)
const expensiveValue = useMemo(() => {
return count * 1000; // Imagine this is a heavy calculation
}, [count]);
// 3. useCallback (Cache a function)
// Without this, 'handleClick' is a NEW function every render,
// causing Child to re-render even if wrapped in React.memo!
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return (
<>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<Child name="Alice" onClick={handleClick} />
</>
);
};
17. What is lazy loading (React.lazy/Suspense)?
Lazy Loading (or Code Splitting) is a technique where you split your code into smaller bundles and only load the parts that are currently needed by the user.
By default, React bundles your entire app into one large JavaScript file. If the user is on the Homepage, they shouldn't have to download the code for the "Admin Dashboard." React.lazy allows you to import components dynamically, and Suspense lets you show a loading spinner while that component is being fetched from the server.
import React, { Suspense } from 'react';
// Lazy load the component (It won't be in the main bundle)
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<h1>My App</h1>
{/* Show fallback UI while the component code downloads */}
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
18. How do you optimize a list with thousands of items?
Rendering thousands of DOM nodes at once will crash or freeze the browser. The solution is Virtualization (or Windowing).
Concept: Instead of rendering 10,000 items, you only render the 10 items that fit on the user's screen (the "window"). As the user scrolls, you recycle those DOM nodes and fill them with the new data.
Libraries: react-window or react-virtualized are the industry standards for this.
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
Row {index}
</div>
);
const Example = () => (
<List
height={150} // Height of the container
itemCount={10000} // Total items
itemSize={35} // Height of each item
width={300}
>
{Row}
</List>
);
// Result: Only ~5 divs exist in the DOM at any time, but it feels like 10,000.
19. How do you handle Side Effects (Data Fetching) in React?
Data fetching is the most common side effect. It is typically handled inside the useEffect hook.
Key steps to mention:
- Loading State: Show a spinner while waiting.
- Error Handling: Catch network errors and show a message.
- Cleanup: (Bonus) Prevent setting state if the component unmounts before the data arrives (using flags or AbortController).
const UserList = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch("https://api.example.com/users", { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
})
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((err) => {
if (err.name !== "AbortError") {
setError(err.message);
setLoading(false);
}
});
return () => controller.abort(); // Cleanup on unmount
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
};
20. How does Routing work in React (React Router)?
React is usually used to build Single Page Applications (SPAs). Traditional websites request a new HTML file from the server for every page load. SPAs load one HTML file once, and then JavaScript (React Router) changes what is displayed on the screen.
React Router uses the browser's History API to update the URL without refreshing the page.
- BrowserRouter: Wraps the app to enable routing.
- Routes & Route: Define the mapping between a URL path and a Component.
- Link: Replaces the
<a>tag. It changes the URL without triggering a full page reload.
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const App = () => {
return (
<BrowserRouter>
<nav>
{/* Link prevents page reload */}
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Dynamic Route */}
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</BrowserRouter>
);
};
You have now covered the advanced performance and architectural aspects of React. Knowing how to memoize expensive calculations and virtualize long lists sets you apart as a developer who builds scalable applications, not just working ones. In the final section, we will discuss advanced patterns and testing.