Frontend · JavaScript · React 2026

React.js & JavaScript
Interview Questions India 2026

Top 70 questions with answers and code — closures, event loop, Promises, async/await, React hooks, virtual DOM, state management, and performance optimization.

✍️ Pranjal Jain, Ex-Microsoft · IIT Kanpur 📅 June 8, 2026 ⏱ 28 min read 70 Q&A with code

Core JavaScript (Q1–Q25)

1
What is the difference between var, let, and const?

var: function-scoped, hoisted and initialized to undefined, can be re-declared. let: block-scoped, hoisted but NOT initialized (Temporal Dead Zone until declaration), no re-declaration in same scope. const: same as let but binding cannot be reassigned (the object itself can be mutated).

var x = 1; var x = 2; // OK
let y = 1; let y = 2; // SyntaxError
const obj = { a: 1 }; obj.a = 2; // OK — mutating the object
const obj = {}; // TypeError — re-assigning binding

Rule of thumb: Use const by default. Use let when you need to reassign. Never use var in modern JS.

2
Explain JavaScript closures with an example.

A closure is a function that retains access to its outer lexical scope even after the outer function has returned. This happens because inner functions hold a reference to the outer scope's variable environment.

function makeCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2  — closure preserved count

Classic interview trick: the classic var loop closure bug — all callbacks share the same var i. Fix: use let in the loop or an IIFE to capture a fresh binding per iteration.

3
What is the JavaScript Event Loop? Explain microtasks vs macrotasks.

JS is single-threaded. The event loop coordinates:

  • Call Stack — executes synchronous code frame by frame.
  • Microtask Queue — Promises (.then/.catch/.finally), queueMicrotask, MutationObserver.
  • Task Queue (Macrotask)setTimeout, setInterval, I/O, UI events.

Rule: After every macrotask, drain the ENTIRE microtask queue before running the next macrotask.

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
4
What is prototypal inheritance in JavaScript?

Every JS object has an internal [[Prototype]] link to another object. When you access a property that doesn't exist on the object, JS walks up the prototype chain until it finds it or hits null.

const animal = { eat() { return 'eating'; } };
const dog = Object.create(animal); // dog's prototype = animal
dog.bark = () => 'woof';
dog.eat(); // 'eating' — found up the chain
dog.bark(); // 'woof' — own property

class syntax in ES6 is syntactic sugar over prototypal inheritance. Internally class Dog extends Animal sets Dog.prototype.__proto__ = Animal.prototype.

5
What is the difference between call(), apply(), and bind()?

All three set the this context for a function. Difference: how arguments are passed and when it's called.

function greet(greeting, name) {
  return `${greeting}, ${name}! I am ${this.title}`;
}
const ctx = { title: 'Engineer' };

greet.call(ctx, 'Hello', 'Alice');   // called immediately, args spread
greet.apply(ctx, ['Hi', 'Bob']);      // called immediately, args in array
const bound = greet.bind(ctx, 'Hey'); // returns new function, not called
bound('Charlie');                    // called later
6
Explain Promises and async/await. What are Promise combinators?

A Promise is an object representing eventual completion/failure of async work. States: pending → fulfilled | rejected. Key combinators:

  • Promise.all(arr) — waits for ALL to settle. Rejects immediately if any rejects.
  • Promise.allSettled(arr) — waits for ALL, never rejects early. Returns array of {status, value/reason}.
  • Promise.race(arr) — settles with the first promise to settle.
  • Promise.any(arr) — fulfills with first fulfillment; rejects if ALL reject (AggregateError).
// async/await is syntactic sugar over Promises
async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error('Not found');
    return await res.json();
  } catch(err) {
    console.error(err);
  }
}
7
What is debouncing vs throttling?

Debounce: Execute the function only after N ms of silence (no new calls). Great for search-as-you-type — wait until user stops typing before firing API call.

Throttle: Execute at most once per N ms regardless of how many times it's called. Great for scroll/resize events — fire at regular intervals, not on every pixel.

