react

Practice top 10 react coding interview questions before every reactjs interviews

13 min read
#react

React has become the go-to library for building modern web applications, and cracking frontend interviews requires more than just theoretical knowledge—you need to practice real-world coding challenges. Whether you're just starting out or already have experience, practicing interview-style questions is key to reinforcing your React fundamentals.

We've compiled a curated list of the most frequently asked React and frontend interview questions. These range from basic UI tasks to advanced interactive scenarios—all aimed at helping you gain confidence and get hired for a frontend role.

A debounced search box is a search input field where the search operation (like fetching suggestions or querying a database) is delayed until the user has stopped typing for a certain amount of time — typically a few hundred milliseconds.

Why use debouncing?

Without debouncing:

  • Every keystroke triggers a search request.
  • This can overwhelm the server and make the app laggy.

With debouncing:

  • The search function is only called after the user stops typing for a specified time (e.g., 300ms).
  • Reduces the number of function calls and improves performance.

In our example we will build a search input that waits 500ms after the user stops typing before triggering a function (like an API call).

import React, { useState, useEffect } from "react";
function DebouncedSearch() {
  const [query, setQuery] = useState("");
  const [debouncedQuery, setDebouncedQuery] = useState("");
  // Debounce effect
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedQuery(query);
    }, 500);

    return () => {
      clearTimeout(timer); // Cleanup on query change
    };
  }, [query]);
  // Do something with debounced query
  useEffect(() => {
    if (debouncedQuery) {
      console.log("Calling API with:", debouncedQuery);
      // Simulate API call here
    }
  }, [debouncedQuery]);

  return (
    <div className="p-4">
      <input
        type="text"
        placeholder="Search..."
        className="border p-2 rounded w-full"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
    </div>
  );
}
export default DebouncedSearch;

query updates instantly as the user types. We have used setTimeout which waits for 500 ms after every keystroke.
After 500ms of inactivity, it sets debouncedQuery. Another useEffect triggers whenever debouncedQuery changes, simulating the API call.

2. React Accordion Component

An accordion is a UI component where:

  • You have multiple sections (items).
  • Clicking one expands it to show content.
  • Clicking again (or clicking another) collapses it.
import React, { useState } from "react";

const accordionData = [
  {
    title: "What is React?",
    content: "React is a JavaScript library for building user interfaces.",
  },
  {
    title: "Why use React?",
    content: "It makes creating interactive UIs painless and efficient.",
  },
  {
    title: "How do you use React?",
    content:
      "By creating components and managing state using hooks like useState.",
  },
];

