React js Performance Optimization Practice Questions with Solutions

Introduction

React Performance Optimization means improving an application’s responsiveness by reducing unnecessary work and avoiding avoidable re-renders or expensive calculations. React applications do not need every component to be optimized, but performance techniques become useful when an application becomes larger or contains expensive rendering work. In this chapter, we will solve practical questions using techniques such as React.memo, useMemo, useCallback, efficient list rendering, and state management. React js Performance Optimization practice questions with solutions help to understand the concepts.

1. What is Performance Optimization in React?

Performance Optimization in React means reducing unnecessary work so that the application remains responsive and efficient.

For example, if a parent component re-renders, some child components may also render again even when their relevant props have not changed.

Optimization techniques can help in appropriate situations.

Common React performance tools include:

React.memo()
useMemo()
useCallback()

However, optimization should be based on an actual performance problem rather than added everywhere by default.


2. How can you avoid unnecessary calculations in React?

If a calculation is expensive, useMemo() can cache its result between renders.

Solution:

import { useMemo, useState } from "react";

function App() {
  const [number, setNumber] = useState(10);
  const [count, setCount] = useState(0);

  const squaredNumber = useMemo(() => {
    console.log("Calculation running");

    return number * number;
  }, [number]);

  return (
    <div>
      <h2>Square: {squaredNumber}</h2>

      <button onClick={() => setNumber(number + 1)}>
        Change Number
      </button>

      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
    </div>
  );
}

export default App;

The calculation is recomputed when number changes.

Changing count does not change the number dependency, so React can reuse the memoized calculation.

useMemo() should mainly be used when the calculation is expensive enough to justify memoization.


3. How does React.memo help improve performance?

React.memo() can prevent a component from re-rendering when its props have not changed.

Solution:

import { memo, useState } from "react";

const User = memo(function User({ name }) {
  console.log("User rendered");

  return <h3>{name}</h3>;
});

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>

      <User name="Rahul" />
    </div>
  );
}

export default App;

When count changes, the parent renders again.

Because the name prop remains the same, React.memo() can allow the User component to skip that render.

React.memo() compares props using Object.is by default.


4. How does useCallback help with performance?

useCallback() can preserve the same function reference between renders when its dependencies have not changed.

This can be useful when passing a callback to a memoized child component.

Solution:

import { memo, useCallback, useState } from "react";

const Button = memo(function Button({ onClick }) {
  console.log("Button rendered");

  return (
    <button onClick={onClick}>
      Add
    </button>
  );
});

function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount((currentCount) => currentCount + 1);
  }, []);

  return (
    <div>
      <h2>{count}</h2>

      <Button onClick={handleClick} />
    </div>
  );
}

export default App;

Without useCallback(), a new function could be created during each parent render.

Here, the callback reference remains stable, which can help React.memo() skip unnecessary child renders.

useCallback() itself is not automatically a performance improvement. It is useful when stable function identity matters.


5. How can you optimize a large list in React?

For a large list, use stable keys and avoid unnecessary work while rendering each item.

Solution:

import { memo } from "react";

const Product = memo(function Product({ product }) {
  return (
    <div>
      <h3>{product.name}</h3>
      <p>₹{product.price}</p>
    </div>
  );
});

function ProductList() {
  const products = [
    { id: 1, name: "Laptop", price: 60000 },
    { id: 2, name: "Mobile", price: 30000 },
    { id: 3, name: "Keyboard", price: 1500 }
  ];

  return (
    <div>
      {products.map((product) => (
        <Product
          key={product.id}
          product={product}
        />
      ))}
    </div>
  );
}

export default ProductList;

Important practices include:

  • Use stable unique keys.
  • Avoid expensive calculations inside every list item.
  • Keep unnecessary state updates under control.
  • Use memoization only when profiling shows it helps.
  • For extremely large lists, consider virtualization.

6. How can you prevent unnecessary state updates?

Update state only when the new value is actually different from what the application needs.

For example:

import { useState } from "react";

function App() {
  const [name, setName] = useState("Rahul");

  const updateName = () => {
    if (name !== "Rahul") {
      setName("Rahul");
    }
  };

  return (
    <div>
      <h2>{name}</h2>

      <button onClick={updateName}>
        Set Name
      </button>
    </div>
  );
}

export default App;

More importantly, avoid creating unnecessary state when a value can simply be calculated from existing props or state.

For example, instead of storing:

const [fullName, setFullName] = useState("");

when it can be derived from:

const fullName = firstName + " " + lastName;

you can calculate it directly.

This avoids maintaining duplicated state.


7. How can you optimize expensive filtering and sorting?

If filtering or sorting is expensive and the input data does not change frequently, useMemo() can be considered.

Solution:

import { useMemo, useState } from "react";

function ProductApp() {
  const [search, setSearch] = useState("");
  const [count, setCount] = useState(0);

  const products = [
    { id: 1, name: "Laptop", price: 60000 },
    { id: 2, name: "Mobile", price: 30000 },
    { id: 3, name: "Keyboard", price: 1500 },
    { id: 4, name: "Mouse", price: 800 }
  ];

  const filteredProducts = useMemo(() => {
    return products
      .filter((product) =>
        product.name
          .toLowerCase()
          .includes(search.toLowerCase())
      )
      .sort((a, b) => a.price - b.price);
  }, [products, search]);

  return (
    <div>
      <input
        value={search}
        onChange={(event) => setSearch(event.target.value)}
        placeholder="Search products"
      />

      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>

      {filteredProducts.map((product) => (
        <p key={product.id}>
          {product.name} - ₹{product.price}
        </p>
      ))}
    </div>
  );
}