// Debounce — waits for 300ms silence
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
8
What is the difference between shallow copy and deep copy?

Shallow copy: Copies only the top-level properties. Nested objects are still references to the original.

const a = { x: 1, nested: { y: 2 } };
const b = { ...a }; // or Object.assign({}, a)
b.nested.y = 99; // MUTATES a.nested.y too!

Deep copy: Recursively copies all levels. Options: structuredClone(obj) (modern, native), JSON.parse(JSON.stringify(obj)) (loses functions/Date/undefined), Lodash _.cloneDeep.

9
What is event delegation in JavaScript?

Instead of attaching event listeners to each child element, attach ONE listener to a parent and use event.target to identify which child was clicked. Works because events bubble up the DOM tree.

// Without delegation: N listeners for N items
// With delegation: 1 listener on parent
document.querySelector('#list').addEventListener('click', e => {
  if (e.target.matches('li')) {
    handleItemClick(e.target);
  }
});

Benefits: fewer event listeners (memory), automatically handles dynamically added elements.

10
What are generators in JavaScript?

Generators are functions that can pause execution and resume later. They use function* syntax and yield to pause.

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
const gen = fibonacci();
gen.next().value; // 0
gen.next().value; // 1
gen.next().value; // 1

Generators are the foundation of many async iteration patterns and are used internally by Redux-Saga for side effects.

11
What is the difference between null, undefined, and NaN?

undefined: variable declared but not assigned a value. null: explicitly set to "no value" by developer. NaN (Not a Number): result of invalid arithmetic like 0/0 or parseInt('abc'). typeof NaN === 'number' (historical quirk). Check NaN with Number.isNaN(val) — never val === NaN (always false).

12
What are WeakMap and WeakSet, and when would you use them?

Both use weak references — they don't prevent their keys from being garbage-collected. Keys must be objects. Not iterable.

Use case for WeakMap: Associate private data or caches with DOM nodes or objects without preventing GC. If the object is removed, the WeakMap entry disappears too. WeakSet: Track "visited" objects without creating memory leaks.

13
What is the Temporal Dead Zone (TDZ)?

The period between entering a block scope and the actual let/const declaration being reached. Accessing the variable in TDZ throws ReferenceError, unlike var which is initialized to undefined at hoist time.

console.log(x); // undefined (var is hoisted + initialized)
var x = 5;

console.log(y); // ReferenceError! TDZ
let y = 5;
14
Explain JavaScript's this keyword. How does arrow function change it?

this in regular functions is determined at call time, not definition time. Arrow functions inherit this from their lexical enclosing scope (definition time) — they don't have their own this.

class Timer {
  constructor() { this.seconds = 0; }
  start() {
    // Arrow function: `this` = Timer instance ✓
    setInterval(() => this.seconds++, 1000);
    // Regular function: `this` = undefined (strict) or global ✗
    setInterval(function() { this.seconds++; }, 1000);
  }
}
15
What is memoization? Implement a generic memoize function.

Memoization caches the return value of a function based on its inputs. On subsequent calls with the same arguments, return the cached result instead of recomputing.

function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
const fib = memoize(n => n <= 1 ? n : fib(n-1) + fib(n-2));

React Hooks (Q16–Q35)

16
How does React's virtual DOM work?

The Virtual DOM is a lightweight JavaScript object tree representation of the actual DOM. When state changes: (1) React creates a new virtual DOM tree, (2) diffing algorithm (Fiber) compares it with the previous tree, (3) React calculates the minimal set of changes (patch), (4) applies only those changes to the real DOM (reconciliation). This batches DOM mutations and avoids expensive direct DOM access.

17
What is the difference between useState and useReducer?

useState: simple state, simple updates. Good for independent primitive values. useReducer: when state transitions have complex logic, multiple sub-values, or when next state depends on previous in complex ways. useReducer is like redux in a component — state + dispatch(action) → new state.

