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).
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} />
</>
);
};
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>
);
}
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.
Data fetching is the most common side effect. It is typically handled inside the useEffect hook.
Key steps to mention:
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>;
};
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.
<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.
3 questions · no sign-up, nothing stored
Progress is stored in this browser only - no sign-up, nothing sent anywhere.