function Accordion() {
  const [activeIndex, setActiveIndex] = useState(null); // Track open item

  const toggleAccordion = (index) => {
    if (activeIndex === index) {
      setActiveIndex(null); // Collapse if clicking same item
    } else {
      setActiveIndex(index); // Open new item
    }
  };

  return (
    <div style={{ maxWidth: "600px", margin: "auto", padding: "1rem" }}>
      {accordionData.map((item, index) => (
        <div
          key={index}
          style={{
            marginBottom: "10px",
            border: "1px solid #ccc",
            borderRadius: "6px",
          }}
        >
          <div
            onClick={() => toggleAccordion(index)}
            style={{
              cursor: "pointer",
              padding: "10px 15px",
              background: activeIndex === index ? "#f0f0f0" : "#e0e0e0",
              fontWeight: "bold",
            }}
          >
            {item.title}
          </div>
          {activeIndex === index && (
            <div style={{ padding: "10px 15px", background: "#fafafa" }}>
              {item.content}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

export default Accordion;

3. Form Validation with controlled component

What is a Controlled Component?

A controlled component is an input element (like <input>, <textarea>, <select>) whose value is controlled by React state.

Controlled vs Uncontrolled

ConceptControlledUncontrolled
Value sourceReact useState()DOM manages the input value
Data flowOne-way (React → DOM)Two-way (React ←→ DOM)
Input valuevalue={state}defaultValue only
UpdatesonChange updates state manuallyDOM updates it on its own

Controlled components are preferred in React because they give full control over the form's behavior, including validation and submission.

import React, { useState } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!email) newErrors.email = "Email is required";
    else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = "Email is invalid";

    if (!password) newErrors.password = "Password is required";
    else if (password.length < 6)
      newErrors.password = "Password must be at least 6 characters";

    return newErrors;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const validationErrors = validate();
    setErrors(validationErrors);

    if (Object.keys(validationErrors).length === 0) {
      console.log("Submitting:", { email, password });
      // Send data to API
    }
  };

  return (
    <form
      onSubmit={handleSubmit}
      style={{ maxWidth: "400px", margin: "auto", padding: "1rem" }}
    >
      <h2>Login</h2>

      <div style={{ marginBottom: "1rem" }}>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)} // controlled input
          style={{ display: "block", width: "100%", padding: "8px" }}
        />
        {errors.email && <p style={{ color: "red" }}>{errors.email}</p>}
      </div>

      <div style={{ marginBottom: "1rem" }}>
        <label>Password:</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)} // controlled input
          style={{ display: "block", width: "100%", padding: "8px" }}
        />
        {errors.password && <p style={{ color: "red" }}>{errors.password}</p>}
      </div>

      <button type="submit" style={{ padding: "8px 16px" }}>
        Login
      </button>
    </form>
  );
}

export default LoginForm;

React controls the input value. Whatever is typed is captured via onChange and stored in state.

4. Light/Dark Theme Toggle Button

import React, { useState } from "react";
import "./App.css"; // We'll define theme styles here

function ThemeToggle() {
  const [isDark, setIsDark] = useState(false);

  const toggleTheme = () => {
    setIsDark((prev) => !prev);
  };

  return (
    <div className={isDark ? "app dark" : "app light"}>
      <h1>{isDark ? "Dark Mode 🌙" : "Light Mode ☀️"}</h1>
      <button onClick={toggleTheme}>
        Switch to {isDark ? "Light" : "Dark"} Mode
      </button>
    </div>
  );
}

export default ThemeToggle;
.app {
  padding: 2rem;
  text-align: center;
  transition: background 0.3s ease, color 0.3s ease;
}

.light {
  background-color: #ffffff;
  color: #222;
}

.dark {
  background-color: #1a1a1a;
  color: #f1f1f1;
}

button {
  margin-top: 1rem;
  padding: 0.5rem 1.2rem;
  font-size: 1rem;
  border: none;
  cursor: pointer;
  border-radius: 8px;
  background-color: #444;
  color: white;
}

.light button {
  background-color: #222;
}

.dark button {
  background-color: #ddd;
  color: #222;
}

5. Modal popup

A basic modal system where:

A "Show Modal" button opens the modal.

Modal can be closed by:

Clicking the close button inside it.

Clicking outside the modal (on the backdrop/overlay).

import React, { useState } from "react";
import "./ModalPopup.css";

function Modal({ onClose, children }) {
  // Prevent clicks inside modal from closing it
  const handleModalClick = (e) => {
    e.stopPropagation(); // 🚫 Stops event bubbling to overlay
  };

  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={handleModalClick}>
        <button className="close-btn" onClick={onClose}>
          X
        </button>
        {children}
      </div>
    </div>
  );
}

export default function ModalPopup() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div className="container">
      <h1>Modal Popup Demo</h1>
      <button onClick={() => setShowModal(true)}>Show Modal</button>

      {showModal && (
        <Modal onClose={() => setShowModal(false)}>
          <h2>Hello from the Modal 👋</h2>
          <p>This is a reusable modal component!</p>
        </Modal>
      )}
    </div>
  );
}

ModalPopup.css

.container {
  text-align: center;
  padding: 3rem;
}

button {
  padding: 10px 20px;
  margin-top: 1rem;
  font-size: 16px;
}

.overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.6);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 999;
}

.modal {
  background: white;
  padding: 2rem;
  border-radius: 10px;
  position: relative;
  width: 400px;
  max-width: 90%;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
}

.close-btn {
  position: absolute;
  right: 10px;
  top: 10px;
  background: crimson;
  color: white;
  border: none;
  border-radius: 50%;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}

Clicking outside the modal calls onClose() via the overlay:

<div className="overlay" onClick={onClose}>

Clicking inside the modal should NOT close it — so we stop bubbling:

<div className="modal" onClick={e => e.stopPropagation()}>

Event bubbling: when a click inside a child element propagates ("bubbles") up to its parent. Why we stop it: So clicks inside the modal don’t trigger the overlay's onClick.

6. Drag & Drop Reorderable List

Each list item is a self-contained draggable element. We're composing a parent <ul> with several <li>s — making this clean and modular.

DragAndDropList.jsx

import React, { useState } from "react";
import "./DragAndDropList.css";

const initialItems = ["Apple", "Banana", "Grape", "Orange", "Mango"];

export default function DragAndDropList() {
  const [items, setItems] = useState(initialItems);
  const [draggedIndex, setDraggedIndex] = useState(null);

  const handleDragStart = (index) => {
    setDraggedIndex(index);
  };

  const handleDragOver = (e) => {
    e.preventDefault(); // Required to allow drop
  };

  const handleDrop = (index) => {
    if (draggedIndex === null || draggedIndex === index) return;

    const updated = [...items];
    const [movedItem] = updated.splice(draggedIndex, 1);
    updated.splice(index, 0, movedItem);

    setItems(updated);
    setDraggedIndex(null);
  };

  return (
    <div className="drag-container">
      <h2>Pure React Drag & Drop List</h2>
      <ul className="item-list">
        {items.map((item, index) => (
          <li
            key={item}
            draggable
            onDragStart={() => handleDragStart(index)}
            onDragOver={handleDragOver}
            onDrop={() => handleDrop(index)}
            className="item"
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

DragAndDropList.css

.drag-container {
  max-width: 400px;
  margin: 3rem auto;
  text-align: center;
  font-family: sans-serif;
}

.item-list {
  list-style: none;
  padding: 0;
}

.item {
  background-color: #f0f0f0;
  padding: 1rem;
  margin: 0.5rem 0;
  border-radius: 6px;
  cursor: grab;
  transition: background 0.2s;
}

.item:active {
  cursor: grabbing;
  background-color: #e0e0e0;
}

7. Pagination Component

Pagination Component in React that displays a list of items with "Previous" and "Next" buttons — and shows only a subset of items per page.

Pagination.jsx

import React, { useState } from "react";
import "./Pagination.css";

const items = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`);
const itemsPerPage = 5;

export default function Pagination() {
  const [currentPage, setCurrentPage] = useState(1);

  const totalPages = Math.ceil(items.length / itemsPerPage);

  const startIndex = (currentPage - 1) * itemsPerPage;
  const endIndex = startIndex + itemsPerPage;
  const visibleItems = items.slice(startIndex, endIndex);

  const goToPrev = () => {
    setCurrentPage((prev) => Math.max(prev - 1, 1));
  };

  const goToNext = () => {
    setCurrentPage((prev) => Math.min(prev + 1, totalPages));
  };

  return (
    <div className="pagination-container">
      <h2>Pagination Example</h2>
      <ul className="item-list">
        {visibleItems.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <div className="controls">
        <button onClick={goToPrev} disabled={currentPage === 1}>
          Prev
        </button>
        <span>
          Page {currentPage} of {totalPages}
        </span>
        <button onClick={goToNext} disabled={currentPage === totalPages}>
          Next
        </button>
      </div>
    </div>
  );
}

Pagination.css

.pagination-container {
  max-width: 400px;
  margin: 2rem auto;
  font-family: sans-serif;
  text-align: center;
}

.item-list {
  list-style: none;
  padding: 0;
  margin-bottom: 1rem;
}

.item-list li {
  background: #f0f0f0;
  margin: 0.5rem 0;
  padding: 0.75rem;
  border-radius: 6px;
}

.controls {
  display: flex;
  justify-content: center;
  gap: 1rem;
  align-items: center;
}

.controls button {
  padding: 0.5rem 1rem;
  font-size: 1rem;
}

8. File tree component

FileTree.js

import React, { useState } from "react";

function FileTree({ data, level = 0 }) {
  const [expandedDirs, setExpandedDirs] = useState({});

  const toggleDir = (id) => {
    setExpandedDirs((prev) => ({
      ...prev,
      [id]: !prev[id],
    }));
  };

  const sortItems = (items) => {
    const dirs = items
      .filter((item) => item.children)
      .sort((a, b) => a.name.localeCompare(b.name));
    const files = items
      .filter((item) => !item.children)
      .sort((a, b) => a.name.localeCompare(b.name));
    return [...dirs, ...files];
  };

  return (
    <div>
      {sortItems(data).map((item) => (
        <div key={item.id} style={{ paddingLeft: `${level * 20}px` }}>
          {item.children ? (
            <div>
              <div
                onClick={() => toggleDir(item.id)}
                style={{ cursor: "pointer", fontWeight: "bold" }}
              >
                {expandedDirs[item.id] ? "📂" : "📁"} {item.name}
              </div>
              {expandedDirs[item.id] && item.children.length > 0 && (
                <FileTree data={item.children} level={level + 1} />
              )}
            </div>
          ) : (
            <div>📄 {item.name}</div>
          )}
        </div>
      ))}
    </div>
  );
}

export default FileTree;

App.jsx

import React from "react";
import FileTree from "./FileTree";

const fileData = [
  {
    id: 1,
    name: "README.md",
  },
  {
    id: 2,
    name: "Documents",
    children: [
      {
        id: 3,
        name: "Word.doc",
      },
      {
        id: 4,
        name: "Powerpoint.ppt",
      },
    ],
  },
];

function App() {
  return (
    <div>
      <h1>File Explorer</h1>
      <FileTree data={fileData} />
    </div>
  );
}

export default App;

9. Nested checkbox component

import React, { useEffect, useRef, useState } from "react";

const CheckboxTree = ({ data }) => {
  const [treeData, setTreeData] = useState([]);

  useEffect(() => {
    const initializeTree = (nodes) =>
      nodes.map((node) => ({
        ...node,
        checked: false,
        children: node.children ? initializeTree(node.children) : undefined,
      }));
    setTreeData(initializeTree(data));
  }, [data]);

  const updateChildren = (node, checked) => ({
    ...node,
    checked,
    children: node.children?.map((child) => updateChildren(child, checked)),
  });

  const updateParentState = (nodes) => {
    return nodes.map((node) => {
      if (node.children) {
        const updatedChildren = updateParentState(node.children);
        const checkedStates = updatedChildren.map((child) => child.checked);

        let newChecked;
        if (checkedStates.every((val) => val === true)) {
          newChecked = true;
        } else if (checkedStates.every((val) => val === false)) {
          newChecked = false;
        } else {
          newChecked = "indeterminate";
        }

        return {
          ...node,
          checked: newChecked,
          children: updatedChildren,
        };
      }
      return node;
    });
  };

  const handleToggle = (id, checked, nodes) => {
    return nodes.map((node) => {
      if (node.id === id) {
        return updateChildren(node, checked);
      }
      if (node.children) {
        return {
          ...node,
          children: handleToggle(id, checked, node.children),
        };
      }
      return node;
    });
  };

  const onChange = (id, checked) => {
    const updated = handleToggle(id, checked, treeData);
    const finalTree = updateParentState(updated);
    setTreeData(finalTree);
  };

  return (
    <div>
      {treeData.map((node) => (
        <CheckboxNode key={node.id} node={node} onChange={onChange} />
      ))}
    </div>
  );
};

const CheckboxNode = ({ node, onChange }) => {
  const checkboxRef = useRef(null);

  useEffect(() => {
    if (checkboxRef.current) {
      checkboxRef.current.indeterminate = node.checked === "indeterminate";
    }
  }, [node.checked]);

  const isChecked = node.checked === true;

  return (
    <div style={{ marginLeft: 20 }}>
      <label>
        <input
          type="checkbox"
          ref={checkboxRef}
          checked={isChecked}
          onChange={(e) => onChange(node.id, e.target.checked)}
        />
        {node.name}
      </label>
      {node.children?.map((child) => (
        <CheckboxNode key={child.id} node={child} onChange={onChange} />
      ))}
    </div>
  );
};

export default CheckboxTree;

App.jsx

import React from "react";
import CheckboxTree from "./CheckboxTree";

export function App(props) {
  const fileData = [
    {
      id: 1,
      name: "Electronics",
      children: [
        {
          id: 2,
          name: "Mobile phones",
          children: [
            { id: 3, name: "iPhone" },
            { id: 4, name: "Android" },
          ],
        },
        {
          id: 5,
          name: "Laptops",
          children: [
            { id: 6, name: "MacBook" },
            { id: 7, name: "Surface Pro" },
          ],
        },
      ],
    },
    {
      id: 8,
      name: "Books",
      children: [
        { id: 9, name: "Fiction" },
        { id: 10, name: "Non-fiction" },
      ],
    },
    { id: 11, name: "Toys" },
  ];
  return (
    <div className="App">
      <CheckboxTree data={fileData} />
    </div>
  );
}
import React, { useState } from "react";
import "./ImageCarousel.css"; // Import CSS styles

const ImageCarousel = ({ images }) => {
  const [current, setCurrent] = useState(0);
  const total = images.length;

  const nextImage = () => setCurrent((prev) => (prev + 1) % total);
  const prevImage = () => setCurrent((prev) => (prev - 1 + total) % total);
  const goToImage = (index) => setCurrent(index);

  return (
    <div className="carousel-container">
      <div className="carousel">
        <img
          src={images[current]}
          alt={`Slide ${current + 1}`}
          className="carousel-image"
        />

        <button className="nav-button left" onClick={prevImage}>
          &#10094;
        </button>
        <button className="nav-button right" onClick={nextImage}>
          &#10095;
        </button>
      </div>

      <div className="dot-container">
        {images.map((_, index) => (
          <button
            key={index}
            className={`dot ${index === current ? "active" : ""}`}
            onClick={() => goToImage(index)}
          ></button>
        ))}
      </div>
    </div>
  );
};

export default ImageCarousel;

ImageCarousel.css

.carousel-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: black;
  padding: 1rem;
  box-sizing: border-box;
}

.carousel {
  position: relative;
  width: 100%;
  max-width: 600px;
  max-height: 400px;
  background-color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.carousel-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background-color: black;
}

.nav-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 2rem;
  color: white;
  background: rgba(0, 0, 0, 0.3);
  border: none;
  padding: 0.5rem 1rem;
  cursor: pointer;
  z-index: 1;
}

.nav-button.left {
  left: 10px;
}

.nav-button.right {
  right: 10px;
}

.dot-container {
  margin-top: 10px;
  display: flex;
  gap: 8px;
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: gray;
  border: none;
  cursor: pointer;
}

.dot.active {
  background: white;
}

App.jsx

import React from "react";
import ImageCarousel from "./ImageCarousel";

const imageUrls = [
  "https://via.placeholder.com/600x400?text=Image+1",
  "https://via.placeholder.com/600x400?text=Image+2",
  "https://via.placeholder.com/600x400?text=Image+3",
  "https://via.placeholder.com/600x400?text=Image+4",
];

function App() {
  return <ImageCarousel images={imageUrls} />;
}

export default App;