// useState — simple counter
const [count, setCount] = useState(0);

// useReducer — multiple related actions
const [state, dispatch] = useReducer((state, action) => {
  switch(action.type) {
    case 'INCREMENT': return { ...state, count: state.count + 1 };
    case 'RESET': return { count: 0, error: null };
    default: return state;
  }
}, { count: 0, error: null });
18
Explain useEffect with its dependency array patterns.

useEffect runs side effects after render. The dependency array controls when it re-runs:

  • useEffect(fn) — runs after EVERY render
  • useEffect(fn, []) — runs once after mount only (like componentDidMount)
  • useEffect(fn, [dep1, dep2]) — runs after mount and when dep1 or dep2 changes

Return a cleanup function to cancel subscriptions, clear timers, or abort fetch. It runs before the next effect and on unmount.

useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/data`, { signal: controller.signal })
    .then(r => r.json())
    .then(setData);
  return () => controller.abort(); // cleanup
}, [userId]);
19
What is the difference between useMemo and useCallback?

useMemo memoizes a computed value. useCallback memoizes a function reference.

// useMemo — expensive calculation
const sortedList = useMemo(
  () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// useCallback — stable fn ref for memo'd child
const handleClick = useCallback(
  (id) => deleteItem(id),
  [deleteItem]
);

When to use: Don't reach for these by default — they add complexity. Memoize when: (1) a calculation is provably expensive, or (2) a function passed to a React.memo() child is causing unnecessary re-renders.

20
What is useRef and what are its use cases?

useRef returns a mutable object { current: value } that persists across renders without triggering re-renders when changed. Two main uses:

  • DOM references: <input ref={inputRef} />inputRef.current.focus()
  • Mutable instance variable: Store previous values, timer IDs, or any mutable data that shouldn't trigger re-render.
const prevCount = useRef(count);
useEffect(() => { prevCount.current = count; });
// Now prevCount.current has the previous count value
21
Explain React Context API and when to use it over Redux.

Context provides a way to pass data through the component tree without passing props at every level (prop drilling). Create with createContext, provide with Provider, consume with useContext.

Use Context when: Theme, locale, auth state, small apps where Redux overhead isn't justified.

Use Redux when: Frequent state updates affecting many components (Context re-renders ALL consumers), complex state logic, need for time-travel debugging or middleware.

22
What is React.memo()? How does it differ from useMemo?

React.memo() is a Higher Order Component that memoizes a component itself — it skips re-rendering if props haven't changed (shallow equality by default). useMemo memoizes a value inside a component. useCallback memoizes function references so that children wrapped in React.memo don't get unnecessary re-renders from new function references.

const ExpensiveChild = React.memo(({ data, onClick }) => {
  // Only re-renders when data or onClick reference changes
  return <div onClick={onClick}>{data}</div>;
});
23
What are custom hooks and when should you create one?

Custom hooks are functions starting with use that encapsulate reusable stateful logic. Create one when you find yourself duplicating useState/useEffect/useCallback logic across multiple components.

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    fetch(url).then(r => r.json()).then(setData)
      .catch(setError).finally(() => setLoading(false));
  }, [url]);
  return { data, loading, error };
}
24
What are React keys and why are they important?

Keys help React identify which list items have changed/added/removed during reconciliation. Keys must be unique among siblings and stable across renders. Using array indices as keys can cause bugs when the list is reordered (React thinks elements moved but didn't change), causing stale state in form inputs.

// Bad: index as key — breaks with sorting/filtering
{todos.map((t, i) => <Todo key={i} item={t} />)}
// Good: stable unique ID
{todos.map(t => <Todo key={t.id} item={t} />)}
25
What is code splitting and lazy loading in React?

Code splitting breaks the JS bundle into smaller chunks that load on demand, reducing initial page load time. React's React.lazy() + Suspense enables this natively for component-level splitting.

const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Dashboard />
    </Suspense>
  );
}

Route-level code splitting is the most impactful — each page loads only the JS it needs.

Advanced Topics (Q26–Q40)

26
What is tree shaking and how does it work?

Tree shaking is dead-code elimination during bundling. Bundlers (Webpack, Rollup, Vite) analyze ES module import/export statements (static analysis) and remove unused exports from the final bundle. Only works with ES modules (not CommonJS require()). Named exports are more tree-shakeable than default exports of large objects.

27
Explain CORS and how to handle it.

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks requests from origin A to origin B unless server B explicitly permits it. The server must include Access-Control-Allow-Origin header. For requests with custom headers or methods other than GET/POST/HEAD, a preflight OPTIONS request is sent first. Fix: configure CORS on the backend, or use a proxy for development. Never use * with credentials.

28
What is the difference between localStorage, sessionStorage, and cookies?
StorageCapacityExpiryJS AccessSent with Requests
localStorage~5 MBUntil manually clearedYesNo
sessionStorage~5 MBTab/window closeYesNo
Cookies~4 KBSet by expires/max-ageYes (unless HttpOnly)Yes (same origin)

Auth tokens: use HttpOnly Secure SameSite cookies. Don't store JWTs in localStorage — accessible via XSS.

29
What are web workers? When would you use them?

Web Workers run JavaScript in a background thread, separate from the main UI thread. They have no DOM access. Communication is via postMessage. Use them for CPU-intensive tasks (image processing, large data parsing, encryption) that would otherwise block the UI thread and cause jank. Service Workers are a specialized type of Web Worker used for caching (PWAs) and background sync.

30
What is the difference between synchronous and asynchronous iteration?

Sync iterables (Symbol.iterator) work with for...of. Async iterables (Symbol.asyncIterator) work with for await...of — each iteration step can be a Promise.

async function* paginate(url) {
  let nextUrl = url;
  while (nextUrl) {
    const res = await fetch(nextUrl).then(r => r.json());
    yield res.data;
    nextUrl = res.nextPage;
  }
}
for await (const page of paginate('/api/items')) {
  processPage(page);
}

State Management (Q31–Q40)

31
Explain Redux's three core principles.
  1. Single source of truth: All app state in one store.
  2. State is read-only: Only dispatch actions to change state — never mutate directly.
  3. Changes via pure reducers: Reducer = (state, action) → newState. No side effects.

Redux Toolkit (RTK) is now the standard — createSlice, createAsyncThunk, Immer under the hood for "mutable-looking" reducers. RTK Query handles data fetching and caching.

32
What is React Query (TanStack Query) and why use it over Redux for server state?

React Query handles server state — data that lives on a server and is fetched async. Redux is for client state. React Query provides: automatic caching with stale-time, background refetching, pagination/infinite scroll, optimistic updates, and request deduplication — all out of the box. For most CRUD apps, React Query + useState/Zustand replaces the need for Redux entirely.

33
What is prop drilling and how do you solve it?

Prop drilling is passing props through multiple intermediate components that don't need them, just to get data to a deeply nested child. Solutions in order of complexity: (1) Component composition — restructure to avoid deep nesting, (2) React Context — for low-frequency updates (theme, auth), (3) Zustand/Jotai — lightweight state managers, (4) Redux — for complex shared state across many components.

34
What is reconciliation in React 18? What changed with the Fiber architecture?

Fiber (React 16+) made reconciliation interruptible. Before Fiber, reconciliation was synchronous — it would block the main thread for large trees. Fiber breaks reconciliation into units of work that can be paused, resumed, or abandoned. React 18 builds on Fiber with Concurrent Mode: startTransition marks low-priority updates (search filtering) so urgent updates (typing) interrupt them. useTransition exposes this API. Suspense for data fetching also relies on concurrent rendering.

35
How would you optimize a React app that's slow to render?

Diagnosis first: Use React DevTools Profiler to identify slow components. Then:

  1. Wrap expensive components in React.memo()
  2. Stabilize function props with useCallback
  3. Memoize expensive computed values with useMemo
  4. Virtualize long lists with react-window or react-virtual
  5. Code-split large routes with React.lazy
  6. Move expensive state down — colocate state closer to where it's used
  7. Use startTransition for non-urgent updates (filtering, searching)

More JS (Q41–Q50)

36
Implement a custom Promise from scratch.
class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.handlers = [];
    const resolve = (val) => {
      if (this.state !== 'pending') return;
      this.state = 'fulfilled'; this.value = val;
      this.handlers.forEach(h => h.onFulfilled(val));
    };
    executor(resolve);
  }
  then(onFulfilled) {
    return new MyPromise((resolve) => {
      if (this.state === 'fulfilled') {
        resolve(onFulfilled(this.value));
      } else {
        this.handlers.push({ onFulfilled: v => resolve(onFulfilled(v)) });
      }
    });
  }
}
37
What is the difference between map, filter, reduce, and forEach?

map: transforms each element, returns new array of same length. filter: returns new array of elements that pass predicate. reduce: accumulates to a single value (can build any output). forEach: side effects only, returns undefined. Key: map/filter/reduce are pure and chainable; forEach is for side effects.

38
What are Symbol and BigInt primitive types?

Symbol: unique, immutable identifier. Every Symbol() call creates a distinct value. Used as unique object keys to avoid name collisions. Well-known symbols like Symbol.iterator customize JS built-in behavior.

BigInt: arbitrary-precision integers. Use n suffix: 9007199254740993n. Needed when working with numbers exceeding Number.MAX_SAFE_INTEGER (2^53 - 1), e.g., database IDs, cryptography.

39
What is the difference between CommonJS (require) and ES Modules (import)?

CommonJS: synchronous, dynamic (can require() inside conditionals), resolves at runtime. module.exports. Used in Node.js historically. ES Modules: asynchronous, static (imports hoisted and resolved at parse time), enables tree shaking. export/import. Modern standard. Mix-and-match requires interop care.

40
What is a Service Worker and how does it enable PWAs?

A Service Worker is a script running in the background, independent of the webpage. It intercepts network requests (acts as a programmable proxy) enabling: offline caching (Cache API), background sync, push notifications. PWAs (Progressive Web Apps) use Service Workers to work offline and be installable. Lifecycle: install → activate → fetch events.

Frequently Asked Questions

What frontend companies pay the most in India 2026?
Top-paying frontend/fullstack companies in India: Google (SDE-3 ₹60-80L), Microsoft (SDE-2 ₹30-50L), Flipkart (SDE-2 ₹35-50L), Swiggy/Zomato (SDE-3 ₹40-60L), CRED/Razorpay (SDE-3 ₹40-60L), Zepto/Blinkit (SDE-2 ₹20-30L). Most high-paying companies expect React + system design + DSA proficiency for senior roles.
How should I prepare for a React interview at a product company?
3-phase approach: (1) Master core JavaScript — closures, event loop, prototypes, async patterns. Do all 40 questions in this guide. (2) React internals — hooks (useState/useEffect/useMemo/useCallback/useRef), virtual DOM, reconciliation, custom hooks, performance optimization. Build 2-3 real projects. (3) Practical patterns — state management (Redux Toolkit or Zustand), data fetching (React Query), code splitting, accessibility. Companies also expect DSA (LeetCode Medium) for SDE-2+.
Is Next.js knowledge required for frontend interviews?
Increasingly yes at product companies. Key Next.js concepts to know: SSR (getServerSideProps), SSG (getStaticProps/getStaticPaths), ISR (revalidate), App Router vs Pages Router (Next.js 13+), Server Components vs Client Components, route handlers, image optimization. Know the when-to-use: SSG for marketing pages, SSR for personalized pages, CSR for dashboards.
Pranjal Jain
Pranjal Jain

Ex-Microsoft SDE · IIT Kanpur · Founder of Prepflix. Has interviewed hundreds of engineers at Microsoft and advises candidates targeting product companies across India.