export default ProductApp;

The filtering and sorting result is memoized based on its dependencies.

In a real application, products would often come from props, state, or fetched data rather than being recreated inside the component.


8. How can you reduce unnecessary re-renders caused by changing object props?

Objects are compared by reference. Creating a new object on every parent render can cause a memoized child to render again.

For example:

const user = {
  name: "Rahul"
};

<User user={user} />

If this object is recreated during every render, its reference changes.

You can use useMemo() when keeping the object reference stable is actually useful.

Solution:

import { memo, useMemo, useState } from "react";

const User = memo(function User({ user }) {
  console.log("User rendered");

  return <h3>{user.name}</h3>;
});

function App() {
  const [count, setCount] = useState(0);

  const user = useMemo(() => {
    return {
      name: "Rahul"
    };
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>

      <User user={user} />
    </div>
  );
}

export default App;

Now the user object keeps the same reference between renders.

Again, this optimization should be used when there is a meaningful performance reason.


9. How can state placement improve React performance?

Keeping state as close as possible to the components that need it can reduce unnecessary parent and sibling re-renders.

For example, if only one component needs a counter, you do not necessarily need to keep that counter in the application’s top-level component.

Solution:

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h3>{count}</h3>

      <button
        onClick={() =>
          setCount((currentCount) => currentCount + 1)
        }
      >
        Add
      </button>
    </div>
  );
}

function App() {
  return (
    <div>
      <h2>My Application</h2>

      <Counter />
    </div>
  );
}

export default App;

The counter state belongs to Counter, where it is needed.

This technique is sometimes called state colocation.

It can reduce the amount of the component tree affected by state changes.


10. How do you build a performance-conscious React product list?

You can combine several techniques while keeping the implementation simple.

Solution:

import {
  memo,
  useCallback,
  useMemo,
  useState
} from "react";

const Product = memo(function Product({
  product,
  onSelect
}) {
  return (
    <div>
      <h3>{product.name}</h3>
      <p>₹{product.price}</p>

      <button onClick={() => onSelect(product.id)}>
        Select
      </button>
    </div>
  );
});

function ProductApp() {
  const [search, setSearch] = useState("");
  const [selectedId, setSelectedId] = useState(null);

  const products = [
    { id: 1, name: "Laptop", price: 60000 },
    { id: 2, name: "Mobile", price: 30000 },
    { id: 3, name: "Keyboard", price: 1500 },
    { id: 4, name: "Mouse", price: 800 }
  ];

  const filteredProducts = useMemo(() => {
    return products.filter((product) =>
      product.name
        .toLowerCase()
        .includes(search.toLowerCase())
    );
  }, [search]);

  const handleSelect = useCallback((id) => {
    setSelectedId(id);
  }, []);

  return (
    <div>
      <h2>Product List</h2>

      <input
        value={search}
        onChange={(event) => setSearch(event.target.value)}
        placeholder="Search products"
      />

      {filteredProducts.map((product) => (
        <Product
          key={product.id}
          product={product}
          onSelect={handleSelect}
        />
      ))}

      {selectedId !== null && (
        <p>
          Selected Product ID: {selectedId}
        </p>
      )}
    </div>
  );
}

export default ProductApp;

This example demonstrates:

  • React.memo() for a child component.
  • useMemo() for derived filtering work.
  • useCallback() for a stable callback reference.
  • Stable key values.
  • State kept only where it is needed.

These techniques are not automatically required for every component. In real applications, use profiling and measurements to identify actual bottlenecks before adding optimization.

Key Takeaways

  • React performance optimization aims to reduce unnecessary work.
  • Do not optimize every component without a reason.
  • React.memo() can skip a component render when its props are unchanged.
  • useMemo() caches a calculation result between renders.
  • useCallback() caches a function reference between renders.
  • useMemo() and useCallback() are performance tools, not requirements for normal React code.
  • Stable unique keys are important when rendering lists.
  • Avoid unnecessary duplicated state when a value can be derived from existing data.
  • Keeping state close to where it is used can reduce unnecessary rendering work.
  • Objects, arrays, and functions are compared by reference when used as props.
  • Large lists may benefit from virtualization.
  • For reliable optimization decisions, use React’s profiling tools and actual performance measurements.

FAQs

1. What is React Performance Optimization?

React Performance Optimization involves reducing unnecessary rendering, calculations, state updates, and other work so that an application remains responsive.

2. Does React.memo improve performance automatically?

Not always. React.memo() can help when a component frequently receives the same props and re-rendering it is unnecessary. It also adds comparison work, so it should be used where it provides a real benefit.

3. What is the difference between useMemo and useCallback?

useMemo() memoizes a calculated value, while useCallback() memoizes a function reference.

Example:

const value = useMemo(() => calculate(), [data]);

const handleClick = useCallback(() => {
  doSomething();
}, []);

4. Should useMemo and useCallback be used everywhere?

No. They are optimization tools and can add complexity and their own overhead. Use them when they solve a measured or meaningful performance problem.

5. How can large lists affect React performance?

Rendering a very large number of elements can increase rendering and browser work. Stable keys, efficient item components, avoiding unnecessary calculations, pagination, and list virtualization can help depending on the application.

6. How does state placement affect React performance?

State changes cause the component that owns the state to render again. Keeping state close to the components that need it can limit how much of the component tree is affected.

7. How can you find performance problems in a React application?

You can use browser performance tools and React Developer Tools, including the React Profiler, to identify components that render frequently or take significant time. Optimization should be based on actual measurements rather than assumptions.

Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.

Scroll to Top