Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
ReactInterview PreparationFrontend DevelopmentJavaScriptHooks

React Interview Questions

React & frontend interview guide — 391 unique questions with theory, examples, and interview-ready answers across hooks, rendering, state, performance, JavaScript, and architecture.

Jul 5, 2026543 min read

Introduction

This guide merges 391 unique interview questions from 23 preparation notes into one reference — React fundamentals, hooks, rendering, state management, performance, JavaScript, TypeScript, Next.js, architecture, and behavioral rounds.

Questions are grouped into 14 topic areas, deduplicated by title, and stripped of company-specific references. Each entry keeps theory, trade-offs where relevant, concise interview answers, and practical code examples.

Quick index

React Fundamentals

#Section
150 components, one context — re-renders
2Component Performance — LCP, CLS, INP
3Components in React
4Controlled vs Uncontrolled Components
5Difference between Server Components and Client Components
6Explain React Server Components and their advantages
7Functional vs Class Components
8Grid Toggle Component
9Higher Order Components (HOC)
10How do you integrate RTK Query with your React components?
11How do you structure reusable UI components in React?
12How does React decide whether a component should re-render?
13How would you design a reusable component library?
14JSX vs HTML — Key Differences
15Keep business logic separate from UI components
16Keep components small and reusable
17Props in React
18React Fragments
19Star Rating Component (1–10)
20State in React
21State vs Props
22Synthetic Events
23Understand when components re-render
24What are controlled components?
25What are uncontrolled components?
26What is a class component?
27What is a functional component?
28What is a Higher Order Component (HOC)?
29What is JSX?
30What is React and why is it efficient?
31What is React Developer Tools?
32What is React?

Hooks

#Section
33Clean up subscriptions, timers, and event listeners inside useEffect
34Custom Hooks
35Dependency Array
36Difference between useMemo, useCallback, and React.memo
37Explain how useState works internally
38Fetching Data with useEffect
39Implement a basic version of useState
40Memoization (useMemo, useCallback)
41Rules of Hooks
42Single state object vs individual useState
43Use custom hooks to avoid duplicate logic
44Use useMemo and useCallback only when necessary
45useCallback
46useEffect
47useEffect as Lifecycle Replacement
48useEffect Hook
49useEffect vs useMemo — Execution Order
50useMemo
51useReducer
52useRef & useRef vs useState
53useRef Hook
54useRef vs useState
55useState
56useState Hook
57What are Hooks? Explain useEffect, useCallback, and useMemo
58What has been your experience with useCallback and useMemo in real projects?
59What is the role of the dependency array in useEffect?
60What is useRef?
61Why Do We Use Custom Hooks?

Rendering & Reconciliation

#Section
62CSR vs SSR
63CSR vs SSR vs SSG & Hydration
64CSR vs SSR vs SSG vs ISR
65Explain React Fiber Architecture
66Explain React's rendering lifecycle from state update to DOM update
67Explain Virtual DOM and its comparison mechanism
68Flexbox vs Grid — key differences
69How does React reconciliation work?
70Keys in React Lists
71Next.js SSR vs CSR vs ISR
72Optimize 10+ fields — re-renders
73React Fiber
74React Internals & Rendering Lifecycle
75React reconciliation — what to update?
76SSR vs CSR with examples and use-cases
77Tell me about your key skills
78this Keyword
79To-Do List (Vanilla JS / React) — Optimize Re-renders
80Use unique keys while rendering lists
81Virtual DOM
82Virtual DOM, Hydration, Reconciliation & Scheduling
83What is the Virtual DOM?
84Why are keys important in React lists?
85Why do keys matter in React and how do they improve performance?
86You need to display frequently changing stock market data on a page. Would you use SSR, SSG, or CSR? Why?

State Management

#Section
87Avoid unnecessary prop drilling
88Can you use RTK Query without Redux Toolkit?
89Context API
90Explain the Redux Workflow
91Have you used Redux Toolkit (RTK) or TanStack Query?
92How do you handle loading and error states in RTK Query?
93How do you use Context API?
94How does caching work in RTK Query?
95How does Redux work, from installation to usage in a project?
96Lifting State Up
97Optimize Redux application performance
98Prop Drilling
99Redux
100Redux Toolkit (RTK)
101Redux vs Context API
102Redux Workflow
103State Management at Scale
104What are the advantages of Redux?
105What are the benefits of using Redux Toolkit over traditional Redux?
106What does createApi do in RTK Query?
107What is tagTypes in RTK Query?
108What is a slice in Redux Toolkit?
109What is RTK Query?
110What is the difference between query and mutation in RTK Query?
111When should you avoid Context API?

Routing & Navigation

#Section
112Dynamic Routes — useParams
113How do you connect routes in React?
114Navigation — useNavigate & useLocation
115Protected Routes
116React Router
117React Router Basics
118Your API routes are receiving a high volume of requests. How would you improve scalability and performance?

API & Data Fetching

#Section
119A Next.js page is loading slowly because it fetches data from multiple APIs. How would you optimize the page performance?
120Async Data Handling — React Query & Caching
121Axios vs fetch in React
122Build a custom React hook for data fetching
123CORS
124Custom useFetch Hook
125How do you handle race conditions in API calls?
126How does SSO work?
127Long Polling vs WebSockets
128Optimize API calls and handle loading/error states properly
129REST vs GraphQL APIs
130Scenario-Based: 3 API calls, fast load, partial errors
131Spread & Rest Operators
132Spread vs Rest Operator
133TanStack Query (React Query)
134WebSocket
135WebSockets
136What are REST API methods and their differences?
137What is fetchBaseQuery?
138What is reducerPath in createApi?
139What is Axios? How do you use it?
140What is OAuth and how does it work?
141You need to implement server-side caching to reduce API calls. How would you approach it?

Performance

#Section
142A page contains a heavy chart library that is only needed after user interaction. How would you optimize the loading strategy?
143Bundle Size Optimization
144Closure memory leak and fix
145Code Splitting
146Code Splitting & Lazy Loading
147Core Web Vitals (LCP, INP, CLS)
148Debounce and Throttle implementation
149Debounce vs Throttle
150Debouncing
151Debouncing implementation
152Design & Implement a Virtualized Infinite Feed
153Explain code splitting and lazy loading
154How does memoization work in React?
155How to optimize performance?
156How would you implement infinite scrolling efficiently?
157How would you optimize a React application with thousands of rows?
158Image Optimization (WebP, AVIF)
159Infinite Scroll
160Intersection Observer & Rendering Optimizations
161Lazy Loading
162Memory Management
163Pagination
164React.memo
165Suspense (React)
166Throttle promises by batching
167Throttling implementation
168Use React.lazy() and code splitting for better performance
169Web Vitals & Production Monitoring
170What are memory leaks in React and how to detect them?
171What is Debouncing and Throttling?
172What is the difference between debounce and throttle?
173What techniques would you use to improve Core Web Vitals?
174Write a debounce function (cancellable)
175Your Next.js application has a large JavaScript bundle size affecting performance. How would you reduce it?

Architecture & Patterns

#Section
176Error Boundaries & Design Patterns
177Follow a proper folder structure
178How would you design a scalable frontend architecture for a large-scale application?
179Micro-frontend Architecture
180React Architecture
181React vs Vue — Architecture & Performance Tradeoffs
182Scalable, Maintainable Architecture
183Use Error Boundaries to handle unexpected UI crashes
184What are Error Boundaries in React?
185What are portals?
186What are React Portals? How are modals mounted using them?
187What is an Error Boundary?
188What is Module Federation?

JavaScript Core

#Section
189Async/Await
190async/await and Promises
191Call, Apply, Bind — Difference + Polyfill
192Can call(), apply(), bind() be used with Arrow Functions?
193Closures
194Currying — Implement sum(1)(2)(3)
195Currying for Infinite Sum
196defer vs async — which blocks HTML parsing?
197Difference between Promise.all() and Promise.allSettled()
198Event Loop — Microtasks vs Macrotasks
199Execute N callback-based async tasks in series
200Explain closures with a practical example
201Explain currying and function composition
202Explain lexical scope and closure in detail
203Garbage Collection
204How do map() and bind() methods work?
205How does garbage collection work in JavaScript?
206How does the JavaScript Event Loop work?
207How to control tab order in DOM (explain tabIndex)
208How to override toString on String.prototype
209Implement Array.prototype.reduce polyfill
210Implement auto-retry for promises
211Implement Promise.all polyfill
212Implement Promise.any polyfill
213Implement your own version of Promise.all()
214JavaScript Event Loop
215Polyfill for Array.map()
216Polyfill for Function.bind()
217Polyfills & Babel
218Promise & Async/Await Output Puzzle
219Promises
220Prototype & Inheritance
221Prototypes and Prototype Chain
222var, let & const
223var, let, and const
224What are WeakMap and WeakSet?
225What happens behind the scenes when async/await executes?
226What is createAsyncThunk?
227What is a Promise?
228What is Closure?
229What is Hoisting in JavaScript?
230What is Hoisting?
231What is the difference between bind and apply in JavaScript?
232What is the difference between synchronous and asynchronous iteration?
233What is the Event Loop and how does the Microtask Queue work?
234What is the Event Loop?
235Why a fintech app specifically

TypeScript

#Section
236How does extends work in TypeScript and what is the difference between type and interface?
237TypeScript in Large Codebases
238Why do we use TypeScript?

Next.js

#Section
239You are migrating a React SPA to Next.js. What challenges would you expect and how would you handle them?
240You need to upload large files from the frontend to a cloud storage service. How would you implement the flow in Next.js?

Testing & Tooling

#Section
241How would you implement A/B testing in a Next.js application?
242Testing — RTL, Jest, Playwright
243Webpack
244What is Snapshot Testing?

Behavioral & Soft Skills

#Section
245Culture Fit & Behavioral Questions
246Describe a disagreement within your team and how you resolved it
247How did you handle a critical production issue?
248How do you mentor junior developers and review code?
249Tell us about a challenging project you worked on

General & Advanced

#Section
250== vs ===
251A decision you regretted
252Accessibility (a11y)
253Arrow Functions
254Authentication vs Authorization
255Auto Increment/Decrement Counter
256Batching in React
257Best Practices in React
258Bridging Fundamentals and Real Engineering
259Browser Storage
260Build a traffic light system in React
261Caching (Client + Server)
262Can We Use Hooks inside Conditions?
263Class Lifecycle Methods
264Coding: Find the first non-repeated character
265Common JavaScript Gotchas
266Conditional Rendering
267const vs let
268Cross Browser Compatibility
269CSS Specificity
270Data Types
271Debugging Real-World Performance Issues
272Deep Copy vs Shallow Copy
273Dependent field state structure
274Destructuring
275Difference between unique_ptr and shared_ptr
276Difference between vector and list
277Difference between Deep Copy and Shallow Copy
278Difference between Normal Functions and Arrow Functions
279Difference between PATCH and PUT
280Difference between position: relative, absolute, fixed, and static
281Difference between undefined and not defined
282display: none vs visibility: hidden
283Engineering Quality & Ownership
284Event Bubbling and Capturing
285Event bubbling vs event delegation
286Explain copy constructor and move constructor
287Explain event delegation with a practical example
288Explain frontend caching strategies
289Explain map, filter, and reduce with examples
290Explain move semantics
291Find Missing Numbers in Array
292Find the First Non-Repeating Character in a string
293First Repeating Character
294Flatten a Nested Array
295Flatten Array without Array.flat()
296Flatten Array without Built-in Methods
297Flatten Nested Array
298Focus on accessibility
299Form Handling in React
300Given an array, how do you find the second largest element?
301Handling Complex Codebases
302How do you authenticate your React application?
303How do you ensure the performance of a React application?
304How Do You Flatten an Array?
305How do you implement authentication in React?
306How do you manage state in large React applications?
307How does addEventListener work?
308How does error handling work in React?
309How does JavaScript code execution work?
310How does React work internally?
311How does the JavaScript Engine work?
312How does virtual table (vtable) work?
313How to handle forms in React?
314How to measure performance in a React application?
315How would you build a tool like Create React App?
316How would you design a frontend system serving millions of users daily?
317Implement Lodash's flatten method
318Implement responsive design from the beginning
319Inline 5 Divs without Flex/Margin/Padding
320Is JavaScript tightly coupled or loosely coupled?
321Join/Merge Objects from Object List
322Keep dependencies updated and remove unused packages
323Largest Frontend Systems Built
324Lift state up only when required
325Live search box — vanilla JavaScript
326Loading, Error & Empty States
327Local Storage vs Cookies
328Map, Filter & Reduce
329Measure performance using React DevTools and Lighthouse
330Multi-field form — PAN, Aadhaar
331Never mutate state directly
332New features in latest React versions
333Normal Functions vs Arrow Functions
334Normal vs Arrow Functions
335Object Methods
336Object vs Map differences in JavaScript
337Objects in JavaScript
338Optimistic UI Updates
339Output prediction for tricky JavaScript snippets
340Output-based JavaScript Questions
341Output-based tricky JS questions (Bonus)
342Principles of Functional Programming
343Projects deep dive
344Quick Revision Cheat Sheet
345React 18 Automatic Batching
346Render Phase vs Commit Phase
347Round-by-round prep strategy
348Scope & Scope Chain
349Server-Side Rendering techniques to improve SEO
350Session Storage vs Local Storage
351Stopwatch Implementation
352Strict Mode
353Sum of Numbers without a for Loop
354Team Collaboration & Conflict Resolution
355Technical weaknesses
356Tree Shaking
357Two Sum Problem
358Type Coercion
359Types of Objects & How to Define Them
360Unfamiliar codebase
361Use environment variables for configuration values
362useContext Hook
363useDeferredValue Hook
364useLayoutEffect Hook
365useTransition & useDeferredValue
366Web Accessibility and Semantic Elements
367What are microservices?
368What are Microtasks and Macrotasks?
369What are Smart Pointers?
370What do you do for security purposes in frontend applications?
371What happens during object construction and destruction?
372What happens internally when a state update is triggered?
373What is providesTags and invalidatesTags?
374What is an event in React?
375What is defaultProps?
376What is Event Bubbling?
377What is Event Capturing and Bubbling
378What is forwardRef?
379What is Props Drilling?
380What is RAII?
381What is Temporal Dead Zone (TDZ)?
382What is the difference between var, let, and const?
383What is the difference between children and props?
384What is the most challenging task you handled in your project?
385What is Web Accessibility?
386What was your role in your last project?
387Why did you choose React?
388Why does CSS block rendering?
389Why use React?
390Write unit tests for critical functionality
391Your application has 10,000 blog posts. How would you generate and serve these pages efficiently?

React Fundamentals

1. 50 components, one context — re-renders

Theory

When a Context value changes, every component that calls useContext for that context re-renders — regardless of whether it uses the part that changed.

50 components sharing one context → all 50 re-render on any context update.

This is why a fintech app asks this — they want you to know Context is not a free global store.

Pros & Cons

Single large contextSplit contexts / Zustand selectors
❌ 50 re-renders on any change✅ Only subscribers to changed slice re-render
✅ Simple setupSlightly more setup
❌ Performance death at scale✅ Scales to a fintech app-level apps

Real-Life Example

jsx
// ❌ One context — 50 components all re-render when theme OR user changes
const AppContext = createContext();
 
function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("dark");
  const [notifications, setNotifications] = useState([]);
 
  // New object every render → ALL consumers re-render
  const value = { user, setUser, theme, setTheme, notifications, setNotifications };
 
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}
 
// 50 components:
function Header() { const { user } = useContext(AppContext); ... }
function Sidebar() { const { theme } = useContext(AppContext); ... }
function NotifBell() { const { notifications } = useContext(AppContext); ... }
// theme changes → Header, Sidebar, NotifBell ALL re-render even if they don't use theme
jsx
// ✅ FIX 1 — Split contexts
const UserContext = createContext()
const ThemeContext = createContext()
 
// theme change → only ThemeContext consumers re-render
 
// ✅ FIX 2 — Memoize value
const userValue = useMemo(() => ({ user, setUser }), [user])
 
// ✅ FIX 3 — Zustand with selectors
const theme = useStore((s) => s.theme) // only re-renders when theme changes
 
// ✅ FIX 4 — React 19 use(Context) + smaller providers per route

Interview answer (concise)

Performance

All 50 components that consume the context re-render when the Provider value changes — React doesn't know which field each component uses. Fix by splitting contexts by update frequency, memoizing the value object, or using Zustand/Jotai with selectors so components subscribe only to the slice they need.

Back to index


2. Component Performance — LCP, CLS, INP

Theory

Core Web Vitals are Google's user-centric metrics. They directly affect SEO, conversion, and how users perceive your app.

MetricWhat it measuresGood threshold
LCP (Largest Contentful Paint)When main content appears≤ 2.5s
CLS (Cumulative Layout Shift)Visual stability — unexpected jumps≤ 0.1
INP (Interaction to Next Paint)Responsiveness to user input (replaced FID)≤ 200ms

Other important: FCP (First Contentful Paint), TTFB (Time to First Byte), TBT (Total Blocking Time).

Pros & Cons — Optimization Approaches

ApproachHelpsTrade-off
Code splittingLCP, TBTMore chunks to manage
Image optimization (next/image, WebP)LCPBuild pipeline complexity
Font preload + font-display: swapLCP, CLSFOUT if not tuned
Skeleton + reserved dimensionsCLSExtra CSS
defer non-critical JSTBT, INPDelayed interactivity
VirtualizationINP on long listsScroll complexity

Interview Answer

LCP is about getting meaningful content on screen fast — optimize hero images, fonts, and critical path JS. CLS means reserving space so nothing jumps. INP is input responsiveness — reduce main-thread work, split long tasks, and defer heavy renders with startTransition.

LCP Optimization

jsx
// ❌ LCP killer — unoptimized hero image, render-blocking font
<img src="/hero-4mb.jpg" />
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
 
// ✅ Optimized
import Image from "next/image";
 
<Image
  src="/hero.webp"
  alt="Product showcase"
  priority          // preload LCP candidate
  width={1200}
  height={630}
  sizes="(max-width: 768px) 100vw, 1200px"
/>
 
// Preload LCP image in <head>
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />

CLS Prevention

jsx
// ❌ CLS — image loads, page jumps
<img src={product.image} />
 
// ✅ Reserve space
<div style={{ aspectRatio: "16/9", width: "100%" }}>
  <img src={product.image} width={640} height={360} alt={product.name} />
</div>
 
// ✅ Skeleton with fixed height
function ProductCardSkeleton() {
  return <div className="card-skeleton" style={{ height: 320 }} />;
}
css
/* Font loading — prevent invisible text then swap jump */
@font-face {
  src: url('/fonts/inter.woff2') format('woff2');
  font-family: 'Inter';
  font-display: swap; /* or optional for less CLS */
}

INP Optimization

jsx
import { useTransition, memo, useDeferredValue } from 'react'
 
// Defer expensive filter on search
function ProductSearch({ products }) {
  const [query, setQuery] = useState('')
  const deferredQuery = useDeferredValue(query)
 
  const filtered = useMemo(() => filterProducts(products, deferredQuery), [products, deferredQuery])
 
  return (
    <>
      <input onChange={(e) => setQuery(e.target.value)} />
      <ProductList products={filtered} />
    </>
  )
}
 
// Break up long tasks
function handleHeavyClick() {
  requestIdleCallback(() => processAnalytics(data))
  startTransition(() => setResults(computeExpensive()))
}

Back to index


3. Components in React

Theory

A component is an independent, reusable piece of UI. Components accept props, manage state, and return JSX. They can be nested — <App> contains <Header>, <Sidebar>, <Main>.

Two types: function components (modern standard) and class components (legacy).

Pros & Cons

Small focused componentsLarge monolithic components
✅ Easy to test and reuse❌ Hard to maintain
✅ Clear responsibility❌ Bug in one place breaks all

Interview Answer

Components are reusable UI building blocks. Each component encapsulates its own markup and logic and can be composed together to build full pages.

Real Example

jsx
function App() {
  return (
    <>
      <Header />
      <ProductList />
      <Footer />
    </>
  )
}

Back to index


4. Controlled vs Uncontrolled Components

Theory

ControlledUncontrolled
Value sourceReact stateDOM itself
UpdatesonChange + setStateref.current.value
ValidationReal-time in ReactOn submit only
Preferred?Yes — React standardLegacy, file inputs

Interview Answer

Controlled components store input value in React state — React is the single source of truth. Uncontrolled components let the DOM hold the value and we read it via ref on submit.

Real Example

jsx
// Controlled — preferred
function LoginForm() {
  const [email, setEmail] = useState('')
  return <input value={email} onChange={(e) => setEmail(e.target.value)} />
}
 
// Uncontrolled — ref on submit
function LoginFormUncontrolled() {
  const emailRef = useRef(null)
  const submit = () => console.log(emailRef.current.value)
  return <input ref={emailRef} defaultValue="" />
}

Back to index


5. Difference between Server Components and Client Components

Theory

Server ComponentClient Component
DirectiveDefault in App Router"use client" at top of file
Runs onServer onlyServer (SSR) + Browser
JS sent to browserNone for RSC itselfFull component bundle
Can use hooks (useState)NoYes
Can use browser APIsNoYes
Can access DB/secretsYesNo
Can import Client ComponentsYes (as children)Yes
Can import Server ComponentsNoNo (only receive as props/children)

Composition pattern

tsx
// app/page.tsx — Server Component
import ClientCounter from './ClientCounter'
 
export default async function Page() {
  const data = await fetchFromDB() // runs on server
 
  return (
    <main>
      <h1>Dashboard</h1>
      <ClientCounter initialCount={data.count} />
    </main>
  )
}
tsx
// ClientCounter.tsx
'use client'
 
import { useState } from 'react'
 
export default function ClientCounter({ initialCount }) {
  const [count, setCount] = useState(initialCount)
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

When to use each

  • Server: Static content, data fetching, heavy libraries, SEO-critical markup
  • Client: Interactivity, effects, browser APIs, animations, form state

Interview answer

Warning

Server Components run on the server, never ship their logic to the client, and can access backend resources directly. Client Components handle interactivity. The boundary is "use client" — pass serializable props from server to client, never import server components into client files.

Back to index


6. Explain React Server Components and their advantages

Theory

React Server Components (RSC) run only on the server. They never ship their JavaScript to the client. They can directly access backend resources (DB, file system, secrets) and pass rendered output or serializable props to Client Components.

RSC vs SSR vs Client Components

Server ComponentSSR (traditional)Client Component
Runs on server✅✅ (initial HTML)❌ (runs in browser)
JS shipped to client❌✅ (hydration bundle)✅
Can use useState❌✅ (after hydration)✅
Can access DB directly✅❌ (usually)❌
Interactivity❌✅✅

Composition model

tsx
// app/restaurants/page.tsx — Server Component (default in App Router)
import { db } from '@/lib/db'
import RestaurantList from './RestaurantList' // Client Component
 
export default async function RestaurantsPage() {
  const restaurants = await db.restaurant.findMany() // direct DB access
 
  return (
    <main>
      <h1>Restaurants</h1>
      <RestaurantList initialData={restaurants} />
    </main>
  )
}
tsx
// RestaurantList.tsx — Client Component
'use client'
 
import { useState } from 'react'
 
export default function RestaurantList({ initialData }) {
  const [filter, setFilter] = useState('')
  const filtered = initialData.filter((r) => r.name.toLowerCase().includes(filter.toLowerCase()))
 
  return (
    <>
      <input value={filter} onChange={(e) => setFilter(e.target.value)} />
      <ul>
        {filtered.map((r) => (
          <li key={r.id}>{r.name}</li>
        ))}
      </ul>
    </>
  )
}

Advantages

  1. Smaller client bundles — server-only code never downloads
  2. Zero client-side waterfall — data fetched on server in parallel
  3. Automatic code splitting — per-request component tree
  4. Secure data access — secrets stay on server
  5. Streaming — send HTML progressively with Suspense

Trade-offs to mention

  • Mental model complexity (server vs client boundary)
  • Caching and serialization constraints (props must be serializable)
  • Tooling still evolving; debugging across boundaries

Back to index


7. Functional vs Class Components

Theory

FunctionalClass
Syntaxfunction Comp() {}class Comp extends React.Component
StateuseState, useReducerthis.state, this.setState
LifecycleuseEffectcomponentDidMount, etc.
thisNot neededRequired
StatusModern standardLegacy, still in old codebases

Hooks (React 16.8+) made function components fully capable.

Interview Answer

Function components with hooks are the modern standard — simpler syntax, no this, easier to test. Class components still exist in legacy code but I use functions for all new code.

Real Example

jsx
// Functional (modern)
function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}
 
// Class (legacy)
class Counter extends React.Component {
  state = { count: 0 }
  render() {
    return <button onClick={() => this.setState({ count: this.state.count + 1 })}>{this.state.count}</button>
  }
}

Back to index


8. Grid Toggle Component

Theory

Build an N×N grid where clicking a cell toggles its state (on/off). Tests:

  • State management — 2D grid in state
  • Immutable updates — never mutate array directly
  • Component design — separate Grid, Cell, maybe custom hook
  • Event handling — click toggles correct cell
  • Clean code — readable names, small functions

Common variations interviewers add:

  • Toggle row/column on header click
  • "Select all" / "Clear all"
  • Count active cells
  • Keyboard navigation

Interview Answer

I store the grid as a 2D boolean array, toggle with immutable map — copy the row, flip the cell, replace the row in a new grid array. I split into Grid, Row, and Cell for readability.

Basic Implementation

jsx
import { useState, useCallback } from 'react'
 
function createGrid(rows, cols, fill = false) {
  return Array.from({ length: rows }, () => Array(cols).fill(fill))
}
 
function GridToggle({ rows = 3, cols = 3 }) {
  const [grid, setGrid] = useState(() => createGrid(rows, cols))
 
  const toggleCell = useCallback((rowIndex, colIndex) => {
    setGrid((prev) =>
      prev.map((row, r) => (r === rowIndex ? row.map((cell, c) => (c === colIndex ? !cell : cell)) : row)),
    )
  }, [])
 
  const activeCount = grid.flat().filter(Boolean).length
 
  return (
    <div>
      <p>Active cells: {activeCount}</p>
      <div role="grid" aria-label={`${rows} by ${cols} toggle grid`} style={{ display: 'inline-grid', gap: 4 }}>
        {grid.map((row, rowIndex) => (
          <div key={rowIndex} role="row" style={{ display: 'flex', gap: 4 }}>
            {row.map((isActive, colIndex) => (
              <button
                key={colIndex}
                role="gridcell"
                aria-pressed={isActive}
                aria-label={`Row ${rowIndex + 1}, Column ${colIndex + 1}, ${isActive ? 'on' : 'off'}`}
                onClick={() => toggleCell(rowIndex, colIndex)}
                style={{
                  width: 48,
                  height: 48,
                  border: '2px solid #333',
                  borderRadius: 4,
                  cursor: 'pointer',
                  backgroundColor: isActive ? '#22c55e' : '#f3f4f6',
                  transition: 'background-color 0.15s',
                }}
              />
            ))}
          </div>
        ))}
      </div>
    </div>
  )
}
 
export default GridToggle

Senior Structure — Custom Hook + Subcomponents

jsx
import { useState, useCallback, useMemo } from 'react'
 
// --- Custom hook — logic separated from UI ---
function useGridToggle(initialRows = 3, initialCols = 3) {
  const [grid, setGrid] = useState(() => createGrid(initialRows, initialCols))
 
  const toggleCell = useCallback((row, col) => {
    setGrid((prev) => prev.map((r, ri) => (ri === row ? r.map((cell, ci) => (ci === col ? !cell : cell)) : r)))
  }, [])
 
  const toggleRow = useCallback(
    (row) => {
      const allActive = grid[row].every(Boolean)
      setGrid((prev) => prev.map((r, ri) => (ri === row ? r.map(() => !allActive) : r)))
    },
    [grid],
  )
 
  const toggleCol = useCallback(
    (col) => {
      const allActive = grid.every((row) => row[col])
      setGrid((prev) => prev.map((row) => row.map((cell, ci) => (ci === col ? !allActive : cell))))
    },
    [grid],
  )
 
  const reset = useCallback(() => {
    setGrid(createGrid(grid.length, grid[0]?.length ?? 0))
  }, [grid])
 
  const selectAll = useCallback(() => {
    setGrid((prev) => prev.map((row) => row.map(() => true)))
  }, [])
 
  const activeCount = useMemo(() => grid.flat().filter(Boolean).length, [grid])
 
  return { grid, toggleCell, toggleRow, toggleCol, reset, selectAll, activeCount }
}
 
// --- Cell component ---
function Cell({ isActive, row, col, onToggle }) {
  return (
    <button
      type="button"
      role="gridcell"
      aria-pressed={isActive}
      aria-label={`Cell ${row + 1}-${col + 1}, ${isActive ? 'active' : 'inactive'}`}
      onClick={() => onToggle(row, col)}
      className={`cell ${isActive ? 'cell--active' : ''}`}
    />
  )
}
 
// --- Grid component ---
function GridToggle({ rows = 4, cols = 4 }) {
  const { grid, toggleCell, toggleRow, toggleCol, reset, selectAll, activeCount } = useGridToggle(rows, cols)
 
  return (
    <div className="grid-toggle">
      <header className="grid-toggle__header">
        <span>
          {activeCount} / {rows * cols} selected
        </span>
        <button type="button" onClick={selectAll}>
          Select All
        </button>
        <button type="button" onClick={reset}>
          Reset
        </button>
      </header>
 
      <div role="grid" aria-rowcount={rows} aria-colcount={cols}>
        {/* Column toggle headers */}
        <div role="row" className="grid-toggle__col-headers">
          <div style={{ width: 48 }} />
          {Array.from({ length: cols }, (_, col) => (
            <button
              key={col}
              type="button"
              aria-label={`Toggle column ${col + 1}`}
              onClick={() => toggleCol(col)}
              className="grid-toggle__header-btn">
              C{col + 1}
            </button>
          ))}
        </div>
 
        {grid.map((row, rowIndex) => (
          <div key={rowIndex} role="row" className="grid-toggle__row">
            <button
              type="button"
              aria-label={`Toggle row ${rowIndex + 1}`}
              onClick={() => toggleRow(rowIndex)}
              className="grid-toggle__header-btn">
              R{rowIndex + 1}
            </button>
            {row.map((isActive, colIndex) => (
              <Cell key={colIndex} isActive={isActive} row={rowIndex} col={colIndex} onToggle={toggleCell} />
            ))}
          </div>
        ))}
      </div>
    </div>
  )
}

CSS (optional — mention in interview)

css
.grid-toggle__row {
  display: flex;
  gap: 4px;
  margin-bottom: 4px;
}
 
.cell {
  transition:
    background-color 0.15s,
    transform 0.1s;
  cursor: pointer;
  border: 2px solid #374151;
  border-radius: 6px;
  background: #f9fafb;
  width: 48px;
  height: 48px;
}
 
.cell--active {
  border-color: #16a34a;
  background: #22c55e;
}
 
.cell:active {
  transform: scale(0.95);
}
 
.grid-toggle__header-btn {
  cursor: pointer;
  width: 48px;
  height: 32px;
  font-size: 11px;
}

Immutable Update — Explain This in Interview

javascript
// ❌ WRONG — mutates state directly
const toggleCell = (row, col) => {
  grid[row][col] = !grid[row][col]
  setGrid(grid) // same reference — React may not re-render!
}
 
// ✅ CORRECT — new array references at every level
const toggleCell = (row, col) => {
  setGrid((prev) => prev.map((r, ri) => (ri === row ? r.map((cell, ci) => (ci === col ? !cell : cell)) : r)))
}

Flat Array Alternative (some interviewers prefer)

jsx
function useGridFlat(rows, cols) {
  const [cells, setCells] = useState(() => Array(rows * cols).fill(false))
 
  const toggle = (index) => {
    setCells((prev) => prev.map((cell, i) => (i === index ? !cell : cell)))
  }
 
  const getCell = (row, col) => cells[row * cols + col]
 
  return { cells, toggle, getCell, rows, cols }
}

What Interviewers Look For

CriteriaHow to demonstrate
State management2D array or flat array with clear indexing
Immutable updatesmap — never direct mutation
Component designHook + presentational components
Event handlingCorrect row/col passed to handler
Clean codeNamed functions, no magic numbers
Bonusa11y (aria-pressed), select all, reset

Back to index


9. Higher Order Components (HOC)

Theory

A HOC is a function that takes a component and returns an enhanced component with extra props or behavior: const Enhanced = withAuth(Wrapped).

Legacy pattern — custom hooks are preferred today for the same reuse without wrapper nesting.

Interview Answer

An HOC wraps a component to inject behavior or data — like withAuth. I know them for legacy code, but prefer custom hooks for new projects.

Real Example

jsx
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const { user } = useAuth()
    if (!user) return <Navigate to="/login" />
    return <WrappedComponent {...props} user={user} />
  }
}
 
export default withAuth(Dashboard)

Back to index


10. How do you integrate RTK Query with your React components?

Answer

You use the auto-generated hooks like useGetUsersQuery, useAddUserMutation, etc., directly in your components to fetch and manipulate data.

Back to index


11. How do you structure reusable UI components in React?

Theory

A reusable component library follows atomic design principles: atoms (Button, Input) → molecules (SearchBar, FormField) → organisms (Header, ProductCard) → templates → pages. Components should be composable, accessible, themeable, and documented.

Pros & Cons

ApproachProsCons
Monolithic componentsFast to build initiallyHard to customize, prop explosion
Compound componentsFlexible compositionSteeper learning curve
Headless + styledLogic reuse across designsTwo layers to maintain
Design tokensConsistent themingSetup overhead

Real-Life Example

plaintext
packages/
├── tokens/                 # Design tokens
│   ├── colors.json
│   ├── spacing.json
│   └── typography.json
├── primitives/             # Atoms — unstyled, accessible
│   ├── Button/
│   ├── Input/
│   └── Checkbox/
├── components/             # Molecules & organisms
│   ├── SearchBar/
│   ├── ProductCard/
│   └── Modal/
└── docs/                   # Storybook
tsx
// Compound component pattern — flexible Modal
const Modal = ({ children, isOpen, onClose }) => {
  if (!isOpen) return null
  return createPortal(
    <ModalContext.Provider value={{ onClose }}>
      <div className="modal-overlay" onClick={onClose}>
        {children}
      </div>
    </ModalContext.Provider>,
    document.body,
  )
}
 
Modal.Header = ({ children }) => <header className="modal-header">{children}</header>
Modal.Body = ({ children }) => <div className="modal-body">{children}</div>
Modal.Footer = ({ children }) => <footer className="modal-footer">{children}</footer>
 
// Usage — compose as needed
;<Modal isOpen={open} onClose={close}>
  <Modal.Header>
    <h2>Confirm Order</h2>
  </Modal.Header>
  <Modal.Body>Your total is ₹499. Proceed?</Modal.Body>
  <Modal.Footer>
    <Button variant="ghost" onClick={close}>
      Cancel
    </Button>
    <Button variant="primary" onClick={confirm}>
      Confirm
    </Button>
  </Modal.Footer>
</Modal>

Key principles:

  • Accept className and spread remaining props for extensibility
  • Use forwardRef for focus management
  • Document with Storybook + TypeScript prop types
  • Test accessibility with @testing-library/jest-dom and axe
#TopicKey Point
1Promise.allAll resolve or first reject; order preserved
2Promise.anyFirst resolve wins; all reject = AggregateError
3reduce polyfillAccumulator + callback; handles sparse arrays
4flattenRecursive depth control; flat(Infinity) native
5Auto-retryExponential backoff + jitter
6Batch promisesChunk size + delay between batches
7DebounceWait for pause
8ThrottleOnce per interval
9Series tasksCallback chain or reduce with promises
10Output predictionHoisting, closure, event loop, coercion
11Object vs MapString keys vs any key; JSON vs performance
12PATCH vs PUTPartial vs full replacement
13Debounce vs ThrottleStop vs rate-limit
14JS EngineParse → interpret → JIT optimize → GC
15Event LoopSync → microtasks → macrotask
16Virtual DOMJS tree + diffing heuristics
17KeysStable identity for list reconciliation
18useState internalsHook linked list on Fiber
19useState polyfillArray + index reset on setState
20PortalsRender outside parent DOM
21Error BoundariesCatch render errors; class only
22MemoizationuseMemo / useCallback / React.memo
23SSR vs CSRServer HTML vs client JS render
24Module FederationRuntime remote module sharing
25Micro-frontendsIndependent deployable frontend apps
26SSR for SEOMeta tags, structured data, SSG/ISR
27tabIndex0 = focusable, -1 = programmatic only
28Capturing/BubblingTop-down vs bottom-up event propagation
29toString overridePossible but never mutate prototypes
30Memory leaksCleanup effects, remove listeners
31PerformanceProfiler, Web Vitals, bundle analyzer
32OAuthAuthorization without password sharing
33SSOOne login, multiple apps via IdP
34REST methodsGET read, POST create, PUT replace, PATCH partial, DELETE remove
35FPPure functions, immutability, composition
36MicroservicesSmall independent deployable services
37CRA-like toolCLI + template + bundler + dev server
38UI componentsAtoms → molecules → organisms + Storybook

Back to index


12. How does React decide whether a component should re-render?

Theory

A component re-renders when React schedules an update for it. Triggers include:

TriggerExample
State changesetState, useState setter, useReducer dispatch
Parent re-renderChild re-renders by default (unless memoized)
Context changeConsumer re-renders when context value changes
Force updateforceUpdate (class) — rarely used
External storeuseSyncExternalStore subscription fires

React does not re-render when:

  • Props are the same reference and state unchanged (for memoized components)
  • State update is bailed out because Object.is(oldState, newState) is true

Re-render vs DOM update

Re-render = React calls your component function again and diffs the output. DOM update = Only happens if the diff produces changes (commit phase).

Practical Example

jsx
function Parent() {
  const [count, setCount] = useState(0)
  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <Child name="Amit" /> {/* re-renders on every Parent render */}
      <MemoChild name="Rahul" /> {/* skips if props unchanged */}
    </>
  )
}
 
const MemoChild = React.memo(function MemoChild({ name }) {
  console.log('MemoChild render')
  return <p>{name}</p>
})

Bailout conditions

jsx
function Counter() {
  const [count, setCount] = useState(0)
 
  const handleClick = () => {
    setCount(0) // React bails out — state already 0, no re-render
  }
 
  return <button onClick={handleClick}>{count}</button>
}

Interview answer

Performance

React re-renders when state, context, or parent updates change. By default children re-render with parents. Use React.memo, useMemo, and useCallback selectively — not everywhere — to skip unnecessary work. Re-rendering is cheap; committing DOM changes is the expensive part.

Back to index


13. How would you design a reusable component library?

Architecture

plaintext
packages/
├── tokens/          # Colors, spacing, typography (JSON / CSS vars)
├── primitives/      # Button, Input, Checkbox (unstyled logic)
├── components/      # Composed UI (DatePicker, Modal)
├── icons/
└── docs/            # Storybook

Design principles

PrincipleImplementation
ComposableCompound components (<Select><Select.Trigger /></Select>)
AccessibleWAI-ARIA, keyboard nav, focus management
ThemeableCSS variables or Tailwind preset
TypedTypeScript with strict prop types
Tree-shakeableESM, per-component exports
VersionedSemver, changelog, codemods for breaking changes

Button example

tsx
// packages/components/src/Button/Button.tsx
import { forwardRef } from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import styles from './Button.module.css'
 
const buttonVariants = cva(styles.base, {
  variants: {
    variant: {
      primary: styles.primary,
      ghost: styles.ghost,
      danger: styles.danger,
    },
    size: {
      sm: styles.sm,
      md: styles.md,
      lg: styles.lg,
    },
  },
  defaultVariants: { variant: 'primary', size: 'md' },
})
 
export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
  asChild?: boolean
  loading?: boolean
}
 
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild, loading, children, disabled, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button'
    return (
      <Comp
        ref={ref}
        className={buttonVariants({ variant, size, className })}
        disabled={disabled || loading}
        aria-busy={loading}
        {...props}>
        {loading ? <Spinner /> : children}
      </Comp>
    )
  },
)

Tooling stack

  • Storybook — documentation + visual testing
  • Chromatic — visual regression CI
  • Changesets — versioning and publishing
  • Vitest + Testing Library — unit/a11y tests
  • ESLint plugin — enforce import boundaries

Interview answer

Tip

Start with design tokens, build accessible primitives, compose into higher-level components. Use TypeScript, compound component patterns, CSS variables for theming, Storybook for docs, and publish as tree-shakeable ESM packages with semver and visual regression tests in CI.

Back to index


14. JSX vs HTML — Key Differences

Theory

JSXHTML
classNameclass
htmlForfor
onClick (camelCase)onclick (lowercase)
Self-closing required (<img />)Optional in HTML5
style={{ color: "red" }} (object)style="color: red" (string)
{} for JavaScript expressionsNot available
One root element or FragmentMultiple roots OK in HTML5

Interview Answer

JSX looks like HTML but uses camelCase attributes, className instead of class, JavaScript expressions in curly braces, and compiles to JavaScript — it's not parsed as HTML.

Real Example

jsx
// ❌ Invalid JSX
<div class="card" onclick={handleClick} style="color: blue">
 
// ✅ Valid JSX
<div className="card" onClick={handleClick} style={{ color: "blue" }}>
  {isLoggedIn ? <Dashboard /> : <Login />}
</div>

Back to index


15. Keep business logic separate from UI components

Theory

UI components render markup and handle user interaction. Business logic handles rules, calculations, validations, and data transformations. Mixing them makes components hard to test and reuse.

Separate into: services (API calls), utils (pure functions), hooks (stateful logic), and components (presentation).

Pros & Cons

Separated logicMixed logic in components
✅ Test business rules without rendering❌ Can't unit test calculations
✅ Reuse logic in CLI, mobile, server❌ 300-line components
✅ Designers can work on UI independently❌ Bug fixes risk breaking UI
✅ Easier to swap UI framework—

Real-Life Example

tsx
// utils/pricing.ts — pure business logic, no React
export function calculateOrderTotal(items: CartItem[], coupon?: Coupon): OrderTotal {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  const discount = coupon ? applyCoupon(subtotal, coupon) : 0
  const tax = (subtotal - discount) * 0.05
  const deliveryFee = subtotal > 500 ? 0 : 40
  return {
    subtotal,
    discount,
    tax,
    deliveryFee,
    total: subtotal - discount + tax + deliveryFee,
  }
}
 
// services/orderService.ts — API layer
export const orderService = {
  placeOrder: (payload: OrderPayload) => api.post('/orders', payload),
  cancelOrder: (id: string) => api.patch(`/orders/${id}`, { status: 'cancelled' }),
}
 
// hooks/useCheckout.ts — stateful orchestration
function useCheckout() {
  const [items] = useCart()
  const total = useMemo(() => calculateOrderTotal(items), [items])
 
  const placeOrder = async () => {
    const result = await orderService.placeOrder({ items, total })
    return result
  }
 
  return { items, total, placeOrder }
}
 
// components/CheckoutSummary.tsx — UI only
function CheckoutSummary() {
  const { items, total, placeOrder } = useCheckout()
 
  return (
    <div>
      <ItemList items={items} />
      <PriceBreakdown total={total} />
      <button onClick={placeOrder}>Place Order — ₹{total.total}</button>
    </div>
  )
}

Back to index


16. Keep components small and reusable

Theory

A React component should do one thing well. Small components are easier to read, test, reuse, and debug. The Single Responsibility Principle applies here — a ProductCard displays a product; it shouldn't also handle cart logic, API calls, and modal state.

Rule of thumb: If a component exceeds ~150 lines or has more than one reason to change, split it.

Pros & Cons

Small componentsLarge monolithic components
✅ Easy to test in isolation❌ Hard to reason about
✅ Reusable across pages❌ Copy-paste duplication
✅ Faster to review in PRs❌ One bug affects everything
✅ Clear prop contracts❌ Hidden side effects

Real-Life Example

tsx
// ❌ Bad — one giant component doing everything
function OrderPage() {
  const [orders, setOrders] = useState([])
  const [loading, setLoading] = useState(true)
  const [filter, setFilter] = useState('all')
  // ... 200 more lines of fetch, filter, render, modal logic
}
 
// ✅ Good — composed from small, focused components
function OrderPage() {
  const { orders, loading, error } = useOrders()
  const [filter, setFilter] = useState('all')
 
  if (loading) return <OrderListSkeleton />
  if (error) return <ErrorBanner message={error} />
 
  return (
    <div>
      <OrderFilter value={filter} onChange={setFilter} />
      <OrderList orders={filterOrders(orders, filter)} />
    </div>
  )
}
 
function OrderList({ orders }) {
  return (
    <ul>
      {orders.map((order) => (
        <OrderCard key={order.id} order={order} />
      ))}
    </ul>
  )
}
 
function OrderCard({ order }) {
  return (
    <li className="order-card">
      <OrderStatusBadge status={order.status} />
      <span>{order.itemName}</span>
      <span>₹{order.total}</span>
    </li>
  )
}

Back to index


17. Props in React

Theory

Props (properties) pass data from parent to child. They are read-only — a child must never mutate props. Props make components reusable with different data.

Pros & Cons

ProsCons
Explicit data flowProp drilling at depth
Easy to test with different inputsMany props → hard to maintain

Interview Answer

Props are read-only inputs passed from parent to child. They let me reuse the same component with different data.

Real Example

jsx
function ProductCard({ name, price, onAddToCart }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>₹{price}</p>
      <button onClick={onAddToCart}>Add to Cart</button>
    </div>
  )
}
 
;<ProductCard name="Biryani" price={299} onAddToCart={() => addItem('biryani')} />

Back to index


18. React Fragments

Theory

Fragments (<>...</> or <React.Fragment>) let you group children without adding an extra DOM node. Useful when a component must return multiple siblings.

Pros & Cons

ProsCons
No wrapper div pollutionCan't style the fragment itself
Cleaner DOMNamed Fragment needs React.Fragment for keys

Interview Answer

Fragments group multiple elements without an extra DOM node — I use them when returning sibling elements and a wrapper div would break layout or semantics.

Real Example

jsx
function TableRow({ name, email }) {
  return (
    <>
      <td>{name}</td>
      <td>{email}</td>
    </>
  )
}
 
// With key in a list — must use React.Fragment
items.map((item) => (
  <React.Fragment key={item.id}>
    <dt>{item.label}</dt>
    <dd>{item.value}</dd>
  </React.Fragment>
))

Back to index


19. Star Rating Component (1–10)

Theory

Build an interactive star rating from 1 to 10 — click to rate, hover preview, accessible with keyboard and ARIA.

Interview Answer

I render 10 star buttons, track hover and selected state, use aria-label for accessibility, and support keyboard navigation with arrow keys.

React Implementation

tsx
import { useState } from 'react'
 
function StarRating({
  max = 10,
  value,
  onChange,
  readOnly = false,
}: {
  max?: number
  value?: number
  onChange?: (rating: number) => void
  readOnly?: boolean
}) {
  const [hover, setHover] = useState(0)
  const [selected, setSelected] = useState(value ?? 0)
  const display = hover || selected
 
  const handleClick = (rating: number) => {
    if (readOnly) return
    setSelected(rating)
    onChange?.(rating)
  }
 
  const handleKeyDown = (e: React.KeyboardEvent, rating: number) => {
    if (readOnly) return
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault()
      handleClick(rating)
    }
    if (e.key === 'ArrowRight') handleClick(Math.min(rating + 1, max))
    if (e.key === 'ArrowLeft') handleClick(Math.max(rating - 1, 1))
  }
 
  return (
    <div role="radiogroup" aria-label={`Rating out of ${max}`} onMouseLeave={() => !readOnly && setHover(0)}>
      {Array.from({ length: max }, (_, i) => {
        const rating = i + 1
        const filled = rating <= display
        return (
          <button
            key={rating}
            type="button"
            role="radio"
            aria-checked={selected === rating}
            aria-label={`${rating} out of ${max}`}
            disabled={readOnly}
            onClick={() => handleClick(rating)}
            onMouseEnter={() => !readOnly && setHover(rating)}
            onKeyDown={(e) => handleKeyDown(e, rating)}
            style={{
              background: 'none',
              border: 'none',
              cursor: readOnly ? 'default' : 'pointer',
              fontSize: '1.5rem',
              color: filled ? '#f59e0b' : '#d1d5db',
            }}>
            ★
          </button>
        )
      })}
      <span aria-live="polite">{selected > 0 ? `${selected}/${max}` : ''}</span>
    </div>
  )
}
 
// Usage
;<StarRating max={10} onChange={(r) => console.log('Rated:', r)} />

Vanilla JS Version

javascript
function createStarRating(container, max = 10, onRate) {
  container.setAttribute('role', 'radiogroup')
  container.innerHTML = ''
 
  for (let i = 1; i <= max; i++) {
    const star = document.createElement('button')
    star.textContent = '★'
    star.setAttribute('aria-label', `${i} out of ${max}`)
    star.style.cssText = 'background:none;border:none;font-size:24px;cursor:pointer;color:#d1d5db'
 
    star.onmouseenter = () => highlight(i)
    star.onclick = () => {
      onRate?.(i)
      highlight(i, true)
    }
    container.appendChild(star)
  }
 
  container.onmouseleave = () => highlight(0)
 
  function highlight(upTo, persist = false) {
    container.querySelectorAll('button').forEach((btn, idx) => {
      btn.style.color = idx < upTo ? '#f59e0b' : '#d1d5db'
    })
  }
}

Back to index


20. State in React

Theory

State is data that changes over time inside a component. When state updates, React re-renders the component. State is private to the component that owns it (unless lifted up or shared via context).

Managed with useState or useReducer.

Pros & Cons

Local stateGlobal state for everything
✅ Colocated, simple❌ Over-engineering
✅ Component owns its data❌ Hard to trace changes

Interview Answer

State is mutable data owned by a component. When it changes, React re-renders. I use useState for simple values and lift state up when siblings need to share it.

Real Example

jsx
function SearchBar() {
  const [query, setQuery] = useState('')
  return <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." />
}

Back to index


21. State vs Props

Theory

PropsState
SourceParentComponent itself
Mutable?No (read-only)Yes (via setter)
PurposeConfigure componentTrack changing data
AnalogyFunction argumentsVariables inside function

Interview Answer

Props are passed in from outside and are read-only. State is owned internally and changes over time. Props configure a component; state tracks its live data.

Real Example

jsx
function Counter({ initialCount }) {
  // prop — set once by parent
  const [count, setCount] = useState(initialCount) // state — changes on click
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

Back to index


22. Synthetic Events

Theory

React wraps native browser events in SyntheticEvent — a cross-browser normalized wrapper. React uses event delegation (events attached at root, not each node) for performance.

e.persist() is rarely needed in React 17+ (events no longer pooled).

Interview Answer

Synthetic events are React's cross-browser wrapper around native events. React delegates events at the root for performance — call preventDefault and stopPropagation normally.

Real Example

jsx
function SearchInput() {
  const handleSubmit = (e) => {
    e.preventDefault() // works on SyntheticEvent
    fetchResults()
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <input onChange={(e) => setQuery(e.target.value)} />
    </form>
  )
}

Back to index


23. Understand when components re-render

Theory

A component re-renders when:

  1. Its own state changes (useState, useReducer)
  2. Its parent re-renders (children re-render by default)
  3. Context it consumes changes
  4. Props change (for React.memo components — shallow compare)

Re-render ≠ DOM update. React may re-run your component but skip DOM changes if output is identical.

Pros & Cons

Understanding re-rendersIgnoring re-render causes
✅ Targeted optimization❌ Random React.memo everywhere
✅ Fewer performance bugs❌ Unnecessary API calls in effects
✅ Correct dependency arrays❌ Stale UI state

Real-Life Example

tsx
function App() {
  const [count, setCount] = useState(0)
  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
      <ExpensiveChild name="Dashboard" /> {/* re-renders on every count click! */}
    </>
  )
}
 
// Fix — memoize child that doesn't depend on count
const ExpensiveChild = React.memo(function ExpensiveChild({ name }) {
  console.log('ExpensiveChild rendered')
  return <div>{name}</div>
})
 
// Debug re-renders in development
function DebugRender({ name }) {
  useEffect(() => {
    console.log(`${name} rendered at`, new Date().toISOString())
  })
  return null
}

Re-render triggers cheat sheet:

TriggerExample
State updatesetCount(1)
Parent re-renderParent state changes → all children re-render
Context changeTheme context value changes
Force updatekey prop change forces remount
No re-rendersetCount(0) when count is already 0 (bailout)

Back to index


24. What are controlled components?

Answer

Controlled components are form elements whose values are controlled by React state. The value of the input field is set by the component's state.

jsx
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />

Back to index


25. What are uncontrolled components?

Answer

Uncontrolled components manage their own state using the DOM, typically accessed via refs in React.

jsx
const inputRef = useRef()
 
function handleSubmit() {
  alert(`Input: ${inputRef.current.value}`)
}
 
;<input ref={inputRef} type="text" />

Back to index


26. What is a class component?

Answer

A class component is an ES6 class that extends React.Component and has a render() method. It can maintain its own state and lifecycle methods.

jsx
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

Back to index


27. What is a functional component?

Answer

A functional component is a simple JavaScript function that returns JSX. It doesn't have its own state unless you use React hooks like useState.

jsx
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>
}

Back to index


28. What is a Higher Order Component (HOC)?

Theory

A Higher Order Component is a function that takes a component and returns a new enhanced component with additional props or behavior. Pattern: const Enhanced = withFeature(WrappedComponent).

HOCs were the primary reuse pattern before Hooks. They solve cross-cutting concerns: authentication, logging, data fetching, error handling.

Pros & Cons

ProsCons
Reusable logic across components"Wrapper hell" — deeply nested component tree
Separation of concernsProps collision (must rename injected props)
Works with class componentsHarder to trace in DevTools
Hooks and custom hooks are preferred today

Real-Life Example

withAuth HOC

tsx
function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const { user, isLoading } = useAuth()
 
    if (isLoading) return <Spinner />
    if (!user) return <Navigate to="/login" />
 
    return <WrappedComponent {...props} user={user} />
  }
}
 
// Usage
function Dashboard({ user }) {
  return <h1>Welcome, {user.name}</h1>
}
 
export default withAuth(Dashboard)

withLoading HOC

tsx
function withLoading(WrappedComponent, fetchData) {
  return function WithLoadingComponent(props) {
    const [data, setData] = useState(null)
    const [loading, setLoading] = useState(true)
 
    useEffect(() => {
      fetchData(props).then((result) => {
        setData(result)
        setLoading(false)
      })
    }, [props.id])
 
    if (loading) return <Spinner />
    return <WrappedComponent {...props} data={data} />
  }
}
 
const RestaurantPage = withLoading(
  ({ data }) => <RestaurantDetail restaurant={data} />,
  (props) => axios.get(`/api/restaurants/${props.id}`).then((r) => r.data),
)

Modern alternative — custom hooks (preferred)

tsx
// ✅ Today — custom hook instead of HOC
function useAuth() {
  const [user, setUser] = useState(null)
  const [isLoading, setIsLoading] = useState(true)
 
  useEffect(() => {
    apiClient
      .get('/api/me')
      .then((res) => setUser(res.data))
      .finally(() => setIsLoading(false))
  }, [])
 
  return { user, isLoading, isAuthenticated: !!user }
}
 
function Dashboard() {
  const { user, isLoading, isAuthenticated } = useAuth()
  if (isLoading) return <Spinner />
  if (!isAuthenticated) return <Navigate to="/login" />
  return <h1>Welcome, {user.name}</h1>
}

Interview answer

Warning

An HOC is a function that wraps a component to add behavior or data. Common examples: withAuth, withLoading. Today, custom hooks are preferred because they avoid wrapper hell and props naming collisions. Know HOCs for legacy codebases, but recommend hooks for new code.

Back to index


29. What is JSX?

Theory

JSX (JavaScript XML) is syntax that lets you write HTML-like markup inside JavaScript. It is not HTML — it compiles to React.createElement() calls via Babel.

Rules: one parent element (or Fragment), camelCase attributes (className, onClick), expressions in {}.

Pros & Cons

ProsCons
Readable, visual UI codeCan confuse HTML vs JSX differences
Catches errors at compile timeNeeds build step (Babel)

Interview Answer

JSX is syntactic sugar that lets me write UI markup in JavaScript. Babel converts it to React.createElement calls at build time.

Real Example

jsx
// JSX
const element = (
  <button className="btn" onClick={handleClick}>
    Pay ₹{amount}
  </button>
)
 
// Compiles to:
const element = React.createElement('button', { className: 'btn', onClick: handleClick }, 'Pay ₹', amount)

Back to index


30. What is React and why is it efficient?

Theory

React is a JavaScript library for building user interfaces using a component-based, declarative model. You describe what the UI should look like for a given state, and React handles updating the DOM when state changes.

React is efficient because it:

  1. Virtual DOM — compares trees and applies minimal DOM mutations instead of rewriting the entire page
  2. Batched updates — groups multiple state changes into one re-render (React 18 automatic batching)
  3. Fiber architecture — interruptible rendering; urgent updates (typing) aren't blocked by heavy work
  4. One-way data flow — predictable state propagation from parent to child
  5. Component reusability — write once, compose everywhere

Pros & Cons

ProsCons
Large ecosystem and communityJSX learning curve for beginners
Reusable component modelNeeds additional libraries for routing, state (not a full framework)
Strong hiring marketFrequent ecosystem changes (hooks, RSC, etc.)
Virtual DOM minimizes expensive DOM opsCan over-render without memoization discipline

Real-Life Example

jsx
// Declarative — you describe UI based on state
function OrderTracker({ orderId }) {
  const { data: order, isLoading } = useOrder(orderId)
 
  if (isLoading) return <Skeleton />
  if (order.status === 'delivered') return <DeliveredBanner order={order} />
 
  return (
    <div>
      <h2>Order #{order.id}</h2>
      <StatusTimeline steps={order.timeline} />
      <LiveMap driverLocation={order.driverLocation} />
    </div>
  )
}
 
// React efficiently updates only what changed:
// - Status text changes → only that DOM node updates
// - Driver location updates every 5s → only map marker moves
// - You never manually call document.getElementById or innerHTML

Interview answer (concise)

Performance

React is a component-based UI library. You declare UI as a function of state. It's efficient because of Virtual DOM diffing, batched updates, and the Fiber engine that prioritizes user interactions over background rendering.

Back to index


31. What is React Developer Tools?

Answer

A browser extension (Chrome/Firefox) that helps developers inspect the component hierarchy, state, props, hooks, and more in React applications.

Back to index


32. What is React?

Theory

React is a JavaScript library for building user interfaces using reusable components. You describe UI as a function of state — when state changes, React updates the screen efficiently.

It is not a full framework (no built-in routing or API layer). It focuses on the view layer.

Pros & Cons

ProsCons
Component reusabilityNeeds extra libraries (router, state)
Virtual DOM — efficient updatesJSX learning curve
Huge ecosystem and job marketFast-moving ecosystem

Interview Answer

React is a component-based UI library. You build reusable pieces, declare what the UI should look like for a given state, and React handles updating the DOM efficiently.

Real Example

jsx
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>
}
// Reuse anywhere: <Welcome name="Amit" />

Back to index


Hooks

33. Clean up subscriptions, timers, and event listeners inside useEffect

Theory

Effects that create subscriptions, timers, WebSockets, or event listeners must return a cleanup function. Without cleanup, you get memory leaks, stale state updates on unmounted components, and duplicate listeners.

Cleanup runs: (1) before the effect re-runs when deps change, (2) when the component unmounts.

Pros & Cons

With cleanupWithout cleanup
✅ No memory leaks❌ WebSocket stays open after navigate away
✅ No "setState on unmounted component" warnings❌ Duplicate event listeners on re-render
✅ Aborted stale API responses❌ Timers fire after unmount

Real-Life Example

tsx
function LiveOrderTracker({ orderId }) {
  const [status, setStatus] = useState('pending')
 
  useEffect(() => {
    // WebSocket
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`)
    ws.onmessage = (e) => setStatus(JSON.parse(e.data).status)
 
    // Timer
    const pollInterval = setInterval(() => {
      fetch(`/api/orders/${orderId}/status`)
        .then((r) => r.json())
        .then((data) => setStatus(data.status))
    }, 5000)
 
    // Window event
    const handleVisibility = () => {
      if (document.visibilityState === 'visible') ws.send('ping')
    }
    document.addEventListener('visibilitychange', handleVisibility)
 
    // Cleanup everything
    return () => {
      ws.close()
      clearInterval(pollInterval)
      document.removeEventListener('visibilitychange', handleVisibility)
    }
  }, [orderId])
 
  return <StatusBadge status={status} />
}

Cleanup checklist:

ResourceCleanup
setInterval / setTimeoutclearInterval / clearTimeout
addEventListenerremoveEventListener
WebSocketws.close()
fetch / AxiosAbortController.abort()
subscribe() (RxJS, store)unsubscribe()

Back to index


34. Custom Hooks

Theory

A custom hook is a function starting with use that encapsulates reusable stateful logic. It can call other hooks. It is not a component — it returns data and functions.

Pros & Cons

ProsCons
DRY — share logic across componentsMust follow Rules of Hooks
Keeps components cleanOver-abstraction if too granular

Interview Answer

Custom hooks extract reusable stateful logic — fetch, debounce, localStorage — into a use function shared across components.

Real Example

jsx
function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
 
  useEffect(() => {
    fetch(url)
      .then((r) => r.json())
      .then(setData)
      .finally(() => setLoading(false))
  }, [url])
 
  return { data, loading }
}
 
// Usage
function ProductPage() {
  const { data, loading } = useFetch('/api/products')
  if (loading) return <Spinner />
  return <ProductList products={data} />
}

Back to index


35. Dependency Array

Theory

The second argument of useEffect controls when it re-runs:

ArrayBehavior
OmittedRuns after every render
[]Runs once on mount
[a, b]Runs on mount + when a or b changes

React compares deps with Object.is (shallow equality).

Pros & Cons

Correct depsMissing/wrong deps
✅ Synced with latest state❌ Stale closures
✅ Predictable behavior❌ Infinite loops with objects

Interview Answer

The dependency array tells React when to re-run the effect. Empty array means mount only; listing values means re-run when they change. Missing deps cause stale bugs.

Real Example

jsx
useEffect(() => {
  const timer = setInterval(() => tick(), 1000)
  return () => clearInterval(timer) // cleanup
}, []) // mount only — empty array
 
useEffect(() => {
  fetchOrders(userId)
}, [userId]) // re-run when userId changes

Back to index


36. Difference between useMemo, useCallback, and React.memo

Theory

Hook/HOCWhat it memoizesWhen it helps
useMemoComputed valueExpensive calculations
useCallbackFunction referenceStable ref for memoized children / effect deps
React.memoComponent renderSkip re-render if props unchanged (shallow compare)

Practical Example

jsx
function ProductList({ products, onSelect }) {
  // useMemo — cache expensive filter/sort result
  const sorted = useMemo(() => [...products].sort((a, b) => a.price - b.price), [products])
 
  // useCallback — stable function reference for memoized child
  const handleSelect = useCallback((id) => onSelect(id), [onSelect])
 
  return sorted.map((p) => <ProductRow key={p.id} product={p} onSelect={handleSelect} />)
}
 
const ProductRow = React.memo(function ProductRow({ product, onSelect }) {
  return (
    <div onClick={() => onSelect(product.id)}>
      {product.name} — ₹{product.price}
    </div>
  )
})

Common mistake

jsx
// ❌ useMemo on cheap operations — adds overhead, no benefit
const doubled = useMemo(() => count * 2, [count])
 
// ❌ useCallback without React.memo child — pointless
const fn = useCallback(() => doThing(), [])
return <RegularChild onClick={fn} /> // RegularChild always re-renders anyway

Interview answer

Performance

useMemo caches values, useCallback caches functions, React.memo skips component re-renders. Use them when profiling shows a problem — not preemptively. They have their own memory and comparison cost.

Back to index


37. Explain how useState works internally

Theory

useState stores state on the Fiber node associated with the component. Each hook call creates a hook object linked in a singly-linked list on the Fiber (memoizedState).

On each render:

  1. React walks the hook list in the same order hooks were called
  2. Returns the current memoizedState value
  3. The setter enqueues an update on the Fiber's update queue
  4. React schedules a re-render; during the next render, the hook returns the new value

Rules of Hooks exist because hook order must be identical every render.

Pros & Cons

ProsCons
Simple API for local stateState updates are async (batched)
Triggers re-render automaticallyCannot call conditionally
Batching improves performanceStale closure if deps not managed

Real-Life Example

jsx
function Counter() {
  const [count, setCount] = useState(0) // Hook #1 on Fiber
  const [name, setName] = useState('') // Hook #2 on Fiber
 
  // Internally on Fiber:
  // memoizedState → { memoizedState: count, queue: [updates], next: hook2 }
  // hook2 → { memoizedState: name, queue: [], next: null }
 
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

Back to index


38. Fetching Data with useEffect

Theory

Classic pattern: useEffect + fetch + local state for data, loading, error. Always handle cleanup with AbortController to cancel in-flight requests on unmount.

Interview Answer

I fetch in useEffect with AbortController cleanup, track loading and error state, and re-fetch when dependencies like ID change.

Real Example

jsx
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
 
  useEffect(() => {
    const controller = new AbortController()
    setLoading(true)
    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then((r) => {
        if (!r.ok) throw new Error(r.statusText)
        return r.json()
      })
      .then(setUser)
      .catch((e) => {
        if (e.name !== 'AbortError') setError(e)
      })
      .finally(() => setLoading(false))
    return () => controller.abort()
  }, [userId])
}

Back to index


39. Implement a basic version of useState

Theory

A minimal useState implementation uses a module-level array to store state per hook index, incrementing the index on each call. A render function re-executes the component. This mirrors React's hook linked list but simplified for learning.

Real-Life Example

javascript
let hooks = []
let hookIndex = 0
 
function useState(initialValue) {
  const currentIndex = hookIndex
 
  if (hooks[currentIndex] === undefined) {
    hooks[currentIndex] = typeof initialValue === 'function' ? initialValue() : initialValue
  }
 
  const setState = (newValue) => {
    const nextValue = typeof newValue === 'function' ? newValue(hooks[currentIndex]) : newValue
 
    if (Object.is(hooks[currentIndex], nextValue)) return // bailout
 
    hooks[currentIndex] = nextValue
    hookIndex = 0 // reset for re-render
    render() // trigger re-render
  }
 
  hookIndex++
  return [hooks[currentIndex], setState]
}
 
// Usage
function Counter() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('Guest')
  console.log(`Hello ${name}, count: ${count}`)
}
 
function render() {
  hookIndex = 0
  Counter()
}
 
render()
// setCount(1) → re-renders with updated count

Back to index


40. Memoization (useMemo, useCallback)

Theory

HookMemoizesUse when
useMemoComputed valueExpensive calculations
useCallbackFunction referenceStable ref for memoized children
React.memoComponent renderProps unchanged → skip re-render

React compares with Object.is (shallow equality). Profile before optimizing.

Pros & Cons

ProsCons
Skips expensive recalculationsMemory overhead for cached values
Prevents child re-rendersShallow compare misses deep changes
Essential for virtualizationOveruse adds complexity

Real-Life Example

tsx
function DataTable({ rows, filters }) {
  const filtered = useMemo(() => rows.filter((r) => matchesFilters(r, filters)), [rows, filters])
 
  const handleEdit = useCallback((id: string) => {
    openEditor(id)
  }, [])
 
  return filtered.map((row) => <MemoRow key={row.id} row={row} onEdit={handleEdit} />)
}
 
const MemoRow = React.memo(function MemoRow({ row, onEdit }) {
  return (
    <tr onClick={() => onEdit(row.id)}>
      <td>{row.name}</td>
    </tr>
  )
})

Rule: Profile first → optimize hot paths only.

Back to index


41. Rules of Hooks

Theory

  1. Only call hooks at the top level — not inside conditions, loops, or nested functions
  2. Only call hooks from React functions — components or custom hooks

Why: React stores hooks in a linked list indexed by call order. Conditional hooks break the order.

Interview Answer

Hooks must run in the same order every render — top level only, never inside if or loops. Put conditions inside the hook, not around it.

Real Example

jsx
// ❌ WRONG
if (loggedIn) {
  const [user, setUser] = useState(null)
}
 
// ✅ CORRECT
const [user, setUser] = useState(null)
useEffect(() => {
  if (loggedIn) fetchUser().then(setUser)
  else setUser(null)
}, [loggedIn])

Back to index


42. Single state object vs individual useState

Theory

Single objectIndividual useState
UpdatesetForm({...form, pan: x})setPan(x)
Re-render scopeWhole form re-rendersWhole component still re-renders*
ValidationEasy cross-field rulesMust coordinate multiple states
SubmitOne object readyMust assemble
RiskMutating state directly10+ setter names

*Individual useState still re-renders the whole function component — memoized children help.

What breaks in each

Single object breaks when...Individual useState breaks when...
You mutate: form.pan = xCross-field validation (PAN name match)
Spread typo misses fieldsSubmit assembly forgotten
One huge re-render tree10 useState calls — verbose
Deep nesting growsDependent fields get messy

Real-Life Example

tsx
// Single object — good for submit, bad for perf without memo
const [form, setForm] = useState({ pan: '', aadhaar: '', name: '' })
const update = (field: string, value: string) => setForm((prev) => ({ ...prev, [field]: value }))
 
// Individual — good for simple fields
const [pan, setPan] = useState('')
const [aadhaar, setAadhaar] = useState('')
 
// useReducer — a fintech app sweet spot for complex forms
type Action =
  | { type: 'SET_FIELD'; field: keyof FormState; value: string }
  | { type: 'SET_ERRORS'; errors: FormErrors }
  | { type: 'RESET' }
 
function formReducer(state: FormState, action: Action): FormState {
  switch (action.type) {
    case 'SET_FIELD':
      return { ...state, [action.field]: action.value }
    case 'RESET':
      return INITIAL
    default:
      return state
  }
}
 
const [form, dispatch] = useReducer(formReducer, INITIAL)

Interview recommendation: useReducer or React Hook Form for 10+ fields with cross-validation.

Back to index


43. Use custom hooks to avoid duplicate logic

Theory

Custom hooks extract reusable stateful logic into functions prefixed with use. They let you share behavior — data fetching, form handling, debouncing, local storage — across components without copy-pasting useState + useEffect blocks.

A custom hook is not a component — it doesn't render UI. It returns data and functions.

Pros & Cons

Custom hooksDuplicate logic in components
✅ DRY — write once, use everywhere❌ Bug fixes needed in multiple places
✅ Testable in isolation❌ Inconsistent behavior across pages
✅ Readable component code❌ Components become bloated
✅ Composable (hooks calling hooks)—

Real-Life Example

tsx
// Custom hook — reusable across the app
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key)
    return stored ? JSON.parse(stored) : initialValue
  })
 
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])
 
  return [value, setValue] as const
}
 
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])
  return debounced
}
 
function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false)
  useEffect(() => {
    const media = window.matchMedia(query)
    setMatches(media.matches)
    const handler = (e: MediaQueryListEvent) => setMatches(e.matches)
    media.addEventListener('change', handler)
    return () => media.removeEventListener('change', handler)
  }, [query])
  return matches
}
 
// Usage — clean components
function SearchPage() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)
  const isMobile = useMediaQuery('(max-width: 768px)')
  const [recentSearches, setRecentSearches] = useLocalStorage<string[]>('recent', [])
 
  useEffect(() => {
    if (debouncedQuery) fetchResults(debouncedQuery)
  }, [debouncedQuery])
 
  return <SearchInput value={query} onChange={setQuery} compact={isMobile} />
}

Back to index


44. Use useMemo and useCallback only when necessary

Theory

useMemo caches a computed value. useCallback caches a function reference. Both have memory and comparison overhead — use them only when profiling shows a real problem or when passing callbacks to React.memo children.

Don't optimize prematurely. Simple components with cheap computations don't benefit.

Pros & Cons

When to useWhen to skip
Expensive filter/sort on large arrayscount * 2
Stable ref for memoized childrenChild isn't wrapped in React.memo
Referential equality for effect depsFunction only used inside same component
Virtualized list item callbacksComponents that always re-render anyway

Real-Life Example

tsx
function ProductCatalog({ products, filters }) {
  // ✅ useMemo — filtering 10,000 products is expensive
  const filtered = useMemo(() => {
    return products.filter((p) => matchesFilters(p, filters)).sort((a, b) => b.rating - a.rating)
  }, [products, filters])
 
  // ✅ useCallback — ProductRow is memoized
  const handleAddToCart = useCallback((productId: string) => {
    addToCart(productId)
  }, [])
 
  return filtered.map((p) => <ProductRow key={p.id} product={p} onAdd={handleAddToCart} />)
}
 
// ❌ Unnecessary memoization
function Greeting({ name }) {
  const greeting = useMemo(() => `Hello, ${name}`, [name]) // pointless
  const onClick = useCallback(() => alert(name), [name]) // pointless
  return <p onClick={onClick}>{greeting}</p>
}

Rule: Profile first with React DevTools Profiler → identify slow renders → then add memoization.

Back to index


45. useCallback

Theory

useCallback(fn, deps) returns a memoized function reference. Prevents child re-renders when passing callbacks to React.memo children.

Pairs with React.memo — useless without memoized children.

Interview Answer

useCallback memoizes a function reference so React.memo children don't re-render unnecessarily. I only use it when passing callbacks to optimized children.

Real Example

jsx
const MemoRow = React.memo(function Row({ item, onDelete }) {
  return (
    <tr>
      <td>{item.name}</td>
      <td>
        <button onClick={() => onDelete(item.id)}>Delete</button>
      </td>
    </tr>
  )
})
 
function Table({ items }) {
  const handleDelete = useCallback((id) => {
    setItems((prev) => prev.filter((i) => i.id !== id))
  }, [])
 
  return items.map((item) => <MemoRow key={item.id} item={item} onDelete={handleDelete} />)
}

Back to index


46. useEffect

Theory

useEffect(callback, deps) runs side effects after render — API calls, subscriptions, DOM manipulation, timers.

Return a cleanup function to prevent leaks on unmount or before re-run.

Interview Answer

useEffect handles side effects after render. I always return a cleanup for subscriptions and timers, and specify deps to control when it re-runs.

Real Example

jsx
useEffect(() => {
  const controller = new AbortController()
  fetch(`/api/users/${id}`, { signal: controller.signal })
    .then((r) => r.json())
    .then(setUser)
  return () => controller.abort()
}, [id])

Back to index


47. useEffect as Lifecycle Replacement

Theory

LifecycleHook equivalent
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [dep])
componentWillUnmountuseEffect(() => { return cleanup }, [])
componentDidUpdate (all)useEffect(() => {}) — avoid

One useEffect can combine mount + unmount + update with cleanup.

Interview Answer

useEffect with empty deps replaces componentDidMount. Adding deps replaces componentDidUpdate. The cleanup function replaces componentWillUnmount.

Real Example

jsx
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId)
    connection.connect()
    return () => connection.disconnect() // unmount + before re-run
  }, [roomId]) // re-run when roomId changes (didUpdate)
}

Back to index


48. useEffect Hook

Theory

useEffect runs side effects after render — API calls, subscriptions, timers, DOM manipulation. It replaces class lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount).

Returns an optional cleanup function that runs before re-run or unmount.

Pros & Cons

ProsCons
One hook for all side effectsEasy to create infinite loops
Cleanup built-inCan become a "god effect"

Interview Answer

useEffect handles side effects after render — fetching data, subscriptions, timers. I return a cleanup function to avoid memory leaks.

Real Example

jsx
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
 
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((r) => r.json())
      .then(setUser)
  }, [userId]) // re-fetch when userId changes
 
  return user ? <ProfileCard user={user} /> : <Spinner />
}

Back to index


49. useEffect vs useMemo — Execution Order

Theory

When both have the same dependency array and deps change:

  1. Component function runs (render phase)
  2. useMemo runs during render — computes cached value synchronously
  3. JSX returned — React diffs virtual tree
  4. Commit phase — DOM updated
  5. Browser paints
  6. useEffect runs — after paint (passive effect)

So useMemo always runs before useEffect — useMemo is part of render; useEffect is after commit.

Interview Answer

useMemo runs during the render phase when deps change — synchronously. useEffect runs after the browser paints. So useMemo always executes first.

Real Example

jsx
function Demo({ id }) {
  console.log('1: render start')
 
  const computed = useMemo(() => {
    console.log('2: useMemo running')
    return expensiveCalc(id)
  }, [id])
 
  useEffect(() => {
    console.log('4: useEffect running')
  }, [id])
 
  console.log('3: render end, computed =', computed)
  return <div>{computed}</div>
}
 
// When id changes:
// 1: render start → 2: useMemo → 3: render end → (paint) → 4: useEffect

Exception: useLayoutEffect runs after DOM update but before paint — still after useMemo (which is during render).

Back to index


50. useMemo

Theory

useMemo(() => compute(a, b), [a, b]) caches a computed value between renders. Recomputes only when deps change. Runs during render phase.

Use for expensive calculations — not for every value (memoization has overhead).

Interview Answer

useMemo caches expensive computed values between renders. I use it when calculation cost exceeds memoization overhead — not for simple operations.

Real Example

jsx
function ProductList({ products, filter, sortBy }) {
  const filtered = useMemo(() => {
    return products.filter((p) => p.category === filter).sort((a, b) => a[sortBy] - b[sortBy])
  }, [products, filter, sortBy])
 
  return filtered.map((p) => <ProductCard key={p.id} product={p} />)
}

Back to index


51. useReducer

Theory

useReducer(reducer, initialState) manages complex state with predictable transitions — like Redux locally.

Pattern: (state, action) => newState. Good for forms, multi-step wizards, state machines.

Interview Answer

useReducer is useState for complex state — I dispatch actions to a reducer when state logic has multiple transitions or nested updates.

Real Example

jsx
function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { ...state, items: [...state.items, action.payload] }
    case 'REMOVE':
      return { ...state, items: state.items.filter((i) => i.id !== action.payload) }
    case 'SET_LOADING':
      return { ...state, loading: action.payload }
    default:
      return state
  }
}
 
function Cart() {
  const [state, dispatch] = useReducer(reducer, { items: [], loading: false })
  return <button onClick={() => dispatch({ type: 'ADD', payload: product })}>Add</button>
}

Back to index


52. useRef & useRef vs useState

Theory

useRef(initial) returns { current: value } — mutating .current does not trigger re-render.

Uses: DOM refs, storing previous values, timers, mutable values across renders.

useRefuseState
Update triggers render?NoYes
Use forDOM, timers, prev valueUI state

Interview Answer

useRef holds a mutable value that persists across renders without causing re-renders — perfect for DOM refs and timer IDs. useState is for data that should update the UI.

Real Example

jsx
function AutoFocusInput() {
  const inputRef = useRef(null)
  const renderCount = useRef(0)
  renderCount.current++
 
  useEffect(() => {
    inputRef.current?.focus()
  }, [])
  return <input ref={inputRef} />
}

Back to index


53. useRef Hook

Theory

useRef returns a mutable object { current: value } that persists across renders without causing re-renders when changed. Uses: DOM references, storing previous values, timers, mutable flags.

Interview Answer

useRef holds a mutable value that persists across renders without triggering re-renders. I use it for DOM access, timers, and values that don't need to be in state.

Real Example

jsx
function SearchInput() {
  const inputRef = useRef(null)
 
  useEffect(() => {
    inputRef.current?.focus() // DOM access
  }, [])
 
  return <input ref={inputRef} type="search" />
}

Back to index


54. useRef vs useState

Theory

useStateuseRef
Triggers re-render?YesNo
Value accessDirect countref.current
Use forUI data that affects renderDOM refs, timers, prev values
Async updatesBatchedSynchronous

Interview Answer

useState triggers re-renders when changed — use it for UI data. useRef persists without re-rendering — use it for DOM refs and values that shouldn't affect the UI.

Real Example

jsx
function Timer() {
  const [seconds, setSeconds] = useState(0)
  const intervalRef = useRef(null) // no re-render needed for timer ID
 
  const start = () => {
    intervalRef.current = setInterval(() => setSeconds((s) => s + 1), 1000)
  }
 
  const stop = () => clearInterval(intervalRef.current)
 
  return (
    <>
      <p>{seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  )
}

Back to index


55. useState

Theory

useState(initialValue) returns [value, setter]. Initial value can be a function for lazy init: useState(() => expensive()).

Functional updates: setCount(c => c + 1) when new state depends on old.

Interview Answer

useState adds local state to functional components. I use functional updaters when the new value depends on the previous one.

Real Example

jsx
const [count, setCount] = useState(0)
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem('user') ?? 'null'))
setCount((prev) => prev + 1)

Back to index


56. useState Hook

Theory

useState adds local state to function components. Returns [value, setter]. Updates are async and batched. Use functional updater when new state depends on old: setCount(c => c + 1).

Interview Answer

useState gives a component local state and a setter. When state changes, React re-renders. I use functional updates when the new value depends on the previous one.

Real Example

tsx
function OrderForm() {
  const [quantity, setQuantity] = useState(1)
  const [items, setItems] = useState<Item[]>([])
 
  const addItem = (product: Product) => {
    setItems((prev) => [...prev, { ...product, quantity }])
  }
 
  return (
    <div>
      <input type="number" value={quantity} onChange={(e) => setQuantity(Number(e.target.value))} />
      <button onClick={() => addItem(selectedProduct)}>Add</button>
    </div>
  )
}

Back to index


57. What are Hooks? Explain useEffect, useCallback, and useMemo

Theory

Hooks are functions that let you use state, side effects, and other React features in function components — without class components. They follow the Rules of Hooks: only call at the top level, only in React functions.

HookPurpose
useStateLocal component state
useEffectSide effects (API, subscriptions, DOM)
useCallbackMemoize function reference
useMemoMemoize computed value
useRefMutable ref without re-render
useContextConsume context

Pros & Cons

ProsCons
Simpler than class lifecycleRules of Hooks can confuse beginners
Reusable custom hooksEasy to overuse useEffect
Less boilerplateStale closure bugs without proper deps

Real-Life Example

useEffect — side effects

tsx
function LiveOrderTracker({ orderId }) {
  const [status, setStatus] = useState('pending')
 
  // Fetch on mount + when orderId changes; cleanup on unmount
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`)
 
    ws.onmessage = (event) => {
      setStatus(JSON.parse(event.data).status)
    }
 
    ws.onerror = () => setStatus('error')
 
    return () => ws.close() // cleanup — prevent memory leak
  }, [orderId])
 
  return <OrderStatusBadge status={status} />
}

useMemo — expensive computation

tsx
function RestaurantList({ restaurants, filters }) {
  // Only re-filter when restaurants or filters change
  const filtered = useMemo(() => {
    return restaurants
      .filter((r) => !filters.cuisine || r.cuisine === filters.cuisine)
      .filter((r) => !filters.minRating || r.rating >= filters.minRating)
      .sort((a, b) => b.rating - a.rating)
  }, [restaurants, filters])
 
  return filtered.map((r) => <RestaurantCard key={r.id} restaurant={r} />)
}

useCallback — stable function reference

tsx
const RestaurantCard = React.memo(function RestaurantCard({ restaurant, onFavorite }) {
  return (
    <div>
      <h3>{restaurant.name}</h3>
      <button onClick={() => onFavorite(restaurant.id)}>♥ Save</button>
    </div>
  )
})
 
function RestaurantList({ restaurants }) {
  const [favorites, setFavorites] = useState([])
 
  // Without useCallback, RestaurantCard re-renders every parent render
  const handleFavorite = useCallback((id) => {
    setFavorites((prev) => (prev.includes(id) ? prev.filter((f) => f !== id) : [...prev, id]))
  }, [])
 
  return restaurants.map((r) => <RestaurantCard key={r.id} restaurant={r} onFavorite={handleFavorite} />)
}

Custom hook — combining hooks

tsx
function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value)
 
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])
 
  return debounced
}
 
// Usage
const debouncedQuery = useDebounce(searchQuery, 300)
useEffect(() => {
  if (debouncedQuery) fetchResults(debouncedQuery)
}, [debouncedQuery])

Back to index


58. What has been your experience with useCallback and useMemo in real projects?

Theory

HookMemoizesPrevents
useCallbackFunction referenceChild re-render (when passed to React.memo child)
useMemoComputed valueExpensive recalculation on every render

Key insight: These are performance optimizations, not correctness tools. Use them after profiling shows a problem — not everywhere by default.

Pros & Cons

ProsCons
Prevents expensive recalculationsAdds memory overhead (cached values)
Stabilizes references for memoized childrenShallow compare can miss deep changes
Fixes stale closure in effect depsOveruse makes code harder to read
Essential for virtualization librariesPremature optimization wastes dev time

Real-Life Example — When they helped

tsx
// Project: Admin dashboard with 500-row data table
 
function AdminDashboard({ filters }) {
  const { data: orders } = useOrders()
 
  // useMemo — filtering 500 orders was taking 40ms per keystroke
  const filteredOrders = useMemo(() => {
    return orders.filter((order) => {
      if (filters.status && order.status !== filters.status) return false
      if (filters.city && order.city !== filters.city) return false
      if (filters.search) {
        return order.customerName.toLowerCase().includes(filters.search.toLowerCase())
      }
      return true
    })
  }, [orders, filters]) // only recalculate when these change
 
  // useCallback — without this, every row re-rendered on parent render
  const handleCancelOrder = useCallback((orderId: string) => {
    cancelOrder(orderId)
    toast.success('Order cancelled')
  }, []) // stable reference
 
  return (
    <VirtualTable
      rows={filteredOrders}
      renderRow={(order) => <OrderRow key={order.id} order={order} onCancel={handleCancelOrder} />}
    />
  )
}
 
const OrderRow = React.memo(function OrderRow({ order, onCancel }) {
  return (
    <tr>
      <td>{order.id}</td>
      <td>{order.customerName}</td>
      <td>
        <button onClick={() => onCancel(order.id)}>Cancel</button>
      </td>
    </tr>
  )
})

Real-Life Example — When NOT to use them

tsx
// ❌ Over-optimization — cheap operations
function UserGreeting({ name }) {
  const greeting = useMemo(() => `Hello, ${name}`, [name]) // pointless
  const handleClick = useCallback(() => console.log(name), [name]) // pointless
  return <p onClick={handleClick}>{greeting}</p>
}
 
// ✅ Just write it simply
function UserGreeting({ name }) {
  return <p onClick={() => console.log(name)}>Hello, {name}</p>
}

My real project experience (answer template)

Note

In our admin dashboard, we had a table with 500+ rows. Profiling showed filtering took 40ms per render. I added useMemo for the filtered list and useCallback for row action handlers, combined with React.memo on row components. Render time dropped from 40ms to 4ms.

Warning

But I don't use them everywhere. For simple components with cheap computations, they add complexity without benefit. My rule: profile first, optimize second.

Back to index


59. What is the role of the dependency array in useEffect?

Theory

The dependency array is the second argument of useEffect. It tells React when to re-run the effect:

Dependency ArrayBehavior
OmittedRuns after every render
[] (empty)Runs once on mount; cleanup on unmount
[a, b]Runs on mount + whenever a or b changes
No array at allRuns after every render (rarely intended)

React compares dependencies using Object.is (shallow equality). If all deps are the same reference/value, the effect is skipped.

Pros & Cons of strict dependency rules

ProsCons
Prevents stale closuresESLint exhaustive-deps can feel noisy
Ensures effects sync with latest stateEasy to cause infinite loops with objects
Makes effect triggers explicitRequires understanding reference equality

Real-Life Example

tsx
function OrderTracker({ orderId }) {
  const [status, setStatus] = useState('loading')
 
  // [] — run once on mount (fetch initial data)
  useEffect(() => {
    fetchOrder(orderId).then(setStatus)
  }, []) // ⚠️ Bug: orderId not in deps — stale if orderId changes
 
  // ✅ Correct — re-fetch when orderId changes
  useEffect(() => {
    setStatus('loading')
    fetchOrder(orderId).then(setStatus)
  }, [orderId])
 
  // [query] — re-run search when query changes
  useEffect(() => {
    const controller = new AbortController()
 
    fetch(`/api/search?q=${query}`, { signal: controller.signal })
      .then((r) => r.json())
      .then(setResults)
 
    return () => controller.abort() // cleanup prevents race condition
  }, [query])
 
  // WebSocket — setup on mount, cleanup on unmount
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`)
    ws.onmessage = (e) => setStatus(JSON.parse(e.data).status)
 
    return () => ws.close() // cleanup — prevents memory leak
  }, [orderId])
}

Common mistakes

tsx
// ❌ Missing dependency — stale closure
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1) // always uses initial count (0)
  }, 1000)
  return () => clearInterval(interval)
}, [])
 
// ✅ Fix 1 — functional updater (no dep needed)
useEffect(() => {
  const interval = setInterval(() => {
    setCount((c) => c + 1)
  }, 1000)
  return () => clearInterval(interval)
}, [])
 
// ✅ Fix 2 — add count to deps
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1)
  }, 1000)
  return () => clearInterval(interval)
}, [count])
 
// ❌ Object/array in deps — infinite loop (new reference every render)
useEffect(() => {
  fetchData(filters)
}, [filters]) // if filters = { status: "active" } created inline → infinite loop
 
// ✅ Fix — memoize or use primitive deps
const filters = useMemo(() => ({ status, city }), [status, city])
useEffect(() => {
  fetchData(filters)
}, [filters])

Cleanup function role

The return value of useEffect is a cleanup function that runs:

  1. Before the effect re-runs (deps changed)
  2. When the component unmounts

Use cleanup for: timers, subscriptions, event listeners, WebSockets, AbortControllers.

Interview answer (concise)

Note

The dependency array controls when useEffect re-runs. Empty array means mount-only. Listing values means re-run when they change. React uses shallow comparison. Missing deps cause stale closures; unstable references cause infinite loops. Always return a cleanup function for subscriptions and async work.

#TopicOne-liner
1React & efficiencyComponent-based, Virtual DOM, batched updates, Fiber
2React internalsFiber → render phase → commit → effects
3Challenging taskSTAR method, quantify impact, show trade-offs
4JS couplingFlexible language; loose coupling via modules, DI, composition
5TypeScriptStatic types, catch bugs early, better tooling
6type vs interfaceinterface = objects + merging; type = unions + utilities
7Redux flowdispatch → reducer → store → UI; RTK simplifies
8RTK vs QueryRTK = client state; Query = server state
9bind vs applyapply = invoke now with array args; bind = returns new function
10useMemo/useCallbackProfile first; memoize expensive work + stable child refs
11useEffect depsControls re-run timing; cleanup prevents leaks

Back to index


60. What is useRef?

Answer

useRef is a hook that returns a mutable ref object whose .current property persists across renders. Useful for accessing DOM nodes or keeping mutable variables.

jsx
const inputRef = useRef()
;<input ref={inputRef} />

Back to index


61. Why Do We Use Custom Hooks?

Theory

Custom hooks are functions starting with use that compose built-in hooks to extract reusable stateful logic. They share logic, not state — each call gets its own state.

Why use them?

ReasonExample
Reuse logicuseFetch, useDebounce, useLocalStorage
Separate concernsUI component stays clean; logic in hook
TestabilityTest hook with @testing-library/react-hooks
ReadabilityComponent reads like a spec: useAuth(), useCart()
Replace HOCs/render propsModern pattern for shared behavior

Interview Answer

Custom hooks extract reusable stateful logic into functions — same hooks rules apply. They keep components focused on UI and let me share fetch, debounce, and auth logic across the app.

Real Example

jsx
// Custom hook — logic
function useLocalStorage(key, initialValue) {
  const [stored, setStored] = useState(() => {
    try {
      const item = localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch {
      return initialValue
    }
  })
 
  const setValue = (value) => {
    const next = value instanceof Function ? value(stored) : value
    setStored(next)
    localStorage.setItem(key, JSON.stringify(next))
  }
 
  return [stored, setValue]
}
 
// Component — UI only
function SettingsPage() {
  const [theme, setTheme] = useLocalStorage('theme', 'light')
 
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>
  )
}

Rules Reminder

  • Name must start with use
  • Can call other hooks inside
  • Same Rules of Hooks — top level only, no conditions

Back to index


Rendering & Reconciliation

62. CSR vs SSR

Theory

CSRSSR
Render locationBrowserServer
First HTMLEmpty shell + JS bundleFull HTML with data
SEOPoor without extra workExcellent
TTFBFastSlower (server work)
InteractivityAfter JS loadsAfter hydration
Best forDashboards, auth appsSEO pages, public content

Interview Answer

CSR renders in the browser — fast shell but poor SEO. SSR renders on the server — full HTML on first load, better SEO, but higher server cost.

Real Example

jsx
// CSR — Vite/CRA SPA
// index.html: <div id="root"></div>
// User sees blank → JS downloads → React renders
 
// SSR — Next.js
export async function getServerSideProps() {
  const products = await fetchProducts()
  return { props: { products } }
}
export default function Page({ products }) {
  return <ProductList data={products} /> // HTML sent with data
}

Back to index


63. CSR vs SSR vs SSG & Hydration

Theory

CSRSSRSSG
RenderBrowserServer per requestBuild time
First paintSlow (JS first)Fast (HTML first)Fastest
SEOPoorGoodGood
Data freshnessClient fetchPer requestRebuild to update

Hydration: SSR/SSG sends HTML; React attaches event listeners and state on the client without re-creating DOM.

Interview Answer

CSR renders in browser — good for dashboards. SSR renders per request — good for dynamic SEO pages. SSG pre-renders at build — good for blogs. Hydration makes server HTML interactive on the client.

Real Example

jsx
// Next.js SSR
export async function getServerSideProps() {
  const posts = await fetchPosts()
  return { props: { posts } }
}
 
// Next.js SSG
export async function getStaticProps() {
  const posts = await fetchPosts()
  return { props: { posts }, revalidate: 60 } // ISR
}

Back to index


64. CSR vs SSR vs SSG vs ISR

Theory

Four rendering strategies for web applications:

CSRSSRSSGISR
When renderedBrowserServer per requestBuild timeBuild + revalidate
HTML on first loadEmpty shellFull HTMLFull HTMLFull HTML (cached)
SEOPoorExcellentExcellentExcellent
TTFBFastSlowerFast (CDN)Fast (CDN)
Data freshnessClient fetchAlways freshStale until rebuildConfigurable TTL
Server costLowHighLowLow

Pros & Cons

StrategyBest for
CSRDashboards, authenticated apps
SSRPersonalized, SEO-critical, always-fresh
SSGBlogs, marketing, docs
ISRE-commerce listings, large catalogs

Real-Life Example

tsx
// CSR — Create React App / Vite SPA
// index.html: <div id="root"></div>
// JS downloads → React renders in browser
 
// SSR — Next.js getServerSideProps
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id)
  return { props: { product } }
}
 
// SSG — Next.js getStaticProps (build time)
export async function getStaticProps() {
  const posts = await fetchAllPosts()
  return { props: { posts } }
}
 
// ISR — revalidate every 60 seconds
export async function getStaticProps() {
  const products = await fetchProducts()
  return { props: { products }, revalidate: 60 }
}
plaintext
Decision tree:
  Need SEO? → No → CSR
  Need SEO? → Yes → Data changes frequently? → Yes → SSR or ISR
                                      → No → SSG

Back to index


65. Explain React Fiber Architecture

Theory

Fiber is React's reconciliation engine (React 16+). Each component, DOM node, or hook has a Fiber node — a JS object representing a unit of work.

Why Fiber replaced the old stack reconciler

Old Stack ReconcilerFiber
Synchronous, recursiveIncremental, interruptible
Blocks main threadCan pause and resume
No prioritizationPriority lanes (urgent vs deferred)

Fiber node structure (simplified)

javascript
{
  type: 'div',           // component type
  stateNode: domNode,    // real DOM node (for host components)
  child: Fiber,          // first child
  sibling: Fiber,        // next sibling
  return: Fiber,         // parent
  alternate: Fiber,      // previous version (double buffering)
  pendingProps: {},
  memoizedState: {},     // hooks linked list lives here
  flags: Placement,      // side effects to commit
}

Two phases

plaintext
┌─────────────────────────────────────────────────────────┐
│  RENDER PHASE (interruptible)                           │
│  - Walk Fiber tree                                      │
│  - Call component functions                             │
│  - Diff children                                        │
│  - Mark effects (Placement, Update, Deletion)           │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  COMMIT PHASE (synchronous, cannot interrupt)           │
│  1. Before mutation (getSnapshotBeforeUpdate)           │
│  2. Mutation (apply DOM changes)                        │
│  3. Layout (useLayoutEffect)                            │
│  → then passive effects (useEffect) run async           │
└─────────────────────────────────────────────────────────┘

Concurrent features enabled by Fiber

  • Time slicing — split work across frames
  • useTransition — mark updates as low priority
  • Suspense — pause rendering while data loads
  • Offscreen / Activity — hide UI without unmounting

Interview answer

Note

Fiber models the UI as a linked list of work units. The render phase is interruptible so React can prioritize user input. The commit phase applies DOM changes synchronously. Double buffering via alternate lets React compare old and new trees efficiently.

Back to index


66. Explain React's rendering lifecycle from state update to DOM update

Full flow

plaintext
1. Event handler calls setState / dispatch
         ↓
2. Update enqueued on Fiber (priority lane assigned)
         ↓
3. RENDER PHASE begins
   - React calls component function
   - Hooks run (useState returns new value)
   - Children reconciled (diff algorithm)
   - Effect flags marked (Placement, Update, Deletion)
         ↓
4. COMMIT PHASE
   a. Before Mutation: getSnapshotBeforeUpdate (class)
   b. Mutation: apply DOM insert/update/delete
   c. Layout: flushSync useLayoutEffect callbacks
         ↓
5. Browser paints screen
         ↓
6. Passive effects: useEffect callbacks run (async, after paint)

Practical Example — Effect order

jsx
function Demo() {
  const [count, setCount] = useState(0)
 
  useLayoutEffect(() => {
    console.log('1: layout effect — runs after DOM update, before paint')
  }, [count])
 
  useEffect(() => {
    console.log('2: passive effect — runs after paint')
  }, [count])
 
  console.log('3: render')
 
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}
 
// Click output:
// 3: render
// 1: layout effect
// (browser paints)
// 2: passive effect

Class component lifecycle mapping

ClassHooks equivalent
renderFunction body
componentDidMountuseEffect(fn, [])
componentDidUpdateuseEffect(fn, [deps])
componentWillUnmountuseEffect cleanup
getSnapshotBeforeUpdateuseLayoutEffect

Back to index


67. Explain Virtual DOM and its comparison mechanism

Theory

The Virtual DOM is a lightweight JavaScript representation of the real DOM tree. React creates a new VDOM tree on each render, compares it with the previous tree (diffing), and computes the minimum set of DOM mutations needed.

Diffing rules:

  1. Different element types → tear down old subtree, build new one
  2. Same type → update props only, recurse into children
  3. Keys identify list items across renders for efficient reordering

Pros & Cons

ProsCons
Declarative UI — you describe state, React patches DOMAbstraction overhead vs direct DOM manipulation
Batched updates — fewer DOM operationsNot always faster than hand-optimized DOM
Cross-browser consistencyDiffing large trees has CPU cost

Real-Life Example

jsx
// Before render: <ul><li key="a">Apple</li><li key="b">Banana</li></ul>
// After render:  <ul><li key="b">Banana</li><li key="a">Apple</li></ul>
 
// React sees same types (ul, li), same keys (a, b)
// → Only reorders DOM nodes, does NOT destroy/recreate <li> elements
// → Component state inside each <li> is preserved
 
// Without keys (or index keys), inserting at top causes:
// → React thinks every item changed → full re-render of all children

Back to index


68. Flexbox vs Grid — key differences

Theory

Both are CSS layout systems, but they solve different problems:

  • Flexbox — one-dimensional layout (row or column). Content-driven sizing.
  • Grid — two-dimensional layout (rows and columns). Layout-driven sizing.

Comparison

FeatureFlexboxGrid
Dimensions1D (row or column)2D (rows + columns)
ControlContent dictates spaceTemplate dictates space
AlignmentExcellent for distributing itemsExcellent for page layouts
Gap supportgapgap
Best forNavbars, card rows, centeringPage layouts, dashboards, galleries
Item stretchingflex: 11fr

Pros & Cons

FlexboxGrid
✅ Simple for component-level layout✅ Powerful for full page structure
✅ Great for unknown item count✅ Precise column/row control
❌ Awkward for complex 2D layouts❌ Overkill for a single row of buttons
❌ Steeper learning curve

Real-Life Example

css
/* Flexbox — navbar */
.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0 1.5rem;
}
 
.nav-links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
}
 
/* Flexbox — center a modal */
.modal-overlay {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}
 
/* Flexbox — card with footer pushed to bottom */
.product-card {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.product-card .footer {
  margin-top: auto; /* pushes footer down */
}
css
/* Grid — dashboard layout */
.dashboard {
  display: grid;
  grid-template-rows: 64px 1fr;
  grid-template-columns: 240px 1fr;
  grid-template-areas:
    'sidebar header'
    'sidebar main';
  min-height: 100vh;
}
 
.sidebar {
  grid-area: sidebar;
}
.header {
  grid-area: header;
}
.main {
  grid-area: main;
}
 
/* Grid — responsive product gallery */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
  padding: 1.5rem;
}
 
/* Grid — form with aligned labels */
.form-row {
  display: grid;
  grid-template-columns: 120px 1fr;
  align-items: center;
  gap: 0.5rem 1rem;
}
tsx
// When to use which — decision guide
// Flexbox: toolbar, button group, horizontal list, vertical card stack
// Grid: page shell, image gallery, data table layout, complex forms
// Both together: Grid for page, Flexbox inside each grid cell

Interview answer (concise)

Note

Flexbox is for one-dimensional layouts — navbars, aligning items in a row or column. Grid is for two-dimensional layouts — page shells, dashboards, responsive galleries. Use Flexbox for components, Grid for page structure. Often combine both.

Back to index


69. How does React reconciliation work?

Theory

Reconciliation is React's process of comparing the new virtual DOM tree with the previous one and determining the minimum set of DOM operations needed to update the UI.

Key concepts

  1. Virtual DOM — Lightweight JS representation of the UI tree.
  2. Diffing algorithm — Heuristic O(n) comparison (not full tree diff).
  3. Fiber architecture — Each unit of work is a Fiber node; enables incremental rendering, pausing, and prioritization.
  4. Render phase vs Commit phase
    • Render: Build/update Fiber tree, mark side effects (can be interrupted).
    • Commit: Apply DOM mutations, run layout effects, then passive effects.

Diffing heuristics

RuleImplication
Different element types → tear down subtree, rebuild<div> → <span> replaces entire subtree
Same type → update props, recurse children<User name="A" /> → <User name="B" />
Keys identify stable identity across rendersEnables efficient list reordering
mermaid
flowchart LR
  A[State/Props Change] --> B[Render Phase]
  B --> C[Build New Fiber Tree]
  C --> D[Diff with Previous Tree]
  D --> E[Mark DOM Mutations]
  E --> F[Commit Phase]
  F --> G[Apply DOM Updates]
  F --> H[Run useLayoutEffect]
  F --> I[Run useEffect]

Practical Example — Why type matters

tsx
// React destroys the entire subtree when element type changes
function App({ showList }) {
  return showList ? (
    <ul>
      <li>A</li>
    </ul>
  ) : (
    <div>No items</div>
  )
  // Switching between <ul> and <div> = full unmount/remount
}

Concurrent features (4+ YOE depth)

  • Time slicing: Break work into chunks so the main thread stays responsive.
  • Priority lanes: Urgent updates (typing) beat non-urgent (data fetch results).
  • useTransition / useDeferredValue: Mark updates as low priority.

Interview Answer

Reconciliation diffs the new element tree against the previous Fiber tree using heuristics — same component type updates in place, different type replaces the subtree, and keys help match list items. The render phase is interruptible; the commit phase applies DOM changes synchronously. This is how React achieves predictable, performant updates at scale.

Back to index


70. Keys in React Lists

Theory

Keys give list items a stable identity across re-renders. React uses keys to match, move, insert, or delete efficiently. Use unique IDs from data — not array index for mutable lists.

Pros & Cons

Stable ID keysIndex keys
✅ Preserves component state❌ State slides on delete/reorder
✅ Efficient DOM moves❌ Unnecessary re-renders

Interview Answer

Keys help React identify list items across renders. I use stable unique IDs from data — never index keys on lists that can change order or delete items.

Real Example

jsx
// ✅ Good
{
  todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)
}
 
// ❌ Bad — index keys on mutable list
{
  todos.map((todo, i) => <TodoItem key={i} todo={todo} />)
}

Back to index


71. Next.js SSR vs CSR vs ISR

Theory — real trade-offs, not definitions

CSRSSRISR
First byteFast (empty shell)Slower (server work)Fast (CDN cached)
SEOPoor without extra workExcellentExcellent
Data freshnessClient fetchAlways currentTTL-based stale
Server costLowHigh at scaleLow
TTFBLowHigherLow (edge)
PersonalizationEasy (client)Per-requestHard (cached)
a fintech app use caseDashboard post-loginAccount settingsCredit score landing

Pros & Cons — when a fintech app would choose each

CSRSSRISR
✅ Post-auth app screens✅ User-specific bill pay page✅ Marketing / SEO pages
✅ No server render cost✅ Always fresh balance✅ 1M users, same HTML
❌ Blank until JS loads❌ Server spike at peak❌ Stale for TTL window
❌ Bad for crawlers❌ TTFB dependency❌ Invalidation complexity

Real-Life Example

tsx
// CSR — a fintech app app dashboard (authenticated, no SEO need)
'use client'
export default function Dashboard() {
  const { data } = useQuery({ queryKey: ['rewards'], queryFn: fetchRewards })
  return <RewardsCard data={data} />
}
 
// SSR — personalized credit card bill (must be fresh, user-specific)
export async function getServerSideProps({ req }) {
  const bill = await fetchBill(req.cookies.session)
  return { props: { bill } }
}
 
// ISR — credit score explainer page (SEO, same for all, refresh hourly)
export async function getStaticProps() {
  const content = await fetchCMSContent('credit-score-guide')
  return { props: { content }, revalidate: 3600 }
}

Interview answer (concise)

Performance

For a fintech app: ISR for public SEO pages (score guides, landing) — fast CDN, cheap. SSR for personalized financial data that must be fresh on first paint. CSR for authenticated app experiences where SEO doesn't matter and we want rich interactivity. The trade-off is always freshness vs speed vs server cost.

Back to index


72. Optimize 10+ fields — re-renders

Theory

With one state object, every keystroke in any field re-renders the entire form and all 10+ field components.

Fields that cause unnecessary re-renders:

  • All fields when using single useState({ ...10 fields })
  • Parent re-render → all unmemoized children re-render
  • Context provider wrapping form with changing value
  • Inline object/function props to child fields

Pros & Cons

React Hook Form (uncontrolled)Single useState object
✅ Field change doesn't re-render form❌ Every keystroke re-renders all fields
✅ Scales to 50+ fields✅ Simple mental model
Industry standard for large formsOK for <5 fields

Real-Life Example

tsx
// ❌ BAD — typing in field 1 re-renders fields 1-10
function BigForm() {
  const [form, setForm] = useState({ f1: "", f2: "", ... f10: "" });
  return (
    <>
      {FIELDS.map((f) => (
        <input
          key={f}
          value={form[f]}
          onChange={(e) => setForm({ ...form, [f]: e.target.value })}
        />
      ))}
    </>
  );
}
 
// ✅ GOOD — memoized field components
const FormField = React.memo(function FormField({
  label, value, error, onChange, onBlur,
}: FieldProps) {
  return (
    <div>
      <label>{label}</label>
      <input value={value} onChange={(e) => onChange(e.target.value)} onBlur={onBlur} />
      {error && <span role="alert">{error}</span>}
    </div>
  );
});
 
// ✅ BETTER — React Hook Form (a fintech app production pattern)
import { useForm } from "react-hook-form";
 
function OptimizedKYCForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("pan", { validate: validatePAN })} />
      {errors.pan && <span>{errors.pan.message}</span>}
      {/* Only error display re-renders — not entire form */}
    </form>
  );
}

What to say in interview: "I'd start with isolated field components + memo. For 10+ fields I'd switch to React Hook Form because it uses refs — field keystrokes don't re-render siblings."

Back to index


73. React Fiber

Theory

Fiber is React's reconciliation engine (React 16+). It enables:

  • Incremental rendering — split work into units, pause/resume
  • Priority scheduling — user input before background work
  • Concurrent features — Suspense, transitions, useDeferredValue

Each fiber node represents a component instance with work-in-progress state.

Interview Answer

Fiber is React's internal architecture that enables incremental rendering and concurrent features — it can pause, prioritize, and resume work instead of blocking the main thread.

Real Example

jsx
// Concurrent features built on Fiber
const [isPending, startTransition] = useTransition()
 
function handleSearch(query) {
  setInput(query) // urgent — input stays responsive
  startTransition(() => setResults(search(query))) // low priority — can be interrupted
}

Back to index


74. React Internals & Rendering Lifecycle

Theory

When state changes, React runs a render pass (pure, can be interrupted) and a commit pass (DOM updates, synchronous).

plaintext
Trigger (setState, dispatch, parent re-render)
    ↓
Render phase (Fiber work loop — interruptible)
  → Call component functions / hooks
  → Build new element tree
  → Diff with previous tree (reconciliation)
    ↓
Commit phase (synchronous)
  → Apply DOM mutations
  → useLayoutEffect
    ↓
Browser paint
    ↓
useEffect (passive, after paint)
Class component lifecycle (still asked)
PhaseMethods
Mountconstructor → render → componentDidMount
Updaterender → componentDidUpdate
UnmountcomponentWillUnmount
Hooks equivalent
ClassHooks
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [deps])
componentWillUnmountuseEffect(() => () => cleanup, [])
shouldComponentUpdateReact.memo, useMemo

Real Example — Lifecycle in hooks

tsx
function LiveOrderMap({ orderId }: { orderId: string }) {
  const [location, setLocation] = useState<Coords | null>(null)
 
  // Mount + update when orderId changes
  useEffect(() => {
    const ws = new WebSocket(`${WS_URL}/orders/${orderId}/location`)
 
    ws.onmessage = (e) => setLocation(JSON.parse(e.data))
 
    // Unmount cleanup
    return () => ws.close()
  }, [orderId])
 
  // Runs after DOM update, before paint — measure DOM
  useLayoutEffect(() => {
    if (!location) return
    centerMapOn(location)
  }, [location])
 
  return <MapMarker coords={location} />
}

Interview Answer

React separates render (compute next UI, diff) from commit (apply DOM). Hooks map to lifecycle: mount/update/unmount via useEffect, layout reads via useLayoutEffect. Fiber makes rendering interruptible so urgent updates aren't blocked.

Back to index


75. React reconciliation — what to update?

Theory

React reconciliation compares the new element tree with the previous Fiber tree and decides the minimum DOM operations.

Update rules:

  1. Different type (div → span) → destroy entire subtree, rebuild
  2. Same type → update props on DOM node, recurse children
  3. Same type + same key → update in place, preserve state
  4. Keys differ in lists → unmount old, mount new (or move if key matches)

React bails out if Object.is(oldState, newState) for state, or if React.memo shallow-compares equal props.

Pros & Cons

Understanding reconciliationIgnorance
✅ Know why keys matter❌ Index keys cause state bugs
✅ Targeted memoization❌ Random React.memo everywhere
✅ Predict render behavior❌ Unnecessary DOM work

Real-Life Example

jsx
// Case 1: Same type — UPDATE props only
// Before: <div className="card"><h2>₹500</h2></div>
// After:  <div className="card active"><h2>₹500</h2></div>
// → Only className updated on div
 
// Case 2: Different type — REPLACE subtree
// Before: <div><TransactionList /></div>
// After:  <section><TransactionList /></section>
// → TransactionList unmounts and remounts — state lost
 
// Case 3: Keys in list
// Before: [<Row key="a" />, <Row key="b" />]
// After:  [<Row key="b" />, <Row key="a" />]
// → React MOVES nodes — state preserved
 
// Case 4: Bailout
function Counter() {
  const [n, setN] = useState(0)
  return <button onClick={() => setN(0)}>{n}</button> // click at 0 → no re-render
}

Interview answer (concise)

Performance

Reconciliation diffs old and new trees. Same element type updates props in place. Different type replaces the subtree. Keys identify list items for efficient moves. React skips re-renders when state/props are unchanged (Object.is). Commit phase applies only the DOM mutations the diff marks.

Back to index


76. SSR vs CSR with examples and use-cases

Theory

  • CSR (Client-Side Rendering): Server sends empty HTML + JS bundle. Browser downloads JS, executes it, fetches data, renders UI.
  • SSR (Server-Side Rendering): Server runs React, generates full HTML with data, sends to browser. Browser hydrates interactivity on top.

Pros & Cons

CSRSSR
ProsRich interactivity, simpler hosting, SPA navigationFast FCP, SEO-friendly, works without JS
ConsSlow initial load, poor SEO without extra workServer cost, hydration complexity, TTFB dependency
Best forDashboards, admin panels, authenticated appsMarketing pages, e-commerce listings, blogs

Real-Life Example

plaintext
CSR — a food-delivery platform order tracking (authenticated, post-login):
  Browser → GET /app → empty <div id="root"> → download 500KB JS
  → fetch /api/orders → render tracking UI
  → User sees spinner for 2-3 seconds
 
SSR — a food-delivery platform restaurant listing (SEO-critical):
  Browser → GET /restaurants/delhi
  → Server runs React → fetches restaurant data
  → Returns full HTML with restaurant cards
  → User sees content in <1s → JS hydrates for filters/sort
jsx
// Next.js SSR example
export async function getServerSideProps() {
  const restaurants = await fetchRestaurants('delhi')
  return { props: { restaurants } }
}
 
export default function RestaurantPage({ restaurants }) {
  return <RestaurantList data={restaurants} />
}

Back to index


77. Tell me about your key skills

Theory

This is an opening behavioral question. For a Senior React role at Engineo, structure your answer around technical depth, leadership, and business impact — not a laundry list of buzzwords.

Use the T-shaped model: deep in React/JS frontend, broad in related areas (APIs, testing, CI/CD).

What to include

CategoryExamples to mention
CoreReact, TypeScript, JavaScript (ES6+)
StateRedux Toolkit, TanStack Query, Context
StylingCSS Modules, Tailwind, responsive design
TestingJest, React Testing Library, Playwright
ToolsGit, Webpack/Vite, CI/CD
SoftCode review, mentoring, agile, cross-team collaboration

Interview Answer

I'm a senior frontend developer specializing in React and TypeScript, with strong JavaScript fundamentals, experience in state management, authentication, performance optimization, and leading features end-to-end from design to production.

Real Example — Sample answer (60 seconds)

Note

My core stack is React, TypeScript, and JavaScript. On the state side, I use Redux Toolkit for complex client state and TanStack Query for server data. I've built authentication flows with JWT and OAuth, optimized apps with code splitting and memoization, and write tests with Jest and RTL.

Note

In my last role, I led the checkout module for 50K daily users, mentored two junior developers, and established our component library. I'm also comfortable with REST APIs, Git workflows, and CI/CD pipelines.

Tip: Tailor to the job description. Mention skills Engineo likely needs (enterprise apps, Redux, auth).

Back to index


78. this Keyword

Theory

this refers to the execution context — who called the function. Its value depends on how the function is invoked, not where it's defined.

Invocationthis value
Global / standaloneundefined (strict) or window
Object methodThe object
call / apply / bindExplicitly set
new keywordNewly created instance
Arrow functionInherited from enclosing lexical scope
Event handlerThe element (usually)

Pros & Cons

Explicit binding (bind/call)Arrow functions (lexical this)
✅ Full control over this✅ No this confusion in callbacks
❌ Verbose, easy to forget❌ Can't use as constructors
❌ Wrong this if you need dynamic binding

Real-Life Example

javascript
const checkout = {
  items: ['Biryani', 'Naan'],
  total: 348,
 
  printReceipt() {
    console.log(this.total) // 348 — method call
  },
 
  processItems: function () {
    this.items.forEach(function (item) {
      // console.log(this.total); // undefined — `this` is lost in callback
    })
 
    this.items.forEach((item) => {
      console.log(this.total) // 348 — arrow inherits `this` from processItems
    })
  },
}
 
checkout.printReceipt()
 
// Explicit binding
function formatPrice(amount) {
  return `${this.currency}${amount}`
}
formatPrice.call({ currency: '₹' }, 299) // "₹299"
formatPrice.apply({ currency: '$' }, [49]) // "$49"
 
const formatINR = formatPrice.bind({ currency: '₹' })
formatINR(199) // "₹199"
 
// React class component (legacy)
class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState({ count: this.state.count + 1 })
  }
}

Back to index


79. To-Do List (Vanilla JS / React) — Optimize Re-renders

Theory

Todo list tests: CRUD operations, state management, and re-render optimization in React. Avoid re-rendering entire list when one item changes — use stable keys, memoized item components, and immutable updates.

Interview Answer

I use immutable state updates, stable ID keys, React.memo on TodoItem, and useCallback for handlers so only the changed item re-renders.

Vanilla JS Implementation

html
<div id="app">
  <input id="input" placeholder="Add todo..." />
  <button id="add">Add</button>
  <ul id="list"></ul>
</div>
javascript
class TodoApp {
  constructor() {
    this.todos = []
    this.listEl = document.getElementById('list')
    this.inputEl = document.getElementById('input')
 
    document.getElementById('add').onclick = () => this.add()
    this.inputEl.onkeydown = (e) => e.key === 'Enter' && this.add()
  }
 
  add() {
    const text = this.inputEl.value.trim()
    if (!text) return
    this.todos.push({ id: crypto.randomUUID(), text, done: false })
    this.inputEl.value = ''
    this.render()
  }
 
  toggle(id) {
    this.todos = this.todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
    this.render()
  }
 
  remove(id) {
    this.todos = this.todos.filter((t) => t.id !== id)
    this.render()
  }
 
  render() {
    this.listEl.innerHTML = this.todos
      .map(
        (t) => `
      <li data-id="${t.id}">
        <input type="checkbox" ${t.done ? 'checked' : ''} />
        <span style="${t.done ? 'text-decoration:line-through' : ''}">${t.text}</span>
        <button class="delete">×</button>
      </li>`,
      )
      .join('')
 
    this.listEl.querySelectorAll('li').forEach((li) => {
      const id = li.dataset.id
      li.querySelector('input').onchange = () => this.toggle(id)
      li.querySelector('.delete').onclick = () => this.remove(id)
    })
  }
}
 
new TodoApp()

React — Optimized Re-renders

tsx
type Todo = { id: string; text: string; done: boolean }
 
// Memoized item — only re-renders when its props change
const TodoItem = React.memo(function TodoItem({
  todo,
  onToggle,
  onRemove,
}: {
  todo: Todo
  onToggle: (id: string) => void
  onRemove: (id: string) => void
}) {
  console.log('render', todo.id) // verify only changed item renders
  return (
    <li>
      <input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />
      <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>{todo.text}</span>
      <button onClick={() => onRemove(todo.id)}>×</button>
    </li>
  )
})
 
function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([])
  const [input, setInput] = useState('')
 
  const addTodo = useCallback(() => {
    const text = input.trim()
    if (!text) return
    setTodos((prev) => [...prev, { id: crypto.randomUUID(), text, done: false }])
    setInput('')
  }, [input])
 
  const toggleTodo = useCallback((id: string) => {
    setTodos((prev) => prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)))
  }, [])
 
  const removeTodo = useCallback((id: string) => {
    setTodos((prev) => prev.filter((t) => t.id !== id))
  }, [])
 
  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map((todo) => (
          <TodoItem key={todo.id} todo={todo} onToggle={toggleTodo} onRemove={removeTodo} />
        ))}
      </ul>
    </div>
  )
}

What to say: "React.memo on TodoItem + useCallback on handlers + stable UUID keys = only the toggled item re-renders."

Back to index


80. Use unique keys while rendering lists

Theory

Keys give React a stable identity for list items across re-renders. They help React match, move, insert, or delete elements efficiently without destroying component state.

Use stable, unique IDs from your data (database ID, UUID). Avoid array index as key when the list can change order, insert, or delete items.

Pros & Cons

Stable unique keysIndex keys
✅ Preserves component state on reorder❌ State "slides" to wrong item on delete
✅ Efficient DOM moves❌ Unnecessary re-renders
✅ Correct animations❌ Broken enter/exit transitions

Real-Life Example

tsx
// ❌ Index keys on mutable list
function TodoList({ todos, onDelete }) {
  return todos.map((todo, index) => <TodoItem key={index} todo={todo} onDelete={() => onDelete(index)} />)
  // Delete first item → all keys shift → every item re-mounts with wrong state
}
 
// ✅ Stable ID keys
function TodoList({ todos, onDelete }) {
  return todos.map((todo) => <TodoItem key={todo.id} todo={todo} onDelete={() => onDelete(todo.id)} />)
}
 
// When index keys ARE acceptable:
// - Static list that never reorders
// - No local state inside list items
// - No animations

Back to index


81. Virtual DOM

Theory

The Virtual DOM is a lightweight JavaScript copy of the real DOM. On each state change, React builds a new VDOM tree, compares it with the previous one (diffing), and updates only the changed parts of the real DOM.

This is faster than rewriting the entire page.

Pros & Cons

ProsCons
Efficient batched updatesCPU cost for diffing
Declarative — you describe state, not DOM opsNot always faster than hand-tuned DOM

Interview Answer

The Virtual DOM is a JS representation of the UI. React diffs the new tree against the old one and applies only the minimum DOM changes needed.

Real Example

jsx
// You write:
<p className={isActive ? 'active' : ''}>{count}</p>
 
// React diffs: only className and text node update — rest of page untouched

Back to index


82. Virtual DOM, Hydration, Reconciliation & Scheduling

Theory

ConceptWhat it is
Virtual DOMLightweight JS representation of UI (React elements / Fiber nodes)
ReconciliationDiffing old vs new tree; keys identify stable identity in lists
HydrationAttaching event listeners to server-rendered HTML (SSR)
SchedulingPriority lanes — user input > transitions > idle work
Reconciliation rules (simplified)
  • Same component type → update props, recurse children
  • Different type → unmount old, mount new
  • Lists → use stable key (not index if list reorders)

Hydration mismatch — server HTML ≠ client first render → React warns and may re-render client-side.

Real Example — Keys and reconciliation

tsx
// ❌ Index keys — reordering breaks state
{
  items.map((item, i) => <Row key={i} item={item} />)
}
 
// ✅ Stable id keys
{
  items.map((item) => <Row key={item.id} item={item} />)
}
React 18 scheduling — startTransition
tsx
import { useState, useTransition } from 'react'
 
function SearchPage() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<Item[]>([])
  const [isPending, startTransition] = useTransition()
 
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const value = e.target.value
    setQuery(value) // urgent — input stays responsive
 
    startTransition(() => {
      setResults(filterHeavyList(value)) // low priority
    })
  }
 
  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <span>Updating…</span>}
      <ResultList items={results} />
    </>
  )
}
Hydration (Next.js / SSR)
tsx
// ❌ Mismatch — Date.now() differs server vs client
function Bad() {
  return <p>{Date.now()}</p>
}
 
// ✅ Client-only value after mount
function Good() {
  const [now, setNow] = useState<number | null>(null)
  useEffect(() => setNow(Date.now()), [])
  return <p>{now ?? '—'}</p>
}

Interview Answer

Virtual DOM lets React diff and apply minimal DOM updates. Reconciliation uses element type and keys; hydration wires SSR HTML to client handlers and must match server output. React 18 schedules urgent vs transition updates separately.

Back to index


83. What is the Virtual DOM?

Theory

The Virtual DOM (VDOM) is a lightweight JavaScript object tree that mirrors the real DOM structure. React keeps a VDOM representation in memory and, on each state change:

  1. Creates a new VDOM tree
  2. Diffs it against the previous tree (reconciliation)
  3. Applies only the minimum DOM mutations needed

This is faster than rewriting the entire DOM because DOM operations are expensive; JS object comparison is cheap.

Pros & Cons

ProsCons
Declarative UI — describe state, React patches DOMCPU overhead for diffing large trees
Batched updates — fewer DOM operationsNot always faster than hand-optimized DOM
Cross-browser consistencyAbstraction can hide performance issues
Enables React Native (same model, native renderer)Learning curve for reconciliation rules

Real-Life Example

jsx
// You write declarative UI
function OrderStatus({ order }) {
  return (
    <div className="status">
      <h2>Order #{order.id}</h2>
      <p className={order.status}>{order.statusLabel}</p>
    </div>
  )
}
 
// When order.status changes "pending" → "delivered":
// VDOM diff sees: same <div>, same <h2>, <p> className changed
// Real DOM: only updates className on <p> — nothing else touched

Diffing rules (mention in interview):

  • Different element type → destroy old subtree, build new
  • Same type → update props, recurse children
  • Keys help match list items efficiently across re-renders

Back to index


84. Why are keys important in React lists?

Theory

Keys give React a stable identity for elements in a list across re-renders. Without keys (or with bad keys like array index), React may:

  • Reuse wrong component instances
  • Lose internal state
  • Trigger unnecessary re-renders
  • Cause subtle UI bugs

Bad vs Good keys

tsx
// ❌ Index as key — breaks on insert/delete/reorder
{
  items.map((item, index) => <TodoItem key={index} item={item} />)
}
 
// ✅ Stable unique ID from data
{
  items.map((item) => <TodoItem key={item.id} item={item} />)
}

Practical Example — State loss with index keys

tsx
function TodoItem({ item }) {
  const [editing, setEditing] = useState(false)
  return (
    <div>
      {editing ? <input defaultValue={item.text} /> : <span>{item.text}</span>}
      <button onClick={() => setEditing(!editing)}>Edit</button>
    </div>
  )
}
 
// If you delete the first item and keys are index-based,
// the second item "inherits" the first item's editing state.

When index keys are acceptable

  • Static lists that never reorder
  • No component state inside list items
  • No animations depending on identity

Interview Answer

Keys help React match list items across renders. Stable, unique keys preserve component state and enable efficient moves. Index keys cause bugs when the list mutates — especially with local state, focus, or animations.

Back to index


85. Why do keys matter in React and how do they improve performance?

Theory

Keys are stable identifiers that tell React which items in a list correspond between renders. They enable React to match, move, insert, or delete elements efficiently instead of re-creating the entire list.

Without stable keys, React falls back to index-based matching, which breaks when items are inserted, deleted, or reordered.

Pros & Cons

Good keys (unique IDs)Bad keys (array index)
Preserve component state on reorderState "slides" to wrong item on delete
Minimal DOM operationsUnnecessary unmount/remount
Correct animationsBroken enter/exit transitions

Real-Life Example

jsx
// ❌ Bad — index keys on a mutable todo list
{
  todos.map((todo, index) => <TodoItem key={index} todo={todo} />)
}
// Delete first todo → every item below gets new key → all re-render, state bleeds
 
// ✅ Good — stable ID from database
{
  todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)
}
// Delete first todo → only that item unmounts, rest stay intact

Back to index


86. You need to display frequently changing stock market data on a page. Would you use SSR, SSG, or CSR? Why?

Description

Stock prices change every second (or faster). Pre-rendered HTML from build time (SSG) is stale immediately. The choice depends on freshness vs SEO vs interactivity.

Theory

StrategyFreshnessBest for stock data?
SSGStale until rebuild❌ Wrong for live prices
SSRFresh per request⚠️ OK for initial snapshot, expensive at scale
CSRFresh via client fetch/WebSocket✅ Best for live ticks
HybridSSR shell + CSR/WebSocket updates✅ Recommended

Recommended pattern: SSR (or static shell) for layout + SEO metadata, then client-side polling or WebSocket for live quotes.

Pros & Cons

Hybrid (SSR shell + client stream)Pure SSR every tick
✅ Fast first paint, live updates❌ Server hammered on every price change
✅ Scales via CDN for static parts❌ High TTFB under load
❌ Client JS required for numbers❌ No benefit over WebSocket

Real Example — NSE / trading dashboard

tsx
// app/markets/page.tsx — Server Component: static shell + initial snapshot
export const dynamic = 'force-dynamic' // or short revalidate
 
async function getMarketSnapshot() {
  const res = await fetch(`${process.env.API_URL}/quotes/snapshot`, {
    cache: 'no-store', // always fresh on each page load
  })
  return res.json()
}
 
export default async function MarketsPage() {
  const snapshot = await getMarketSnapshot()
 
  return (
    <main>
      <h1>Live Markets</h1>
      {/* Client island — WebSocket updates after hydration */}
      <LiveTicker initialQuotes={snapshot.quotes} />
    </main>
  )
}
tsx
// components/live-ticker.tsx — Client Component
'use client'
 
import { useEffect, useState } from 'react'
 
export function LiveTicker({ initialQuotes }) {
  const [quotes, setQuotes] = useState(initialQuotes)
 
  useEffect(() => {
    const ws = new WebSocket(process.env.NEXT_PUBLIC_WS_URL!)
 
    ws.onmessage = (event) => {
      const update = JSON.parse(event.data)
      setQuotes((prev) => mergeQuote(prev, update))
    }
 
    return () => ws.close()
  }, [])
 
  return (
    <table>
      <tbody>
        {quotes.map((q) => (
          <tr key={q.symbol}>
            <td>{q.symbol}</td>
            <td>{q.price}</td>
            <td className={q.change >= 0 ? 'text-green' : 'text-red'}>{q.changePercent}%</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

Why not SSG? Build-time prices are wrong the moment the user opens the page.

Why not pure CSR? You lose fast meaningful HTML and SEO for /markets landing.

Interview Answer

I'd use a hybrid: SSR or dynamic rendering for the initial snapshot and page shell, then WebSocket or short-interval client polling for live ticks — not SSG, and not SSR on every price change.

Back to index


State Management

87. Avoid unnecessary prop drilling

Theory

Prop drilling is passing data through intermediate components that don't need it. One-to-two levels is fine and explicit. Beyond that, use Context API, component composition, or a state library (Zustand, Redux).

Choose the simplest solution that fits:

  • Theme/locale → Context
  • Auth session → Context or Zustand
  • Complex global state → Redux Toolkit
  • Layout slots → Component composition

Pros & Cons

Context / ZustandDeep prop drilling
✅ Any depth access❌ Fragile — rename breaks chain
✅ Cleaner intermediate components❌ Hard to trace data flow
❌ All consumers re-render on change*✅ Explicit and easy to follow (shallow)

*Mitigate with split contexts or Zustand selectors.

Real-Life Example

tsx
// ✅ Composition — pass JSX directly, skip intermediates
function App() {
  const [user, setUser] = useState(null)
  return <Layout header={<Header user={user} />} sidebar={<Sidebar />} content={<Dashboard user={user} />} />
}
 
// ✅ Context — theme used deep in tree
const ThemeContext = createContext<'light' | 'dark'>('light')
 
function App() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  return (
    <ThemeContext.Provider value={theme}>
      <Layout />
    </ThemeContext.Provider>
  )
}
 
function DeepNestedButton() {
  const theme = useContext(ThemeContext)
  return <button className={theme === 'dark' ? 'btn-dark' : 'btn-light'}>Click</button>
}

Back to index


88. Can you use RTK Query without Redux Toolkit?

Answer

No, RTK Query is tightly coupled with Redux Toolkit and requires it to work properly.

Back to index


89. Context API

Theory

Context API shares data across the component tree without passing props at every level. Three steps:

  1. createContext(defaultValue)
  2. <Provider value={...}> wraps subtree
  3. useContext(MyContext) consumes value

Best for low-frequency global data: theme, locale, authenticated user, feature flags.

Pros & Cons

ProsCons
Eliminates prop drillingAll consumers re-render on value change
Built into ReactEasy to overuse as global store
Simple for theme/authNo DevTools like Redux

Interview Answer

Context shares data across the tree without prop drilling. I use it for theme, auth, and locale — not for frequently changing state like form inputs.

Real Example

jsx
import { createContext, useContext, useState, useMemo } from 'react'
 
const AuthContext = createContext(null)
 
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
 
  const value = useMemo(
    () => ({
      user,
      login: (userData) => setUser(userData),
      logout: () => setUser(null),
      isAuthenticated: !!user,
    }),
    [user],
  )
 
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
 
export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be used within AuthProvider')
  return ctx
}
 
// App setup
function App() {
  return (
    <AuthProvider>
      <Navbar />
      <Routes />
    </AuthProvider>
  )
}
 
// Any deep child — no prop drilling
function ProfileMenu() {
  const { user, logout } = useAuth()
  return (
    <div>
      <span>{user?.name}</span>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

Performance Tip

Split contexts by update frequency — ThemeContext separate from UserContext so theme toggle doesn't re-render auth consumers.

Back to index


90. Explain the Redux Workflow

Theory

Redux is a predictable state container with a strict unidirectional data flow:

plaintext
UI dispatches Action → Reducer processes → Store updates → UI re-renders

Core pieces:

  • Store — single source of truth holding application state
  • Action — plain object { type: "ADD_ITEM", payload: {...} } describing what happened
  • Reducer — pure function (state, action) => newState
  • Dispatch — sends action to store
  • Selector — reads specific slice of state

With Redux Toolkit (RTK), slices, Immer, and createAsyncThunk simplify the boilerplate.

Pros & Cons

Redux workflowAd-hoc useState everywhere
✅ Predictable, traceable updates❌ State scattered across components
✅ Time-travel debugging❌ Hard to sync sibling components
✅ Middleware for async/logging❌ Prop drilling for shared state
❌ Boilerplate without RTK

Real-Life Example

tsx
// 1. Slice — state + reducers
// store/employeeSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
 
export const fetchEmployees = createAsyncThunk('employees/fetchAll', async (deptId: string) => {
  const res = await fetch(`/api/departments/${deptId}/employees`)
  return res.json()
})
 
const employeeSlice = createSlice({
  name: 'employees',
  initialState: { list: [], loading: false, error: null },
  reducers: {
    clearEmployees: (state) => {
      state.list = []
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchEmployees.pending, (state) => {
        state.loading = true
      })
      .addCase(fetchEmployees.fulfilled, (state, action) => {
        state.loading = false
        state.list = action.payload
      })
      .addCase(fetchEmployees.rejected, (state, action) => {
        state.loading = false
        state.error = action.error.message
      })
  },
})
 
// 2. Store
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
  reducer: { employees: employeeSlice.reducer },
})
 
// 3. Provider
;<Provider store={store}>
  <App />
</Provider>
 
// 4. Component — dispatch + select
function EmployeeList() {
  const dispatch = useAppDispatch()
  const { list, loading, error } = useAppSelector((s) => s.employees)
 
  useEffect(() => {
    dispatch(fetchEmployees('D101'))
  }, [dispatch])
 
  if (loading) return <Spinner />
  if (error) return <ErrorBanner message={error} />
  return list.map((e) => <EmployeeCard key={e.id} employee={e} />)
}

Workflow diagram

plaintext
User clicks "Load Employees"
        ↓
dispatch(fetchEmployees("D101"))
        ↓
createAsyncThunk → API call
        ↓
pending → loading: true
        ↓
fulfilled → state.list = data, loading: false
        ↓
useSelector reads new state → component re-renders

Back to index


91. Have you used Redux Toolkit (RTK) or TanStack Query?

Theory

These solve different problems:

Redux Toolkit (RTK)TanStack Query
PurposeClient-side global stateServer state (API data)
ManagesUI state, auth, cart, preferencesFetched/cached remote data
CachingManualAutomatic (stale-while-revalidate)
AsynccreateAsyncThunk, RTK QueryBuilt-in useQuery / useMutation
DevToolsRedux DevToolsReact Query DevTools

They are complementary, not competitors. Use RTK for client state, TanStack Query for server state.

Pros & Cons

RTKTanStack Query
✅ Great for complex client workflows✅ Eliminates boilerplate for API calls
✅ Predictable global state✅ Auto refetch, cache invalidation, dedup
❌ Overkill for API-only state❌ Not for UI-only state (sidebar open)
❌ Boilerplate even with RTK❌ Learning curve for cache concepts

Real-Life Example — Using both together

tsx
// TanStack Query — server state (restaurant data from API)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
 
function RestaurantList({ city }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['restaurants', city],
    queryFn: () => fetch(`/api/restaurants?city=${city}`).then((r) => r.json()),
    staleTime: 5 * 60 * 1000, // fresh for 5 minutes
  })
 
  if (isLoading) return <Spinner />
  if (error) return <ErrorBanner />
  return data.map((r) => <RestaurantCard key={r.id} restaurant={r} />)
}
 
// RTK — client state (shopping cart, not from API cache)
function CartDrawer() {
  const items = useAppSelector((state) => state.cart.items)
  const dispatch = useAppDispatch()
 
  return (
    <div>
      {items.map((item) => (
        <CartItem key={item.id} item={item} onRemove={() => dispatch(removeItem(item.id))} />
      ))}
    </div>
  )
}
 
// TanStack Query mutation — place order, then invalidate cache
function PlaceOrderButton({ cartItems }) {
  const queryClient = useQueryClient()
 
  const mutation = useMutation({
    mutationFn: (order) => fetch('/api/orders', { method: 'POST', body: JSON.stringify(order) }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['orders'] })
      dispatch(clearCart()) // RTK action
    },
  })
 
  return <button onClick={() => mutation.mutate(cartItems)}>Place Order</button>
}

Interview answer (concise)

Note

Yes, I've used both. RTK for client-side state like cart, auth, and UI preferences. TanStack Query for all API data — it handles caching, background refetch, and loading/error states better than putting fetch logic in Redux. In my last project, we used both: Query for server state, RTK for client state.

Back to index


92. How do you handle loading and error states in RTK Query?

Answer

All query and mutation hooks return isLoading, isError, data, and error states, which you can use to show loaders or error messages.

js
const { data, isLoading, isError } = useGetUsersQuery()

Back to index


93. How do you use Context API?

Theory

Context API shares data across the component tree without prop drilling. Pattern: createContext → Provider wraps tree → consumers use useContext.

Best for: theme, locale, auth session — data that many components need but changes infrequently.

Avoid for: fast-changing or fine-grained state (causes mass re-renders).

Pros & Cons

ProsCons
No prop drillingAll consumers re-render on value change
Built into ReactProvider hell if overused
Simple for theme/authNot a full state manager

Interview Answer

I create a context with createContext, wrap relevant parts of the tree in a Provider with a memoized value, and consume with useContext. I split contexts by concern — auth, theme — to limit re-renders.

Real Example

tsx
// Split contexts — Engineo senior pattern
const ThemeContext = createContext<'light' | 'dark'>('light')
const AuthContext = createContext<AuthState | null>(null)
 
function AppProviders({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  const auth = useAuthState() // custom hook with login/logout
 
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme])
 
  return (
    <AuthContext.Provider value={auth}>
      <ThemeContext.Provider value={themeValue}>{children}</ThemeContext.Provider>
    </AuthContext.Provider>
  )
}
 
// Consumer — only re-renders when its context changes
function Header() {
  const { theme } = useContext(ThemeContext)
  const { user } = useContext(AuthContext)
  return <header className={`header-${theme}`}>Hello, {user?.name}</header>
}
tsx
// When NOT to use Context — use TanStack Query instead
// ❌ Context for API data
const DataContext = createContext(fetchedUsers)
 
// ✅ TanStack Query
const { data: users } = useQuery({ queryKey: ['users'], queryFn: fetchUsers })

Back to index


94. How does caching work in RTK Query?

Answer

RTK Query automatically caches fetched data and reuses it on subsequent requests, reducing network calls. You can customize when it should refetch.

Back to index


95. How does Redux work, from installation to usage in a project?

Theory

Redux is a predictable state container. It holds application state in a single store, updated only through actions dispatched to a reducer (pure function). The flow is unidirectional:

plaintext
UI → dispatch(action) → reducer(state, action) → new state → UI re-renders

Pros & Cons

ProsCons
Predictable state updatesBoilerplate without RTK
Time-travel debugging (Redux DevTools)Overkill for simple apps
Centralized state — easy to traceLearning curve for new developers
Middleware for async (thunks, sagas)Can cause unnecessary re-renders without selectors

Real-Life Example — Full flow

Step 1: Installation

bash
npm install @reduxjs/toolkit react-redux

Step 2: Create a slice (RTK — modern approach)

typescript
// store/slices/cartSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 
interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}
 
interface CartState {
  items: CartItem[]
  total: number
}
 
const initialState: CartState = { items: [], total: 0 }
 
const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addItem: (state, action: PayloadAction<CartItem>) => {
      const existing = state.items.find((i) => i.id === action.payload.id)
      if (existing) {
        existing.quantity += 1
      } else {
        state.items.push({ ...action.payload, quantity: 1 })
      }
      state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
    },
    removeItem: (state, action: PayloadAction<string>) => {
      state.items = state.items.filter((i) => i.id !== action.payload)
      state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
    },
    clearCart: () => initialState,
  },
})
 
export const { addItem, removeItem, clearCart } = cartSlice.actions
export default cartSlice.reducer

Step 3: Configure store

typescript
// store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import cartReducer from './slices/cartSlice'
import authReducer from './slices/authSlice'
 
export const store = configureStore({
  reducer: {
    cart: cartReducer,
    auth: authReducer,
  },
})
 
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Step 4: Provide store to React

tsx
// main.tsx
import { Provider } from 'react-redux'
import { store } from './store'
 
ReactDOM.createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <App />
  </Provider>,
)

Step 5: Use in components

tsx
// hooks.ts — typed hooks
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './store'
 
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
 
// CartButton.tsx
import { useAppSelector, useAppDispatch } from '../hooks'
import { addItem } from '../store/slices/cartSlice'
 
function AddToCartButton({ product }) {
  const dispatch = useAppDispatch()
  const cartCount = useAppSelector((state) => state.cart.items.length)
 
  return <button onClick={() => dispatch(addItem(product))}>Add to Cart ({cartCount})</button>
}

Step 6: Async with createAsyncThunk

typescript
// store/slices/ordersSlice.ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
 
export const fetchOrders = createAsyncThunk('orders/fetchAll', async (userId: string) => {
  const res = await fetch(`/api/orders?userId=${userId}`)
  return res.json()
})
 
const ordersSlice = createSlice({
  name: 'orders',
  initialState: { list: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchOrders.pending, (state) => {
        state.loading = true
      })
      .addCase(fetchOrders.fulfilled, (state, action) => {
        state.loading = false
        state.list = action.payload
      })
      .addCase(fetchOrders.rejected, (state, action) => {
        state.loading = false
        state.error = action.error.message
      })
  },
})

Classic Redux flow (without RTK — know for interviews)

javascript
// Action
const ADD_ITEM = 'cart/ADD_ITEM'
const addItem = (item) => ({ type: ADD_ITEM, payload: item })
 
// Reducer (pure function)
function cartReducer(state = [], action) {
  switch (action.type) {
    case ADD_ITEM:
      return [...state, action.payload]
    default:
      return state
  }
}
 
// Store
const store = createStore(cartReducer)
store.dispatch(addItem({ id: 1, name: 'Biryani' }))

Back to index


96. Lifting State Up

Theory

When two sibling components need to share state, lift it to their closest common parent. Parent holds state and passes it down as props + callback to update.

This is React's primary pattern for shared local state before reaching for Context or Redux.

Interview Answer

When siblings need the same data, I lift state to their common parent and pass value plus setter as props — keeps a single source of truth.

Real Example

jsx
function App() {
  const [selectedId, setSelectedId] = useState(null)
  return (
    <>
      <ProductList selectedId={selectedId} onSelect={setSelectedId} />
      <ProductDetail id={selectedId} />
    </>
  )
}

Back to index


97. Optimize Redux application performance

Theory

Redux performance issues come from: unnecessary re-renders, large state trees, unmemoized selectors, and storing server data in Redux.

Optimization strategies

StrategyHow
Reselect / createSelectorMemoized derived data
Slice selectorsSelect only needed slice
Normalized stateFlat entities by ID — avoid nested updates
RTK Query for APIDon't duplicate server state in Redux
React.memo on list itemsPrevent child re-renders
Avoid storing UI state globallyKeep modal open state local
useAppSelector with equalityshallowEqual for object selections
Code split reducersinjectReducer for lazy routes

Pros & Cons

Optimized ReduxUnoptimized Redux
✅ Minimal re-renders❌ Entire tree re-renders on any change
✅ DevTools still useful❌ Slow lists, janky UI
More setup upfrontSimple but doesn't scale

Interview Answer

I use createSelector for memoized selectors, normalize state by entity ID, keep server data in TanStack Query not Redux, split contexts from Redux, and wrap list items in React.memo.

Real Example

tsx
// ❌ Bad — new object every render, all subscribers re-render
const user = useAppSelector((state) => ({
  name: state.auth.user.name,
  role: state.auth.user.role,
}))
 
// ✅ createSelector — memoized
const selectUserProfile = createSelector(
  (state: RootState) => state.auth.user,
  (user) => (user ? { name: user.name, role: user.role } : null),
)
const profile = useAppSelector(selectUserProfile)
 
// Normalized state
// ❌ { orders: [{ id, items: [...] }, ...] }
// ✅ { orders: { byId: { "1": {...} }, allIds: ["1", "2"] } }
 
// RTK Query — server state NOT in Redux slice
const { data: orders } = useGetOrdersQuery(userId)
// Redux only holds: auth, UI preferences, cart
tsx
// injectReducer — code split Redux per route
import { injectReducer } from './store'
injectReducer('reports', reportsReducer)

Back to index


98. Prop Drilling

Theory

Prop drilling is passing props through many intermediate components that don't use them — just to reach a deep child.

Solutions: Context API, component composition (children), state management (Redux/Zustand).

Interview Answer

Prop drilling is passing props through layers that don't need them. I fix it with Context for global data or component composition to skip middle layers.

Real Example

jsx
// ❌ Drilling user through 4 levels
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <Avatar user={user} />
 
// ✅ Context
<UserProvider value={user}>
  <Layout><Sidebar><Avatar /></Sidebar></Layout>
</UserProvider>
 
// ✅ Composition — skip middle layers
<Layout sidebar={<Sidebar avatar={<Avatar user={user} />} />} />

Back to index


99. Redux

Theory

Redux is a predictable state container for JavaScript apps. State lives in a single store, updated only through actions processed by reducers (pure functions).

Redux Toolkit (RTK) is the modern standard — slices, Immer, createAsyncThunk.

Use Redux for complex client-side global state. Use TanStack Query for server state.

Pros & Cons

ProsCons
Single source of truthBoilerplate (reduced with RTK)
Predictable updatesOverkill for simple apps
Redux DevTools time-travelLearning curve
Middleware (logging, async)Can cause unnecessary re-renders

Real-Life Example

tsx
// store/cartSlice.ts
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] as CartItem[] },
  reducers: {
    addItem: (state, action: PayloadAction<CartItem>) => {
      const existing = state.items.find((i) => i.id === action.payload.id)
      if (existing) existing.qty += 1
      else state.items.push({ ...action.payload, qty: 1 })
    },
    removeItem: (state, action: PayloadAction<string>) => {
      state.items = state.items.filter((i) => i.id !== action.payload)
    },
  },
})
 
// Component
function CartBadge() {
  const count = useAppSelector((s) => s.cart.items.length)
  const dispatch = useAppDispatch()
  return <button onClick={() => dispatch(addItem(product))}>Cart ({count})</button>
}

Flow: dispatch(action) → reducer(state, action) → new state → useSelector → re-render

Back to index


100. Redux Toolkit (RTK)

Theory

RTK is the official Redux approach — reduces boilerplate with:

  • createSlice — actions + reducer together
  • configureStore — store with DevTools
  • createAsyncThunk — async logic
  • Immer built-in — "mutate" draft safely

Interview Answer

Redux Toolkit is the modern standard — createSlice combines actions and reducers, Immer allows safe mutation syntax, and createAsyncThunk handles API calls.

Real Example

jsx
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], status: 'idle' },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload)
    },
    clearCart: (state) => {
      state.items = []
    },
  },
  extraReducers: (builder) => {
    builder.addCase(fetchCart.fulfilled, (state, action) => {
      state.items = action.payload
    })
  },
})
 
const store = configureStore({ reducer: { cart: cartSlice.reducer } })

Back to index


101. Redux vs Context API

Theory

Redux / RTKContext
Use caseComplex global state, middlewareTheme, auth, locale
DevToolsYesNo
PerformanceSelective subscriptions (useSelector)All consumers re-render
AsynccreateAsyncThunk, middlewareManual in useEffect
BoilerplateHigher (RTK reduces it)Lower

Interview Answer

Context for simple infrequent global data like theme. Redux for complex shared state with middleware, DevTools, and selective subscriptions — cart, auth with roles, real-time feeds.

Real Example

jsx
// Context — theme (changes rarely)
<ThemeProvider value={theme}>
 
// Redux — cart (many actions, DevTools, persistence)
const items = useSelector((s) => s.cart.items);
dispatch(addItem(product));

Back to index


102. Redux Workflow

Theory

Redux flow: UI → dispatch(action) → reducer(state, action) → new state → UI re-renders

Components never mutate state directly. Reducers are pure functions.

Interview Answer

User action dispatches an action object, the reducer computes new state immutably, the store updates, and useSelector triggers re-render. RTK simplifies this with slices and createAsyncThunk.

Real Example

tsx
// 1. Slice
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload)
    },
    removeItem: (state, action) => {
      state.items = state.items.filter((i) => i.id !== action.payload)
    },
  },
})
 
// 2. Store
const store = configureStore({ reducer: { cart: cartSlice.reducer } })
 
// 3. Provider
;<Provider store={store}>
  <App />
</Provider>
 
// 4. Component
function Cart() {
  const items = useSelector((s) => s.cart.items)
  const dispatch = useDispatch()
  return <button onClick={() => dispatch(addItem(product))}>Cart ({items.length})</button>
}
plaintext
Workflow:
Click "Add" → dispatch(addItem(product))
→ reducer runs → state.cart.items updated
→ useSelector detects change → Cart re-renders

Back to index


103. State Management at Scale

Theory

At scale, state splits into categories — pick the right tool per category:

State typeExamplesTool
Server stateAPI data, cacheTanStack Query
URL stateFilters, paginationReact Router searchParams
Global client stateAuth, cart, themeRedux / Zustand / Jotai
Local UI stateModal open, inputuseState
Form stateValidation, dirtyReact Hook Form

Library Comparison

Redux (RTK)ZustandJotai
BoilerplateMedium (RTK helps)MinimalMinimal
DevToolsExcellentGood pluginLimited
MiddlewareThunks, listenersCustomAtom effects
SelectorsuseSelectorsubscribe with selectoruseAtom
Best forComplex apps, teamsSimple global stateFine-grained atomic state
Learning curveHigherLowLow

Interview Answer

Server state goes to TanStack Query. URL state stays in the router. Global client state — I pick Zustand for simplicity or Redux when we need middleware, DevTools, and predictable patterns across a large team. Local UI stays in useState.

Redux at Scale (RTK)

tsx
// features/cart/cartSlice.ts
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], status: 'idle' },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload)
    },
  },
  extraReducers: (builder) => {
    builder.addCase(syncCart.fulfilled, (state, action) => {
      state.items = action.payload
    })
  },
})
 
// Selectors — prevent unnecessary re-renders
const selectCartItems = (state: RootState) => state.cart.items
const selectCartTotal = createSelector([selectCartItems], (items) => items.reduce((sum, i) => sum + i.price * i.qty, 0))

Zustand — Lightweight Global State

tsx
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
 
interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (id: string) => void
}
 
const useCartStore = create<CartStore>()(
  devtools(
    persist(
      (set) => ({
        items: [],
        addItem: (item) => set((state) => ({ items: [...state.items, item] })),
        removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
      }),
      { name: 'cart-storage' },
    ),
  ),
)
 
// Component — subscribe to slice only
const items = useCartStore((s) => s.items)
const addItem = useCartStore((s) => s.addItem)

Jotai — Atomic Fine-Grained State

tsx
import { atom, useAtom, useAtomValue } from 'jotai'
 
const cartItemsAtom = atom<CartItem[]>([])
const cartTotalAtom = atom((get) => get(cartItemsAtom).reduce((sum, i) => sum + i.price, 0))
 
function CartBadge() {
  const total = useAtomValue(cartTotalAtom) // only re-renders when total changes
  return <span>₹{total}</span>
}

Anti-Patterns at Scale

tsx
// ❌ Everything in Context — all consumers re-render
<AppContext.Provider value={{ user, cart, theme, notifications, ... }}>
 
// ❌ Duplicating server data in Redux
dispatch(setUsers(apiResponse)); // TanStack Query already caches this
 
// ✅ Colocate state as low as possible
function Modal() {
  const [open, setOpen] = useState(false); // only Modal cares
}

Back to index


104. What are the advantages of Redux?

Theory

Redux centralizes application state and enforces predictable updates through pure reducers. It's most valuable when multiple components share complex state or when you need strong debugging and traceability.

Advantages

AdvantageExplanation
Single source of truthAll state in one store — easy to inspect
Predictable updatesPure reducers — same input always same output
Time-travel debuggingRedux DevTools replay every action
Middleware ecosystemLogging, async (thunk/saga), analytics
TestabilityReducers are pure functions — easy unit tests
ScalabilityLarge teams — clear state ownership per slice
Decoupled componentsAny component can read/dispatch without prop drilling

Pros & Cons

When Redux helpsWhen Redux is overkill
Complex shared client state (auth, cart, multi-step forms)Simple local UI state
Multiple features need same dataServer state (use TanStack Query)
Need action history / debuggingSmall apps with few components
Enterprise apps with many developersPrototyping / MVPs

Real-Life Example

tsx
// Without Redux — prop drilling + duplicate fetches
;<App user={user}>
  <Header user={user} />
  <Sidebar user={user} />
  <Dashboard user={user} />
</App>
 
// With Redux — any component accesses state directly
function Header() {
  const user = useAppSelector((s) => s.auth.user)
  return <span>{user.name}</span>
}
 
function Sidebar() {
  const user = useAppSelector((s) => s.auth.user)
  return <nav>{user.role === 'admin' && <AdminLink />}</nav>
}
 
// Redux DevTools — replay bug report
// "User clicked Add → ADD_ITEM → state.cart.items.length went 2→3"
// Reproduce exact sequence of actions

Modern note for interview: Mention Redux Toolkit + TanStack Query together — Redux for client state, Query for server state.

Back to index


105. What are the benefits of using Redux Toolkit over traditional Redux?

Answer

  • Less boilerplate
  • Simplified setup
  • Built-in Redux DevTools support
  • Encourages best practices
  • Includes RTK Query for efficient data fetching

Back to index


106. What does createApi do in RTK Query?

Answer

createApi sets up an API slice where you define endpoints using builder.query and builder.mutation. It auto-generates hooks for each endpoint.

Back to index


107. What is tagTypes in RTK Query?

Answer

tagTypes define the types of data used for cache invalidation. Tags help manage refetching when data changes.

Back to index


108. What is a slice in Redux Toolkit?

Answer

A slice is a portion of the Redux state and the logic associated with it. You create a slice using createSlice(), which automatically generates action creators and reducers.

js
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
  },
})

Back to index


109. What is RTK Query?

Answer

RTK Query is a powerful data fetching and caching tool built into Redux Toolkit. It manages API calls, caching, loading states, and errors efficiently.

Back to index


110. What is the difference between query and mutation in RTK Query?

Answer

  • query: For fetching data (GET)
  • mutation: For modifying data (POST, PUT, DELETE)

Back to index


111. When should you avoid Context API?

Avoid Context when:

SituationWhyAlternative
Frequently changing valuesAll consumers re-renderZustand, Jotai, external store
Large app-wide stateProvider hell, hard to traceRedux Toolkit, Zustand
Performance-critical listsContext in parent re-renders all childrenColocate state, composition
Server dataNo caching, dedup, stale handlingTanStack Query / SWR
Form stateToo granular for contextReact Hook Form
URL-shareable stateNot in URLRouter search params

The re-render problem

jsx
const AppContext = createContext()
 
function AppProvider({ children }) {
  const [user, setUser] = useState(null)
  const [theme, setTheme] = useState('light')
 
  // ❌ New object every render → ALL consumers re-render
  const value = { user, setUser, theme, setTheme }
 
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>
}

Fix — split contexts or memoize

jsx
const UserContext = createContext()
const ThemeContext = createContext()
 
function AppProvider({ children }) {
  const [user, setUser] = useState(null)
  const [theme, setTheme] = useState('light')
 
  const userValue = useMemo(() => ({ user, setUser }), [user])
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme])
 
  return (
    <UserContext.Provider value={userValue}>
      <ThemeContext.Provider value={themeValue}>{children}</ThemeContext.Provider>
    </UserContext.Provider>
  )
}

When Context IS appropriate

  • Theme (changes rarely)
  • Locale / i18n
  • Auth session (read-heavy, changes infrequently)
  • Design system tokens

Interview answer

Warning

Context is great for low-frequency, broadly needed data like theme or locale. Avoid it for fast-changing or fine-grained state — every consumer re-renders when the value changes. Split contexts, use selectors (Zustand), or reach for a proper state library.

Back to index


Routing & Navigation

112. Dynamic Routes — useParams

Theory

Dynamic segments use :param in the path. useParams() reads them from the URL.

Nested routes use <Outlet /> in the parent layout.

Interview Answer

Dynamic routes use :id in the path. useParams reads URL parameters — I use it to fetch data for detail pages.

Real Example

jsx
// Route: /products/:productId
function ProductDetail() {
  const { productId } = useParams()
  const { data, isLoading } = useProduct(productId)
  if (isLoading) return <Spinner />
  return <h1>{data.name}</h1>
}
 
;<Route path="/products/:productId" element={<ProductDetail />} />

Back to index


113. How do you connect routes in React?

Theory

React Router is the standard library for client-side routing in React SPAs. It maps URLs to components without full page reloads, enabling navigation, nested routes, protected routes, and URL parameters.

Core components (v6+):

  • BrowserRouter — wraps app, uses HTML5 history API
  • Routes / Route — define URL-to-component mapping
  • Link / NavLink — navigation without page reload
  • useNavigate, useParams, useSearchParams — programmatic navigation and URL data

Pros & Cons

Client-side routing (SPA)Server-side routing
✅ Instant navigation, no full reload✅ Better SEO out of the box
✅ App-like experience✅ Works without JavaScript
❌ Needs SSR/SSG for SEO❌ Full page reload on navigation
❌ Initial bundle includes all routes (without lazy loading)❌ Slower perceived navigation

Real-Life Example

Installation

bash
npm install react-router-dom

Basic routing setup

tsx
// App.tsx
import { BrowserRouter, Routes, Route, Link, Navigate } from 'react-router-dom'
import { lazy, Suspense } from 'react'
 
const Home = lazy(() => import('./pages/Home'))
const Restaurants = lazy(() => import('./pages/Restaurants'))
const RestaurantDetail = lazy(() => import('./pages/RestaurantDetail'))
const Cart = lazy(() => import('./pages/Cart'))
const Login = lazy(() => import('./pages/Login'))
const NotFound = lazy(() => import('./pages/NotFound'))
 
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/restaurants">Restaurants</Link>
        <Link to="/cart">Cart</Link>
      </nav>
 
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/restaurants" element={<Restaurants />} />
          <Route path="/restaurants/:id" element={<RestaurantDetail />} />
          <Route
            path="/cart"
            element={
              <ProtectedRoute>
                <Cart />
              </ProtectedRoute>
            }
          />
          <Route path="/login" element={<Login />} />
          <Route path="/404" element={<NotFound />} />
          <Route path="*" element={<Navigate to="/404" replace />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

Protected route

tsx
import { Navigate, useLocation } from 'react-router-dom'
 
function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth()
  const location = useLocation()
 
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />
  }
 
  return children
}

Dynamic params and navigation

tsx
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
 
function RestaurantDetail() {
  const { id } = useParams() // /restaurants/:id
  const [searchParams] = useSearchParams() // ?sort=rating&filter=veg
  const navigate = useNavigate()
 
  const sort = searchParams.get('sort') ?? 'rating'
 
  const goToCart = () => navigate('/cart', { state: { from: id } })
 
  return (
    <div>
      <h1>Restaurant #{id}</h1>
      <p>Sorted by: {sort}</p>
      <button onClick={goToCart}>Go to Cart</button>
    </div>
  )
}

Nested routes with layout

tsx
// Dashboard layout with sidebar
;<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<Overview />} />
  <Route path="orders" element={<Orders />} />
  <Route path="settings" element={<Settings />} />
</Route>
 
// DashboardLayout.tsx
import { Outlet } from 'react-router-dom'
 
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />
      </main>{' '}
      {/* child routes render here */}
    </div>
  )
}

Back to index


114. Navigation — useNavigate & useLocation

Theory

  • useNavigate() — programmatic navigation (navigate("/dashboard"), navigate(-1))
  • useLocation() — current location object (pathname, search, state)
  • useSearchParams() — read/write URL query strings

Interview Answer

useNavigate for programmatic redirects after login or form submit. useLocation for reading current path or passed state. useSearchParams for query strings.

Real Example

jsx
function LoginForm() {
  const navigate = useNavigate()
  const location = useLocation()
  const from = location.state?.from?.pathname || '/dashboard'
 
  const handleLogin = async () => {
    await login(credentials)
    navigate(from, { replace: true })
  }
}
 
function ProductFilters() {
  const [searchParams, setSearchParams] = useSearchParams()
  const category = searchParams.get('category') ?? 'all'
  const setCategory = (cat) => setSearchParams({ category: cat })
}

Back to index


115. Protected Routes

Theory

Protected routes redirect unauthenticated users to login. Wrap route element in a guard component that checks auth state.

Interview Answer

I wrap protected routes in a guard component that checks auth — if not logged in, redirect to login with return URL in location state.

Real Example

jsx
function ProtectedRoute({ children }) {
  const { user, isLoading } = useAuth()
  const location = useLocation()
 
  if (isLoading) return <Spinner />
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />
  return children
}
 
;<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

Back to index


116. React Router

Theory

React Router maps URLs to components in a Single Page Application — no full page reload. Core pieces (v6): BrowserRouter, Routes, Route, Link, useNavigate, useParams.

Pros & Cons

SPA routingFull page reload
✅ Instant navigation❌ Slower perceived navigation
✅ App-like UX❌ Needs SSR for SEO

Interview Answer

React Router handles client-side navigation — it maps URL paths to components without reloading the page, giving an app-like experience.

Real Example

jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
 
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/products">Products</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<Products />} />
        <Route path="/products/:id" element={<ProductDetail />} />
      </Routes>
    </BrowserRouter>
  )
}

Back to index


117. React Router Basics

Theory

React Router enables client-side routing in SPAs. Core pieces:

  • BrowserRouter — HTML5 history API
  • Routes / Route — path-to-component mapping
  • Link / NavLink — navigation without full reload
  • Outlet — nested route rendering

Interview Answer

React Router maps URLs to components client-side. BrowserRouter wraps the app, Routes define path-component pairs, Link navigates without page reload.

Real Example

jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
 
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/dashboard">Dashboard</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  )
}

Back to index


118. Your API routes are receiving a high volume of requests. How would you improve scalability and performance?

Description

Route Handlers (app/api/...) become bottlenecks under spike traffic without caching, rate limiting, horizontal scale, and offloading work to queues.

Theory

LayerTactic
EdgeMove read-heavy, low-compute routes to Edge Runtime
CacheCDN cache GET responses; Redis for computed results
Rate limitingToken bucket per IP/user (Upstash, middleware)
QueuePOST returns 202; worker processes async
DBConnection pooling (PgBouncer), read replicas
StatelessScale serverless instances horizontally

Pros & Cons

Rate limit + cache + queueRaw handlers hitting DB every time
✅ Survives viral traffic❌ DB connection exhaustion
✅ Predictable cost❌ Cascading 503 errors
❌ More infrastructure❌ Hard to scale past single region

Real Example — High-traffic search API

tsx
// middleware.ts — rate limit at edge
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
 
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 req/min per IP
})
 
export async function middleware(req: NextRequest) {
  if (!req.nextUrl.pathname.startsWith('/api/')) return NextResponse.next()
 
  const ip = req.ip ?? '127.0.0.1'
  const { success, reset } = await ratelimit.limit(ip)
 
  if (!success) {
    return NextResponse.json({ error: 'Too many requests' }, { status: 429, headers: { 'Retry-After': String(reset) } })
  }
 
  return NextResponse.next()
}
 
export const config = { matcher: '/api/:path*' }
tsx
// app/api/search/route.ts
export const runtime = 'edge'
 
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url)
  const q = searchParams.get('q') ?? ''
 
  const res = await fetch(`${process.env.SEARCH_SERVICE}/q=${encodeURIComponent(q)}`, {
    next: { revalidate: 30 },
  })
 
  const data = await res.json()
 
  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=30, stale-while-revalidate=60',
    },
  })
}
tsx
// Heavy writes — queue instead of blocking
// app/api/orders/route.ts
export async function POST(req: Request) {
  const body = await req.json()
  await queue.publish('order.created', body) // SQS / Kafka / BullMQ
  return Response.json({ status: 'accepted' }, { status: 202 })
}

Interview Answer

I'd add edge rate limiting in middleware, cache GET route responses at CDN with s-maxage, move heavy work to async queues, use connection pooling for the database, and deploy stateless Route Handlers so they scale horizontally under load.

Back to index


API & Data Fetching

119. A Next.js page is loading slowly because it fetches data from multiple APIs. How would you optimize the page performance?

Description

The page blocks on sequential or waterfall API calls — each request waits for the previous one. Time to first byte (TTFB) and Largest Contentful Paint (LCP) suffer when the server or client chains 4–5 fetches.

Theory

In the App Router, Server Components fetch on the server by default. The main levers are:

TechniqueWhat it does
Parallel fetchingPromise.all — independent APIs run at the same time
Streaming + SuspenseShip shell HTML first; slow sections stream in
Colocate dataFetch in the component that needs it (automatic dedup via fetch cache)
Move to edgeexport const runtime = 'edge' for low-latency reads
Reduce payloadReturn only fields the page needs
Cache stable datafetch(url, { next: { revalidate: 60 } })
Waterfall (slow)
text
User → Page → API A (400ms) → API B (300ms) → API C (350ms) = 1050ms
Parallel (fast)
text
User → Page → API A ─┐
              API B ─┼→ merge → 400ms (longest call)
              API C ─┘

Pros & Cons

Parallel + streamingOne giant blocking fetch
✅ Faster perceived load❌ More complex loading UI
✅ Better Core Web Vitals❌ Harder to debug race conditions
✅ Partial content visible early❌ Needs Suspense boundaries

Real Example — Dashboard with user, orders, and notifications

tsx
// app/dashboard/page.tsx — Server Component
import { Suspense } from 'react'
import { UserHeader } from './user-header'
import { OrdersPanel } from './orders-panel'
import { NotificationsPanel } from './notifications-panel'
import { PanelSkeleton } from '@/components/panel-skeleton'
 
// Independent parallel fetches inside each child — no waterfall
export default function DashboardPage() {
  return (
    <main>
      <Suspense fallback={<PanelSkeleton />}>
        <UserHeader />
      </Suspense>
      <div className="grid grid-cols-2 gap-4">
        <Suspense fallback={<PanelSkeleton />}>
          <OrdersPanel />
        </Suspense>
        <Suspense fallback={<PanelSkeleton />}>
          <NotificationsPanel />
        </Suspense>
      </div>
    </main>
  )
}
tsx
// app/dashboard/orders-panel.tsx
async function getOrders(userId: string) {
  const res = await fetch(`${process.env.API_URL}/orders?userId=${userId}`, {
    next: { revalidate: 30, tags: ['orders'] },
  })
  return res.json()
}
 
export async function OrdersPanel() {
  const session = await getSession() // deduped if same fetch elsewhere
  const orders = await getOrders(session.userId)
  return <OrderList orders={orders} />
}
tsx
// When you MUST combine in one file — parallel, not sequential
export async function AnalyticsPage() {
  const [revenue, users, churn] = await Promise.all([fetchRevenue(), fetchUsers(), fetchChurn()])
 
  return <Analytics revenue={revenue} users={users} churn={churn} />
}
Also apply
  • loading.tsx for route-level skeleton
  • React Query / SWR on client islands only for interactive refetch
  • Measure with Lighthouse + Next.js Speed Insights

Interview Answer

I'd eliminate request waterfalls by parallelizing independent fetches with Promise.all, split the page into Server Components with Suspense boundaries for streaming, cache semi-static API responses with revalidate, and measure TTFB and LCP before and after.

Back to index


120. Async Data Handling — React Query & Caching

Theory

TanStack Query manages server state — fetching, caching, background sync, deduplication, retries, stale-while-revalidate.

Key concepts:

  • queryKey — unique cache identifier
  • staleTime — how long data is fresh (no refetch)
  • gcTime (cacheTime) — how long unused data stays in cache
  • invalidation — mark cache stale after mutations

Pros & Cons

ProsCons
Eliminates 80% of useEffect fetch boilerplateLearning curve for cache keys
Background refetch, dedupOver-fetching if keys wrong
Optimistic updates built-inSSR needs hydration setup

Interview Answer

TanStack Query owns server state — I define query keys hierarchically, set staleTime based on data freshness needs, invalidate after mutations, and use optimistic updates for snappy UX on cart and likes.

Core Patterns

tsx
// Hierarchical query keys — invalidate at any level
const productKeys = {
  all: ['products'] as const,
  lists: () => [...productKeys.all, 'list'] as const,
  list: (filters: Filters) => [...productKeys.lists(), filters] as const,
  details: () => [...productKeys.all, 'detail'] as const,
  detail: (id: string) => [...productKeys.details(), id] as const,
}
 
function useProducts(filters: Filters) {
  return useQuery({
    queryKey: productKeys.list(filters),
    queryFn: () => api.getProducts(filters),
    staleTime: 5 * 60 * 1000, // 5 min — product list doesn't change often
    gcTime: 30 * 60 * 1000,
  })
}
 
function useProduct(id: string) {
  return useQuery({
    queryKey: productKeys.detail(id),
    queryFn: () => api.getProduct(id),
    staleTime: 60 * 1000,
    enabled: !!id,
  })
}

Mutation + Invalidation

tsx
function useAddToCart() {
  const queryClient = useQueryClient()
 
  return useMutation({
    mutationFn: (item: CartItem) => api.addToCart(item),
    onMutate: async (newItem) => {
      await queryClient.cancelQueries({ queryKey: ['cart'] })
      const previous = queryClient.getQueryData<CartItem[]>(['cart'])
      queryClient.setQueryData(['cart'], (old = []) => [...old, newItem])
      return { previous }
    },
    onError: (_err, _item, context) => {
      queryClient.setQueryData(['cart'], context?.previous)
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['cart'] })
    },
  })
}

Parallel + Dependent Queries

tsx
// Parallel
const user = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
const orders = useQuery({
  queryKey: ['orders', id],
  queryFn: () => fetchOrders(id),
})
 
// Dependent — orders waits for user
const user = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
const orders = useQuery({
  queryKey: ['orders', user.data?.id],
  queryFn: () => fetchOrders(user.data!.id),
  enabled: !!user.data?.id,
})
 
// Parallel with useQueries
const results = useQueries({
  queries: ids.map((id) => ({
    queryKey: ['product', id],
    queryFn: () => fetchProduct(id),
  })),
})

Caching Strategy Table

Data typestaleTimeRefetch on focus?
User profile5–10 minYes
Product catalog5 minYes
Stock/inventory30 secYes
Static CMS content1 hourNo
Real-time order status0 (always stale)WebSocket + invalidate

Back to index


121. Axios vs fetch in React

Theory

fetchAxios
Built-inYesNo — npm package
JSONManual .json()Auto-transform
InterceptorsManualBuilt-in
TimeoutManual AbortControllerBuilt-in config
Error on 4xxNo — check response.okThrows by default

Interview Answer

fetch is built-in and fine for simple calls. Axios adds interceptors, auto JSON, and better error defaults — I use it with a configured instance for auth tokens and base URL.

Real Example

jsx
const api = axios.create({ baseURL: '/api/v1', timeout: 10000 })
 
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})
 
api.interceptors.response.use(
  (res) => res,
  (err) => {
    if (err.response?.status === 401) logout()
    return Promise.reject(err)
  },
)

Back to index


122. Build a custom React hook for data fetching

Theory

A custom hook extracts reusable stateful logic. For data fetching, handle:

  • Loading, error, and data states
  • Race conditions (stale responses)
  • Abort/cleanup on unmount or dependency change
  • Optional: caching, retry, refetch

Practical Example — Production-grade useFetch

tsx
import { useEffect, useReducer, useRef } from 'react'
 
type FetchState<T> = {
  data: T | null
  error: Error | null
  isLoading: boolean
}
 
type Action<T> = { type: 'loading' } | { type: 'success'; payload: T } | { type: 'error'; payload: Error }
 
function fetchReducer<T>(state: FetchState<T>, action: Action<T>): FetchState<T> {
  switch (action.type) {
    case 'loading':
      return { ...state, isLoading: true, error: null }
    case 'success':
      return { data: action.payload, error: null, isLoading: false }
    case 'error':
      return { ...state, error: action.payload, isLoading: false }
    default:
      return state
  }
}
 
export function useFetch<T>(url: string | null, options?: RequestInit) {
  const [state, dispatch] = useReducer(fetchReducer<T>, {
    data: null,
    error: null,
    isLoading: false,
  })
 
  const optionsRef = useRef(options)
  optionsRef.current = options
 
  useEffect(() => {
    if (!url) return
 
    const controller = new AbortController()
    let cancelled = false
 
    async function load() {
      dispatch({ type: 'loading' })
 
      try {
        const res = await fetch(url!, {
          ...optionsRef.current,
          signal: controller.signal,
        })
 
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
 
        const json = (await res.json()) as T
        if (!cancelled) dispatch({ type: 'success', payload: json })
      } catch (err) {
        if (!cancelled && (err as Error).name !== 'AbortError') {
          dispatch({ type: 'error', payload: err as Error })
        }
      }
    }
 
    load()
 
    return () => {
      cancelled = true
      controller.abort()
    }
  }, [url])
 
  return state
}

Usage

tsx
function RestaurantList() {
  const { data, error, isLoading } = useFetch<Restaurant[]>('https://api.example.com/restaurants')
 
  if (isLoading) return <Spinner />
  if (error) return <ErrorBanner message={error.message} />
  return (
    <ul>
      {data?.map((r) => (
        <li key={r.id}>{r.name}</li>
      ))}
    </ul>
  )
}

What interviewers look for

  • AbortController for cleanup
  • Guard against setting state after unmount
  • Typed generics
  • Separation of concerns (hook vs UI)
  • Mention React Query / SWR for production apps

Performance

In production at scale, I'd reach for TanStack Query or SWR for caching, deduplication, background refetch, and stale-while-revalidate — but I can implement the core pattern when needed.

Back to index


123. CORS

Theory

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks JavaScript from reading responses from a different origin unless the server explicitly allows it.

Origin = protocol + domain + port (e.g. https://a quick-commerce app.com:443)

The browser sends a preflight OPTIONS request for non-simple requests (custom headers, PUT/DELETE, JSON content-type). Server responds with:

plaintext
Access-Control-Allow-Origin: https://a quick-commerce app.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

Pros & Cons

ProsCons
Protects users from malicious sitesConfusing for beginners
Standard, well-supportedMisconfigured servers break apps
Granular control per originCan't fix purely on frontend

Interview Answer

CORS is a browser security policy blocking cross-origin API reads unless the server sends Access-Control-Allow-Origin. Fix it on the server or use a same-origin proxy — not by disabling CORS in the browser.

Real Example

javascript
// Frontend — a quick-commerce app.com calling api.a quick-commerce app.com
fetch('https://api.a quick-commerce app.com/v1/products', {
  method: 'GET',
  credentials: 'include', // sends cookies — server must allow credentials
  headers: { 'Content-Type': 'application/json' },
})
  .then((r) => r.json())
  .then(setProducts)
 
// ❌ CORS error in console:
// "Access to fetch at 'https://api.a quick-commerce app.com' from origin 'https://a quick-commerce app.com'
//  has been blocked by CORS policy"
javascript
// Backend — Express CORS setup
import cors from 'cors'
 
app.use(
  cors({
    origin: ['https://a quick-commerce app.com', 'http://localhost:3000'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
  }),
)

Common Interview Follow-ups

QuestionAnswer
Can frontend bypass CORS?No — it's enforced by the browser
Simple vs preflight request?Simple: GET/POST with basic headers — no OPTIONS. Preflight: custom headers, PUT/DELETE
Why credentials need special header?Access-Control-Allow-Credentials: true + specific origin (not *)

Back to index


124. Custom useFetch Hook

Theory

A custom useFetch hook encapsulates API call logic — loading, data, error — reusable across any endpoint. Senior expectations:

  • AbortController cleanup on unmount / URL change
  • Re-fetch when URL or deps change
  • Handle HTTP errors (non-2xx)
  • Optional: refetch function, enabled flag

Pros & Cons

ProsCons
DRY — one fetch pattern everywhereFor production, TanStack Query is better
Consistent loading/error handlingManual cache invalidation
Easy to test in isolationCan grow complex with auth/retry

Interview Answer

useFetch wraps fetch with useState and useEffect — returns data, loading, and error, aborts in-flight requests on cleanup, and re-fetches when the URL changes.

Full Implementation

jsx
import { useState, useEffect, useCallback, useRef } from 'react'
 
function useFetch(url, options = {}) {
  const { enabled = true, deps = [] } = options
 
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
 
  const abortRef = useRef(null)
 
  const fetchData = useCallback(async () => {
    if (!url || !enabled) return
 
    abortRef.current?.abort()
    const controller = new AbortController()
    abortRef.current = controller
 
    setLoading(true)
    setError(null)
 
    try {
      const response = await fetch(url, {
        signal: controller.signal,
        headers: { 'Content-Type': 'application/json', ...options.headers },
        ...options.fetchOptions,
      })
 
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
 
      const json = await response.json()
      setData(json)
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err.message ?? 'Something went wrong')
        setData(null)
      }
    } finally {
      if (!controller.signal.aborted) {
        setLoading(false)
      }
    }
  }, [url, enabled, ...deps])
 
  useEffect(() => {
    fetchData()
    return () => abortRef.current?.abort()
  }, [fetchData])
 
  const refetch = useCallback(() => fetchData(), [fetchData])
 
  return { data, loading, error, refetch }
}
 
export default useFetch

Usage Examples

jsx
function UserList() {
  const { data, loading, error, refetch } = useFetch('https://jsonplaceholder.typicode.com/users')
 
  if (loading) return <p>Loading...</p>
  if (error)
    return (
      <p>
        Error: {error} <button onClick={refetch}>Retry</button>
      </p>
    )
 
  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
 
function UserDetail({ userId }) {
  const { data, loading, error } = useFetch(userId ? `https://jsonplaceholder.typicode.com/users/${userId}` : null, {
    enabled: !!userId,
    deps: [userId],
  })
  // ...
}

Generic Version (any HTTP method + body)

jsx
function useFetch(url, options = {}) {
  const { method = 'GET', body, enabled = true } = options
  const [state, setState] = useState({ data: null, loading: false, error: null })
 
  useEffect(() => {
    if (!enabled || !url) return
 
    const controller = new AbortController()
    setState((s) => ({ ...s, loading: true, error: null }))
 
    fetch(url, {
      method,
      body: body ? JSON.stringify(body) : undefined,
      headers: { 'Content-Type': 'application/json' },
      signal: controller.signal,
    })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error(r.statusText))))
      .then((data) => setState({ data, loading: false, error: null }))
      .catch((err) => {
        if (err.name !== 'AbortError') {
          setState({ data: null, loading: false, error: err.message })
        }
      })
 
    return () => controller.abort()
  }, [url, method, enabled, JSON.stringify(body)])
 
  return state
}

Interview Tip

Mention that in production you'd use TanStack Query for caching, deduplication, and stale-while-revalidate — but useFetch demonstrates hook composition and effect cleanup.

Back to index


125. How do you handle race conditions in API calls?

The problem

User navigates quickly → Request A starts → Request B starts → B finishes first → A finishes last → stale data overwrites fresh data.

Solutions

ApproachHow
AbortControllerCancel previous request
Ignore flagTrack latest request ID
React Query / SWRBuilt-in stale request handling
Axios cancel tokenLegacy cancel pattern

Practical Example — AbortController

jsx
function useSearch(query) {
  const [results, setResults] = useState([])
  const [loading, setLoading] = useState(false)
 
  useEffect(() => {
    if (!query) return
 
    const controller = new AbortController()
    let cancelled = false
 
    async function search() {
      setLoading(true)
      try {
        const res = await fetch(`/api/search?q=${query}`, {
          signal: controller.signal,
        })
        const data = await res.json()
        if (!cancelled) setResults(data)
      } catch (err) {
        if (err.name !== 'AbortError' && !cancelled) {
          console.error(err)
        }
      } finally {
        if (!cancelled) setLoading(false)
      }
    }
 
    search()
 
    return () => {
      cancelled = true
      controller.abort()
    }
  }, [query])
 
  return { results, loading }
}

Practical Example — Request ID pattern

jsx
function useLatestFetch(url) {
  const [data, setData] = useState(null)
  const requestIdRef = useRef(0)
 
  useEffect(() => {
    const id = ++requestIdRef.current
 
    fetch(url)
      .then((r) => r.json())
      .then((json) => {
        if (id === requestIdRef.current) setData(json)
      })
  }, [url])
 
  return data
}

Interview answer

Performance

Abort the previous request on dependency change using AbortController, or ignore stale responses with a request counter. In production I use TanStack Query which handles deduplication, cancellation, and cache invalidation out of the box.

Back to index


126. How does SSO work?

Theory

Single Sign-On (SSO) lets a user authenticate once and access multiple applications without logging in again. It is typically built on top of OAuth 2.0 + OpenID Connect (OIDC) or SAML.

The Identity Provider (IdP) handles authentication. Service Providers (SPs) trust the IdP's tokens.

Pros & Cons

ProsCons
One login for all company appsSingle point of failure (IdP down = all apps down)
Centralized user managementComplex setup for small teams
Better security (MFA at IdP level)Session management across domains is tricky

Real-Life Example

plaintext
Employee logs into company SSO (Okta) once:
 
1. Employee visits internal-dashboard.company.com
2. No session → redirect to okta.company.com/login
3. Employee enters credentials + MFA on Okta
4. Okta issues SAML assertion / OIDC id_token
5. Dashboard app validates token → creates local session
6. Employee visits hr-portal.company.com
   → Already has Okta session cookie → auto-authenticated
   → No second login required
 
Frontend's role:
- Redirect to IdP login page
- Handle callback with authorization code
- Store session token (httpOnly cookie, not localStorage)
- Check token expiry and refresh silently

Back to index


127. Long Polling vs WebSockets

Theory

Long PollingWebSockets
ConnectionNew HTTP request each cycleSingle persistent connection
DirectionClient pulls (server holds request)Bidirectional push/pull
LatencyHigher — wait + new requestLow — instant push
OverheadHTTP headers every requestMinimal after handshake
ScalingEasier — stateless HTTPHarder — sticky sessions
Use caseFallback, low-frequency updatesHigh-frequency real-time

Long polling: Client sends request → server holds it open until data arrives or timeout → client immediately sends new request.

Short polling: Client requests every N seconds regardless — wasteful.

Interview Answer

Long polling simulates push by holding HTTP requests open — simpler but higher latency. WebSockets give true real-time bidirectional communication — I pick WebSockets for live delivery tracking, long polling as fallback.

Real Example

javascript
// Long polling — order status
async function pollOrderStatus(orderId) {
  while (true) {
    try {
      const res = await fetch(`/api/orders/${orderId}/status?longPoll=true`, {
        signal: AbortSignal.timeout(30000), // 30s server hold
      })
      const data = await res.json()
      updateUI(data)
      if (data.status === 'delivered') break
    } catch (err) {
      await new Promise((r) => setTimeout(r, 2000)) // backoff on error
    }
  }
}
 
// WebSocket — same use case, better UX
const ws = new WebSocket(`wss://api.a quick-commerce app.com/orders/${orderId}`)
ws.onmessage = (e) => {
  const data = JSON.parse(e.data)
  updateUI(data)
  if (data.status === 'delivered') ws.close()
}

When a quick-commerce app Might Use Each

FeatureLikely approach
Live delivery map / ETAWebSocket
Cart inventory syncWebSocket or SSE
Notification badge (low freq)Long polling or SSE
Legacy API / CDN compatibilityLong polling fallback

Back to index


128. Optimize API calls and handle loading/error states properly

Theory

Every API call in a UI should handle three states: loading, success, and error. Optimize by: deduplicating requests, caching responses, aborting stale requests, and using libraries like TanStack Query or SWR.

Never leave users staring at a blank screen or show stale data from a previous query.

Pros & Cons

TanStack Query / SWRManual fetch in useEffect
✅ Auto caching, dedup, refetch❌ Boilerplate for every endpoint
✅ Stale-while-revalidate❌ Race conditions without AbortController
✅ Loading/error built-in❌ No background refetch
✅ DevTools❌ Cache invalidation is manual

Real-Life Example

tsx
// ✅ With TanStack Query — production pattern
function RestaurantList({ city }) {
  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: ['restaurants', city],
    queryFn: () => api.get(`/restaurants?city=${city}`).then((r) => r.data),
    staleTime: 5 * 60 * 1000,
    retry: 2,
  })
 
  if (isLoading) return <RestaurantListSkeleton count={6} />
  if (isError) return <ErrorBanner message={error.message} onRetry={refetch} />
  if (!data?.length) return <EmptyState message="No restaurants found" />
 
  return data.map((r) => <RestaurantCard key={r.id} restaurant={r} />)
}
 
// Manual pattern — when you can't use a library
function useFetch<T>(url: string) {
  const [state, setState] = useState<{
    data: T | null
    loading: boolean
    error: string | null
  }>({ data: null, loading: true, error: null })
 
  useEffect(() => {
    const controller = new AbortController()
    setState({ data: null, loading: true, error: null })
 
    fetch(url, { signal: controller.signal })
      .then((r) => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`)
        return r.json()
      })
      .then((data) => setState({ data, loading: false, error: null }))
      .catch((err) => {
        if (err.name !== 'AbortError') {
          setState({ data: null, loading: false, error: err.message })
        }
      })
 
    return () => controller.abort()
  }, [url])
 
  return state
}

Back to index


129. REST vs GraphQL APIs

Theory

REST exposes multiple endpoints, each returning fixed data shapes. GraphQL exposes a single endpoint where the client specifies exactly what fields it needs.

RESTGraphQL
EndpointsMultiple (/users, /orders)Single (/graphql)
Data fetchingServer defines response shapeClient defines query shape
Over-fetchingCommonAvoided
Under-fetchingNeeds multiple requestsSingle request
CachingHTTP caching (simple)Complex (needs Apollo/Relay)
ToolingUniversalGraphQL schema, Playground

Pros & Cons

RESTGraphQL
✅ Simple, well-understood✅ Fetch exactly what you need
✅ HTTP cache friendly✅ Single request for related data
✅ File upload straightforward✅ Strong typing via schema
❌ Over/under-fetching❌ Caching harder
❌ Multiple round trips❌ N+1 query problem on server
❌ Learning curve

Real-Life Example

javascript
// REST — 3 requests for dashboard
const user = await fetch('/api/user/42').then((r) => r.json())
const orders = await fetch('/api/users/42/orders').then((r) => r.json())
const notifications = await fetch('/api/users/42/notifications').then((r) => r.json())
 
// GraphQL — 1 request, exact fields
const { data } = await fetch('/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: `
      query Dashboard($userId: ID!) {
        user(id: $userId) { name email avatar }
        orders(userId: $userId, limit: 5) { id total status }
        notifications(userId: $userId, unread: true) { id message createdAt }
      }
    `,
    variables: { userId: '42' },
  }),
}).then((r) => r.json())

When to choose:

  • REST: Simple CRUD, public APIs, CDN caching critical, small teams
  • GraphQL: Mobile apps (bandwidth), complex dashboards, many clients with different data needs

Back to index


130. Scenario-Based: 3 API calls, fast load, partial errors

Theory

Requirements from the company interview:

  1. Three independent API calls populate different page sections
  2. Page must load fast
  3. If one API fails, show error only in that block — other sections keep working

Solution architecture:

  • Parallel fetching — Promise.allSettled() or three independent TanStack Query hooks
  • Per-section error boundaries — catch render errors
  • Per-section error/loading state — independent UI per block
  • Streaming / progressive rendering — show each section as data arrives (not wait for all)
  • Suspense boundaries (optional) — per-section loading fallbacks

Pros & Cons

Independent sections + allSettledPromise.all
✅ Partial failure tolerated❌ One failure breaks entire page
✅ Fast perceived load — sections appear as ready❌ Waits for slowest API
✅ Matches real enterprise dashboardsMore complex state per section
More code per section

Real-Life Example — Full implementation

tsx
// hooks/useSectionData.ts
import { useQuery } from '@tanstack/react-query'
 
function useSectionData<T>(key: string, fetcher: () => Promise<T>) {
  return useQuery({
    queryKey: [key],
    queryFn: fetcher,
    retry: 2,
    staleTime: 5 * 60 * 1000,
    // Each query is independent — one failure doesn't affect others
  })
}
tsx
// components/SectionBlock.tsx — reusable section wrapper
function SectionBlock({ title, isLoading, isError, error, onRetry, children }) {
  return (
    <section className="dashboard-section" aria-labelledby={`${title}-heading`}>
      <h2 id={`${title}-heading`}>{title}</h2>
 
      {isLoading && <SectionSkeleton />}
 
      {isError && (
        <div role="alert" className="section-error">
          <p>
            Failed to load {title}. {error?.message}
          </p>
          <button onClick={onRetry}>Retry</button>
        </div>
      )}
 
      {!isLoading && !isError && children}
    </section>
  )
}
tsx
// pages/DashboardPage.tsx — the company scenario solution
import ErrorBoundary from '../ErrorBoundary'
 
function AnnouncementsSection() {
  const { data, isLoading, isError, error, refetch } = useSectionData('announcements', () =>
    fetch('/api/announcements').then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`)
      return r.json()
    }),
  )
 
  return (
    <SectionBlock title="Announcements" isLoading={isLoading} isError={isError} error={error} onRetry={refetch}>
      <AnnouncementList items={data} />
    </SectionBlock>
  )
}
 
function TeamSection() {
  const { data, isLoading, isError, error, refetch } = useSectionData('team', () =>
    fetch('/api/team').then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`)
      return r.json()
    }),
  )
 
  return (
    <SectionBlock title="Team" isLoading={isLoading} isError={isError} error={error} onRetry={refetch}>
      <TeamGrid members={data} />
    </SectionBlock>
  )
}
 
function ProjectsSection() {
  const { data, isLoading, isError, error, refetch } = useSectionData('projects', () =>
    fetch('/api/projects').then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`)
      return r.json()
    }),
  )
 
  return (
    <SectionBlock title="Projects" isLoading={isLoading} isError={isError} error={error} onRetry={refetch}>
      <ProjectList projects={data} />
    </SectionBlock>
  )
}
 
function DashboardPage() {
  return (
    <main className="dashboard-grid">
      {/* Each section: independent fetch, loading, error, and error boundary */}
      <ErrorBoundary fallback={<SectionError name="Announcements" />}>
        <AnnouncementsSection />
      </ErrorBoundary>
 
      <ErrorBoundary fallback={<SectionError name="Team" />}>
        <TeamSection />
      </ErrorBoundary>
 
      <ErrorBoundary fallback={<SectionError name="Projects" />}>
        <ProjectsSection />
      </ErrorBoundary>
    </main>
  )
}

Alternative — Promise.allSettled without React Query

tsx
function DashboardPageManual() {
  const [sections, setSections] = useState({
    announcements: { data: null, loading: true, error: null },
    team: { data: null, loading: true, error: null },
    projects: { data: null, loading: true, error: null },
  })
 
  useEffect(() => {
    const controllers = [new AbortController(), new AbortController(), new AbortController()]
 
    const configs = [
      { key: 'announcements', url: '/api/announcements' },
      { key: 'team', url: '/api/team' },
      { key: 'projects', url: '/api/projects' },
    ]
 
    // Fire all 3 in parallel — don't wait for each other
    configs.forEach(({ key, url }, i) => {
      fetch(url, { signal: controllers[i].signal })
        .then((r) => {
          if (!r.ok) throw new Error(`HTTP ${r.status}`)
          return r.json()
        })
        .then((data) => {
          setSections((prev) => ({
            ...prev,
            [key]: { data, loading: false, error: null },
          }))
        })
        .catch((err) => {
          if (err.name === 'AbortError') return
          setSections((prev) => ({
            ...prev,
            [key]: { data: null, loading: false, error: err.message },
          }))
        })
    })
 
    return () => controllers.forEach((c) => c.abort())
  }, [])
 
  return (
    <main className="dashboard-grid">
      <SectionBlock title="Announcements" {...sections.announcements} />
      <SectionBlock title="Team" {...sections.team} />
      <SectionBlock title="Projects" {...sections.projects} />
    </main>
  )
}

Architecture diagram

plaintext
Dashboard Page
├── Announcements Section
│   ├── fetch /api/announcements (parallel)
│   ├── own loading skeleton
│   ├── own error block (if fails)
│   └── ErrorBoundary (render errors)
├── Team Section
│   ├── fetch /api/team (parallel)
│   ├── own loading / error
│   └── ErrorBoundary
└── Projects Section
    ├── fetch /api/projects (parallel)
    ├── own loading / error
    └── ErrorBoundary
 
✅ Fast: all 3 fire simultaneously, sections render as each resolves
✅ Resilient: team API 503 → only team block shows error

Performance optimizations to mention

TechniqueBenefit
Parallel fetch (not sequential)Total time = slowest API, not sum of all
Independent loading statesPerceived speed — fast sections show first
staleTime caching (React Query)Instant on revisit
Prefetch on navigation hoverData ready before page mount
Skeleton loaders per sectionBetter CLS than full-page spinner
AbortController cleanupNo stale updates on navigate away

Interview answer (concise)

Performance

I'd fire all three API calls in parallel — not sequentially — so total wait time equals the slowest call. Each section manages its own loading, data, and error state independently using TanStack Query or separate fetch calls with Promise.allSettled. If the team API fails, only the team block shows an error with a retry button; announcements and projects render normally. I'd wrap each section in an Error Boundary for render errors, use skeleton loaders for perceived speed, and cache responses with staleTime for repeat visits.

#TopicOne-liner
1HoistingDeclarations registered before execution; var=undefined, let=TDZ
2Event LoopSync → microtasks → macrotask
3Arrow vs normalArrow = lexical this; normal = dynamic this
4Promisepending/fulfilled/rejected async result
5all vs allSettledall = fail fast; allSettled = all outcomes, partial success
6Virtual DOMJS tree + diff → minimal real DOM updates
7React performanceSplit, memo, virtualize, cache, profile, Web Vitals
8Redux workflowdispatch → reducer → store → UI
9Redux advantagesSingle truth, predictable, DevTools, testable
10Snapshot testingSave render output, compare on future runs
11First non-repeatedFrequency map + second left-to-right scan → "I"
12Error BoundaryCatch render errors, show fallback per section
133 API scenarioParallel fetch, per-section state/error, Error Boundaries

Back to index


131. Spread & Rest Operators

Theory

Both use ... syntax but serve opposite purposes:

OperatorContextAction
SpreadArrays, objects, function callsExpands iterable into individual elements
RestFunction parameters, destructuringCollects remaining elements into array/object

Pros & Cons

ProsCons
Immutable updates (copy + modify)Shallow copy only — nested objects shared
Clean function argument handlingCan be confusing spread vs rest
Merge objects/arrays easilyPerformance cost on very large arrays

Real-Life Example

javascript
// SPREAD — arrays
const cart = [item1, item2]
const updatedCart = [...cart, item3] // add
const withoutFirst = [...cart.slice(1)] // or cart.filter
const merged = [...cartA, ...cartB]
 
// SPREAD — objects (immutable state update)
const user = { name: 'Amit', role: 'user' }
const updated = { ...user, role: 'admin' } // override one field
const withDefaults = { theme: 'light', ...user } // user overrides defaults
 
// SPREAD — function calls
Math.max(...prices)
fetchOrder(...orderIds)
 
// REST — function parameters
function createOrder(customerId, ...items) {
  console.log(items) // array of all remaining args
  return { customerId, items }
}
createOrder('c_1', { id: 1 }, { id: 2 })
 
// REST — destructuring
const [head, ...tail] = [1, 2, 3, 4] // head=1, tail=[2,3,4]
const { name, ...rest } = user // rest = all other properties
 
// React state update
setFilters((prev) => ({ ...prev, city: 'Delhi', page: 1 }))
setItems((prev) => prev.filter((i) => i.id !== removedId))
javascript
// ⚠️ Shallow copy trap
const state = { user: { name: 'Amit' }, cart: [] }
const copy = { ...state }
copy.user.name = 'Rahul'
console.log(state.user.name) // "Rahul" — nested object is shared!
// Fix: structuredClone(state) or deep merge

Back to index


132. Spread vs Rest Operator

Theory

Both use ... syntax — opposite purposes:

SpreadRest
ActionExpandsCollects
ContextArrays, objects, function callsFunction params, destructuring

Interview Answer

Spread expands an iterable into elements — for copying and merging. Rest collects remaining elements into an array — in parameters and destructuring.

Real Example

javascript
// Spread
const merged = { ...defaults, ...userConfig }
const allItems = [...cartA, ...cartB]
Math.max(...numbers)
 
// Rest
function sum(...args) {
  return args.reduce((a, b) => a + b, 0)
}
const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2,3,4]
 
// React immutable update
setUser((prev) => ({ ...prev, name: 'New Name' }))

Back to index


133. TanStack Query (React Query)

Theory

TanStack Query manages server state — caching, background refetch, deduplication, stale-while-revalidate. Replaces most manual useEffect fetch logic.

Core: useQuery (read), useMutation (write), QueryClient.

Pros & Cons

ProsCons
Caching, dedup, retry built-inLearning curve
Less boilerplate than useEffectAnother dependency

Interview Answer

TanStack Query handles server state — caching, refetching, loading and error states. I use useQuery for reads and useMutation for writes instead of manual useEffect fetching.

Real Example

jsx
function ProductList() {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['products', category],
    queryFn: () => api.get(`/products?cat=${category}`).then((r) => r.data),
    staleTime: 5 * 60 * 1000,
  })
 
  const mutation = useMutation({
    mutationFn: (product) => api.post('/products', product),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['products'] }),
  })
}

Back to index


134. WebSocket

Theory

WebSocket provides a persistent, full-duplex connection between client and server over a single TCP connection. Unlike HTTP polling, the server can push data to the client instantly.

Use for: live chat, notifications, order tracking, stock tickers, collaborative editing.

Protocol: starts as HTTP upgrade request (ws:// or wss://).

Pros & Cons

WebSocketHTTP Polling
✅ Real-time, low latency✅ Simple, works everywhere
✅ Single connection — efficient❌ Wasted requests when no updates
✅ Bidirectional❌ Higher latency
❌ Connection management complexity❌ Server load at scale
❌ Doesn't work through some proxies

Real-Life Example

tsx
function LiveOrderTracker({ orderId }: { orderId: string }) {
  const [status, setStatus] = useState('preparing')
  const [driverLocation, setDriverLocation] = useState<{
    lat: number
    lng: number
  } | null>(null)
 
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}/live`)
 
    ws.onopen = () => ws.send(JSON.stringify({ type: 'subscribe' }))
 
    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data)
      if (msg.type === 'status_update') setStatus(msg.status)
      if (msg.type === 'location_update') setDriverLocation(msg.location)
    }
 
    ws.onerror = () => setStatus('connection_error')
    ws.onclose = () => console.log('WebSocket closed')
 
    // Reconnect with exponential backoff in production
    return () => ws.close()
  }, [orderId])
 
  return (
    <div>
      <OrderStatusBadge status={status} />
      {driverLocation && <LiveMap location={driverLocation} />}
    </div>
  )
}
javascript
// Reconnection pattern
function connectWebSocket(url, { maxRetries = 5 } = {}) {
  let retries = 0
  let ws
 
  function connect() {
    ws = new WebSocket(url)
    ws.onclose = () => {
      if (retries < maxRetries) {
        setTimeout(connect, Math.min(1000 * 2 ** retries++, 30000))
      }
    }
  }
  connect()
  return () => ws?.close()
}

Back to index


135. WebSockets

Theory

WebSocket provides a persistent, full-duplex TCP connection between client and server over a single handshake. After upgrade from HTTP, both sides can send messages anytime without re-establishing connection.

Protocol: ws:// (dev) / wss:// (prod — always use in production)

Ideal for: live order tracking, chat, stock updates, delivery ETA.

Pros & Cons

ProsCons
Real-time, low latencyStateful — harder to scale
BidirectionalConnection drops need reconnect logic
Less overhead than pollingNot ideal for request/response CRUD
Server can push instantlyProxies/firewalls may block

Interview Answer

WebSocket opens a persistent bidirectional connection — perfect for live order tracking or inventory updates. I always use wss, handle reconnect with exponential backoff, and clean up on unmount.

Real Example

javascript
function useOrderTracking(orderId) {
  const [status, setStatus] = useState(null)
  const wsRef = useRef(null)
 
  useEffect(() => {
    if (!orderId) return
 
    const ws = new WebSocket(`wss://api.a quick-commerce app.com/orders/${orderId}/live`)
    wsRef.current = ws
 
    ws.onopen = () => console.log('Connected to order tracking')
    ws.onmessage = (event) => {
      const update = JSON.parse(event.data)
      setStatus(update) // { status: "out_for_delivery", eta: "8 min" }
    }
    ws.onerror = (err) => console.error('WebSocket error', err)
    ws.onclose = () => console.log('Connection closed')
 
    return () => ws.close()
  }, [orderId])
 
  return status
}

Reconnection Pattern

javascript
function connectWithRetry(url, { maxRetries = 5, baseDelay = 1000 } = {}) {
  let retries = 0
  let ws
 
  function connect() {
    ws = new WebSocket(url)
 
    ws.onopen = () => {
      retries = 0
    }
 
    ws.onclose = () => {
      if (retries < maxRetries) {
        const delay = baseDelay * Math.pow(2, retries)
        retries++
        setTimeout(connect, delay)
      }
    }
  }
 
  connect()
  return () => ws?.close()
}

Back to index


136. What are REST API methods and their differences?

Theory

REST (Representational State Transfer) uses HTTP methods to perform CRUD operations on resources identified by URLs. Each method has defined semantics for safety and idempotency.

Pros & Cons

MethodSafeIdempotentBodyUse Case
GET✅✅NoRead resource
POST❌❌YesCreate resource
PUT❌✅YesReplace resource
PATCH❌❌*YesPartial update
DELETE❌✅OptionalRemove resource

*PATCH idempotency depends on implementation

Real-Life Example

javascript
// Restaurant API for a food delivery app
 
// GET — fetch restaurants (safe, cacheable)
const restaurants = await fetch('/api/restaurants?city=delhi').then((r) => r.json())
 
// POST — create a new order (not idempotent — duplicate = 2 orders)
await fetch('/api/orders', {
  method: 'POST',
  body: JSON.stringify({ restaurantId: 42, items: [{ id: 1, qty: 2 }] }),
})
 
// PUT — replace entire cart
await fetch('/api/cart', {
  method: 'PUT',
  body: JSON.stringify({ items: [{ id: 1, qty: 3 }] }),
})
 
// PATCH — update order status
await fetch('/api/orders/99', {
  method: 'PATCH',
  body: JSON.stringify({ status: 'delivered' }),
})
 
// DELETE — remove item from cart
await fetch('/api/cart/items/5', { method: 'DELETE' })

Back to index


137. What is fetchBaseQuery?

Answer

It is a default base query function built on fetch() for making HTTP requests. It simplifies making REST API calls.

Back to index


138. What is reducerPath in createApi?

Answer

reducerPath is the key in the Redux state where the API slice reducer is mounted. Example: reducerPath: 'userApi'

Back to index


139. What is Axios? How do you use it?

Theory

Axios is a promise-based HTTP client for browsers and Node.js. It simplifies API calls with features like automatic JSON transformation, request/response interceptors, timeout support, and cancellation.

Unlike raw fetch, Axios provides:

  • Automatic JSON parsing
  • Request/response interceptors (auth tokens, error handling)
  • Built-in timeout and cancel token support
  • Better error handling (non-2xx throws by default with config)

Pros & Cons

ProsCons
Clean API, less boilerplate than fetchExtra dependency (~13KB gzipped)
Interceptors for auth and global errorsfetch is native — no install needed
Automatic JSON transformTanStack Query often preferred for data fetching layer
Request cancellation built-inCan hide fetch details from juniors

Real-Life Example

Installation

bash
npm install axios

Basic usage

javascript
import axios from 'axios'
 
// GET
const { data: restaurants } = await axios.get('/api/restaurants', {
  params: { city: 'bangalore', cuisine: 'north-indian' },
})
 
// POST
const { data: order } = await axios.post('/api/orders', {
  restaurantId: 42,
  items: [{ id: 1, quantity: 2 }],
})
 
// PUT / PATCH / DELETE
await axios.put(`/api/cart/${userId}`, { items: updatedCart })
await axios.patch(`/api/orders/${orderId}`, { status: 'cancelled' })
await axios.delete(`/api/cart/items/${itemId}`)

Axios instance with interceptors (production pattern)

javascript
// api/client.js
import axios from 'axios'
 
const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' },
})
 
// Request interceptor — attach auth token
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('access_token')
    if (token) config.headers.Authorization = `Bearer ${token}`
    return config
  },
  (error) => Promise.reject(error),
)
 
// Response interceptor — handle 401 globally
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Redirect to login or refresh token
      window.location.href = '/login'
    }
    if (error.response?.status === 503) {
      toast.error('Service temporarily unavailable')
    }
    return Promise.reject(error)
  },
)
 
export default apiClient

Usage in React component

tsx
import { useEffect, useState } from 'react'
import apiClient from '../api/client'
 
function OrderHistory({ userId }) {
  const [orders, setOrders] = useState([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
 
  useEffect(() => {
    const controller = new AbortController()
 
    apiClient
      .get(`/users/${userId}/orders`, { signal: controller.signal })
      .then((res) => setOrders(res.data))
      .catch((err) => {
        if (!axios.isCancel(err)) setError(err.message)
      })
      .finally(() => setLoading(false))
 
    return () => controller.abort()
  }, [userId])
 
  if (loading) return <Spinner />
  if (error) return <ErrorBanner message={error} />
  return <OrderList orders={orders} />
}

Back to index


140. What is OAuth and how does it work?

Theory

OAuth 2.0 is an authorization framework that lets a user grant a third-party application limited access to their resources without sharing their password. It defines roles: Resource Owner (user), Client (your app), Authorization Server (Google, GitHub), Resource Server (API).

Common flows:

  • Authorization Code (most secure, used by web apps)
  • PKCE (extension for SPAs and mobile)
  • Client Credentials (machine-to-machine)

Pros & Cons

ProsCons
No password sharing with third partiesComplex to implement correctly
Granular scopes (read email, not password)Token management overhead
Industry standardMisconfiguration leads to security holes

Real-Life Example

plaintext
"Sign in with Google" on a food delivery app:
 
1. User clicks "Sign in with Google"
2. App redirects to Google Authorization Server:
   GET https://accounts.google.com/o/oauth2/v2/auth
     ?client_id=YOUR_APP_ID
     &redirect_uri=https://app.com/callback
     &response_type=code
     &scope=email profile
     &state=random_csrf_token
 
3. User logs in and grants permission on Google's page
4. Google redirects back:
   https://app.com/callback?code=AUTH_CODE&state=random_csrf_token
 
5. Backend exchanges code for tokens (server-side, never in browser):
   POST https://oauth2.googleapis.com/token
     code=AUTH_CODE&client_secret=SECRET&grant_type=authorization_code
 
6. Backend receives: { access_token, refresh_token, id_token }
7. Backend creates session / JWT for the user in your app

Back to index


141. You need to implement server-side caching to reduce API calls. How would you approach it?

Description

Repeated identical upstream API calls waste latency and cost. Next.js offers multiple cache layers — choosing the right one depends on how often data changes and whether it's per-user or global.

Theory

LayerMechanismScope
Request memoizationSame fetch in one render treeSingle request
Data Cachefetch with cache / revalidateCross-request, persistent
Full Route CacheStatic pages at build timeStatic routes
Router CacheClient-side RSC payload cacheNavigation
Redis / CDNExternal cache in Route Handler or BFFProduction scale
revalidate options
tsx
fetch(url, { cache: 'force-cache' }) // default — cache until invalidated
fetch(url, { next: { revalidate: 60 } }) // ISR — refresh every 60s
fetch(url, { cache: 'no-store' }) // never cache
fetch(url, { next: { tags: ['products'] } }) // on-demand invalidation

Pros & Cons

Cached readsAlways fresh (no-store)
✅ Lower latency, fewer API bills✅ Simplest mental model
✅ Survives traffic spikes❌ Upstream overload at scale
❌ Stale data risk❌ Slower every request

Real Example — Product catalog BFF

tsx
// lib/products.ts
export async function getProducts() {
  const res = await fetch(`${process.env.CATALOG_API}/products`, {
    next: { revalidate: 300, tags: ['products'] }, // 5 min ISR
  })
 
  if (!res.ok) throw new Error('Catalog unavailable')
  return res.json()
}
tsx
// app/api/revalidate/route.ts — on-demand after CMS publish
import { revalidateTag } from 'next/cache'
import { NextRequest } from 'next/server'
 
export async function POST(req: NextRequest) {
  const secret = req.nextUrl.searchParams.get('secret')
  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ error: 'Invalid secret' }, { status: 401 })
  }
 
  revalidateTag('products')
  return Response.json({ revalidated: true })
}
tsx
// Redis layer for hot per-user data (Route Handler)
// app/api/dashboard/route.ts
import { Redis } from '@upstash/redis'
 
const redis = Redis.fromEnv()
 
export async function GET(req: Request) {
  const userId = await getUserId(req)
  const cacheKey = `dashboard:${userId}`
 
  const cached = await redis.get(cacheKey)
  if (cached) return Response.json(cached)
 
  const data = await fetchDashboardFromUpstream(userId)
  await redis.setex(cacheKey, 60, data) // 60s TTL
 
  return Response.json(data)
}

Interview Answer

I'd use Next.js Data Cache with revalidate and cache tags for shared data, no-store only for user-specific or real-time data, and add Redis at the Route Handler layer when upstream rate limits or cross-instance consistency require it — with webhooks calling revalidateTag on content updates.

Back to index


Performance

142. A page contains a heavy chart library that is only needed after user interaction. How would you optimize the loading strategy?

Description

Chart libraries (Recharts, Chart.js, D3) can add 100–300+ KB to the client bundle. If the chart appears only after "View analytics" or tab click, it should not load on initial page visit.

Theory

StrategyUse case
next/dynamic + ssr: falseChart needs window; load on mount
Dynamic import on clickUser must click before download starts
Server Component for dataFetch aggregates on server; pass props to chart island
Suspense fallbackSkeleton while chunk loads

Pros & Cons

Load on interactionBundle in main page
✅ Smaller initial JS❌ Slower first load
✅ Better INP / LCP❌ Wasted bytes if user never opens chart
❌ Brief delay on first open❌ Mobile users suffer

Real Example — Analytics tab with Recharts

tsx
// app/sales/page.tsx — Server Component
import { SalesSummary } from './sales-summary'
import { SalesChartLoader } from './sales-chart-loader'
 
export default async function SalesPage() {
  const summary = await getSalesSummary() // server — no chart lib
 
  return (
    <main>
      <SalesSummary data={summary} />
      <SalesChartLoader chartData={summary.byMonth} />
    </main>
  )
}
tsx
// app/sales/sales-chart-loader.tsx
'use client'
 
import { useState } from 'react'
import dynamic from 'next/dynamic'
 
const SalesChart = dynamic(() => import('./sales-chart'), {
  ssr: false,
  loading: () => <div className="h-64 animate-pulse bg-gray-100" />,
})
 
export function SalesChartLoader({ chartData }) {
  const [showChart, setShowChart] = useState(false)
 
  if (!showChart) {
    return (
      <button type="button" onClick={() => setShowChart(true)} className="btn-primary">
        View chart
      </button>
    )
  }
 
  return <SalesChart data={chartData} />
}
tsx
// app/sales/sales-chart.tsx — only loaded after button click
'use client'
 
import { LineChart, Line, XAxis, YAxis, ResponsiveContainer } from 'recharts'
 
export default function SalesChart({ data }) {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <LineChart data={data}>
        <XAxis dataKey="month" />
        <YAxis />
        <Line type="monotone" dataKey="revenue" stroke="#2563eb" />
      </LineChart>
    </ResponsiveContainer>
  )
}

Interview Answer

I'd keep data fetching in a Server Component, load the chart library with next/dynamic and ssr: false, and defer the import until the user clicks — so the heavy dependency never hits the initial bundle.

Back to index


143. Bundle Size Optimization

Theory

Smaller bundles = faster download, parse, and execute. Target < 200 KB gzipped for initial JS.

Strategies: code splitting, tree shaking, analyze bundle, replace heavy libraries, dynamic imports, remove unused code.

Pros & Cons

Optimized bundleLarge bundle
✅ Faster TTI and LCP❌ Slow on 3G/4G
✅ Better mobile experience❌ Higher bounce rate
✅ Lower CDN costs❌ Poor Lighthouse scores

Real-Life Example

bash
npx vite-bundle-visualizer
npx source-map-explorer dist/assets/*.js
 
# Find heavy imports
npm install -D webpack-bundle-analyzer
javascript
// ❌ Imports entire library (70KB)
import _ from 'lodash'
_.debounce(fn, 300)
 
// ✅ Import only what you need (2KB)
import debounce from 'lodash/debounce'
 
// ❌ Import all icons
import * as Icons from 'lucide-react'
 
// ✅ Import specific icon
import { Search, Cart } from 'lucide-react'
 
// Replace moment.js (300KB) with date-fns (tree-shakeable)
import { format } from 'date-fns'
TechniqueSavings
Route code splitting40–70% initial bundle reduction
lodash → lodash-es + per-method import~65 KB
moment → date-fns~280 KB
Remove unused dependenciesVaries

Back to index


144. Closure memory leak and fix

Theory

A memory leak occurs when JavaScript retains references to objects that are no longer needed. Closures are a common cause — an inner function holds references to outer variables, preventing garbage collection of large objects even after the outer function returns.

Pros & Cons

Awareness of closure leaksIgnorance
✅ Clean up timers, listeners, refs❌ App slows over time
✅ Pass only needed data into closures❌ Detached DOM nodes stay in memory
✅ Use WeakMap for DOM metadata

Real-Life Example

javascript
// ❌ LEAK — closure holds entire dataset forever
function createTransactionHandler(transactions) {
  // transactions = 50MB array
  return function handleClick(id) {
    const txn = transactions.find((t) => t.id === id)
    console.log(txn.amount)
  }
}
 
const handler = createTransactionHandler(hugeTransactionList)
button.addEventListener('click', handler)
// Even if button removed, handler + hugeTransactionList stay in memory
 
// ✅ FIX 1 — pass only what's needed
function createTransactionHandler(transactionMap) {
  return function handleClick(id) {
    console.log(transactionMap.get(id)?.amount)
  }
}
 
// ✅ FIX 2 — remove listener on cleanup
function setupHandler(button, transactions) {
  const handler = (e) => {
    const id = e.target.dataset.id
    console.log(transactions.find((t) => t.id === id)?.amount)
  }
  button.addEventListener('click', handler)
  return () => button.removeEventListener('click', handler)
}
 
// ✅ FIX 3 — WeakMap for DOM-associated data (GC when element removed)
const txnMetadata = new WeakMap()
function attachTransaction(el, data) {
  txnMetadata.set(el, { id: data.id, amount: data.amount })
  // When el is GC'd, WeakMap entry is GC'd too
}
javascript
// ❌ LEAK — interval in closure
function startPolling(userId) {
  const cache = loadMassiveUserCache(userId)
  setInterval(() => {
    updateUI(cache.lastBalance)
  }, 1000)
  // Never cleared — cache lives forever
}
 
// ✅ FIX
function startPolling(userId) {
  let cache = loadMassiveUserCache(userId)
  const intervalId = setInterval(() => updateUI(cache.lastBalance), 1000)
 
  return () => {
    clearInterval(intervalId)
    cache = null // release reference
  }
}

Back to index


145. Code Splitting

Theory

Code splitting breaks your JavaScript bundle into smaller chunks loaded on demand. The bundler (Webpack, Vite) creates separate files per dynamic import or entry point.

Split strategies:

  • Route-based — each page is a chunk (biggest win)
  • Component-based — heavy widgets loaded when needed
  • Vendor splitting — separate chunk for node_modules

Pros & Cons

ProsCons
Smaller initial bundleMore HTTP requests (mitigated by HTTP/2)
Parallel chunk loadingRequires Suspense/error boundaries
Cache-friendly (vendor chunk stable)Complexity in configuration

Real-Life Example

tsx
// Route-based splitting (automatic with lazy)
const routes = [
  { path: '/', component: lazy(() => import('./Home')) },
  { path: '/dashboard', component: lazy(() => import('./Dashboard')) },
]
 
// Webpack magic comment — named chunk
const HeavyChart = lazy(() => import(/* webpackChunkName: "charts" */ './HeavyChart'))
 
// Vite — automatic code splitting on dynamic import
const module = await import('./heavy-module.js')
javascript
// webpack.config.js — split vendor
optimization: {
  splitChunks: {
    chunks: "all",
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: "vendors",
        chunks: "all",
      },
    },
  },
},

Back to index


146. Code Splitting & Lazy Loading

Theory

Code splitting breaks the bundle into chunks loaded on demand. React.lazy(() => import("./Page")) dynamically imports a component.

Always pair with <Suspense fallback={...}>.

Interview Answer

Code splitting loads routes and heavy components on demand. React.lazy plus Suspense reduces initial bundle size and improves first load.

Real Example

jsx
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
 
function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

Back to index


147. Core Web Vitals (LCP, INP, CLS)

Theory

Google's Core Web Vitals measure real-user experience:

MetricMeasuresGood
LCP (Largest Contentful Paint)Loading — largest visible element≤ 2.5s
INP (Interaction to Next Paint)Responsiveness (replaced FID)≤ 200ms
CLS (Cumulative Layout Shift)Visual stability≤ 0.1

Pros & Cons

Monitoring Web VitalsIgnoring them
✅ SEO ranking factor❌ Higher bounce rates
✅ Data-driven optimization❌ Poor mobile UX
✅ RUM catches real user issues❌ Lighthouse lab scores ≠ field data

Real-Life Example

javascript
// Measure in production
import { onLCP, onINP, onCLS } from 'web-vitals'
 
function sendToAnalytics({ name, value, id }) {
  gtag('event', name, { value: Math.round(value), event_id: id })
}
 
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
tsx
// LCP — preload hero image
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<img src="/hero.webp" width={1200} height={600} alt="Hero" fetchpriority="high" />
 
// CLS — reserve space
.product-card img { aspect-ratio: 4/3; width: 100%; }
 
// INP — defer heavy work
import { useTransition } from "react";
const [isPending, startTransition] = useTransition();
startTransition(() => setFilteredProducts(heavyFilter(allProducts)));

Back to index


148. Debounce and Throttle implementation

Debounce — execute after pause in events

Waits until the user stops triggering for delay ms, then runs once.

javascript
function debounce(fn, delay) {
  let timerId
 
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
}
 
// Usage: search input — API call only after user stops typing
const search = debounce((query) => {
  console.log('Searching:', query)
}, 300)

Throttle — execute at most once per interval

Runs immediately, then ignores subsequent calls for limit ms.

javascript
function throttle(fn, limit) {
  let inThrottle = false
 
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}
 
// Usage: scroll handler — fire at most every 100ms
const onScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY)
}, 100)

Leading + trailing debounce (advanced)

javascript
function debounce(fn, delay, { leading = false, trailing = true } = {}) {
  let timerId
  let lastArgs
  let lastThis
 
  return function (...args) {
    lastArgs = args
    lastThis = this
 
    const callNow = leading && !timerId
 
    clearTimeout(timerId)
    timerId = setTimeout(() => {
      timerId = null
      if (trailing && !callNow) fn.apply(lastThis, lastArgs)
    }, delay)
 
    if (callNow) fn.apply(this, args)
  }
}

When to use which

PatternUse case
DebounceSearch input, resize end, form validation
ThrottleScroll, mouse move, button spam prevention

Back to index


149. Debounce vs Throttle

Theory

Both control execution frequency of functions during rapid events:

  • Debounce — wait until activity stops, then execute once
  • Throttle — execute at most once per time window during continuous activity

Pros & Cons

DebounceThrottle
✅ Minimizes executions (API cost)✅ Regular updates during scroll
✅ Great for search, auto-save✅ Immediate first response
❌ Delays until pause❌ May miss final event
❌ More calls than debounce

Real-Life Example

javascript
function debounce(fn, delay) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
}
 
function throttle(fn, limit) {
  let inThrottle = false
  return (...args) => {
    if (!inThrottle) {
      fn(...args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}
 
// Debounce — restaurant search
const search = debounce((q) => {
  fetch(`/api/restaurants?q=${q}`).then(renderResults)
}, 300)
 
// Throttle — infinite scroll position tracker
const onScroll = throttle(() => {
  if (nearBottom()) loadMoreItems()
}, 200)
 
// Throttle — prevent double-submit on payment button
const handlePay = throttle(() => processPayment(), 2000)
ScenarioUse
Search inputDebounce
Window resize (layout recalc)Debounce
Scroll handlerThrottle
Mouse move / dragThrottle
Auto-save formDebounce
API rate limitingThrottle

Back to index


150. Debouncing

Theory

Debouncing delays execution until the user stops an action for a specified time. Only the last call in a burst runs.

Common use: search input — wait until user stops typing before API call.

Pros & Cons

ProsCons
Reduces API calls / CPU workSlight delay in response
Better UX for searchWrong delay feels laggy
Prevents rate-limit errorsNeeds cleanup on unmount

Debounce vs Throttle

DebounceThrottle
RunsAfter pauseOnce per interval
Use forSearch, auto-saveScroll, resize

Interview Answer

Debouncing waits until the user stops typing before running the function — I use it on search inputs to avoid firing an API call on every keystroke.

Debounce Utility

javascript
function debounce(fn, delay = 300) {
  let timer
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

Custom useDebounce Hook

jsx
function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value)
 
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])
 
  return debouncedValue
}

Search with Debounce + useFetch

jsx
function ProductSearch() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 400)
 
  const { data, loading } = useFetch(
    debouncedQuery ? `https://api.example.com/products?q=${encodeURIComponent(debouncedQuery)}` : null,
    { enabled: debouncedQuery.length >= 2 },
  )
 
  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products..."
        aria-label="Search products"
      />
      {loading && <span>Searching...</span>}
      <ProductList products={data ?? []} />
    </>
  )
}

Debounced Callback Hook

jsx
function useDebouncedCallback(callback, delay = 300) {
  const callbackRef = useRef(callback)
  const timerRef = useRef(null)
 
  useEffect(() => {
    callbackRef.current = callback
  }, [callback])
 
  useEffect(() => () => clearTimeout(timerRef.current), [])
 
  return useCallback(
    (...args) => {
      clearTimeout(timerRef.current)
      timerRef.current = setTimeout(() => callbackRef.current(...args), delay)
    },
    [delay],
  )
}
 
// Usage
const debouncedSave = useDebouncedCallback((text) => saveToApi(text), 500)
;<input onChange={(e) => debouncedSave(e.target.value)} />
#TopicOne-liner
1CounteruseState + functional updaters, optional min/max
2useFetchuseState + useEffect + AbortController cleanup
3Merge objectsreduce + spread (shallow) or recursive deepMerge
4useDeferredValueDefer heavy re-renders; input stays responsive
5Context APIcreateContext → Provider → useContext
6Custom hooksReuse stateful logic; name with use
7DebouncingWait for pause before executing — search inputs

the company Interview Strategy

Coding round

  1. Counter — start simple, add bounds/accessibility if time allows
  2. useFetch — always mention AbortController cleanup
  3. Merge objects — clarify shallow vs deep with interviewer before coding

Fundamentals round

  • Connect useDeferredValue with debouncing — both optimize search UX differently
  • Explain Context vs custom hooks — Context shares data; hooks share logic
  • Mention TanStack Query as production upgrade over raw useFetch

the company's 1st round balances hands-on React coding with modern hook knowledge — practice building counters and hooks from scratch without copy-paste templates.

Back to index


151. Debouncing implementation

Theory

Debouncing delays execution of a function until after a specified pause in triggering events. If the event fires again before the delay expires, the timer resets. The function runs only once the user stops the action.

Classic use: search-as-you-type, resize-end handlers, auto-save on form changes.

Pros & Cons

ProsCons
Reduces API calls dramaticallyAdds perceived latency (waits for pause)
Prevents expensive work on every keystrokeCan feel unresponsive if delay is too long
Simple to implementMay skip intermediate states user expected to see

Real-Life Example

javascript
function debounce(fn, delay) {
  let timerId
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
}
 
// Real-life: Restaurant search on a food-delivery platform-style app
const searchRestaurants = debounce(async (query) => {
  const res = await fetch(`/api/restaurants?q=${encodeURIComponent(query)}`)
  const data = await res.json()
  renderResults(data)
}, 300)
 
// User types "pizza" → only 1 API call after they stop typing for 300ms
document.getElementById('search').addEventListener('input', (e) => {
  searchRestaurants(e.target.value)
})

Back to index


152. Design & Implement a Virtualized Infinite Feed

Description

the organization machine coding ask: Build a feed (like Twitter/LinkedIn) that loads more items on scroll without rendering 10,000 DOM nodes.

Theory

ApproachIdea
Windowing / virtualizationOnly render visible rows + overscan buffer
Infinite scrollFetch next page when sentinel enters viewport
Intersection ObserverDetect when sentinel is visible (no scroll event spam)

Libraries: @tanstack/react-virtual, react-window, react-virtuoso.

Pros & Cons

Virtualized listRender all items
✅ Constant DOM size✅ Simpler code
✅ Smooth scroll on 100K items❌ Memory + paint cost explodes
❌ Variable row height is harder❌ Mobile browsers crash

Real Example — Virtualized infinite feed

tsx
'use client'
 
import { useRef, useCallback, useEffect } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { useInfiniteQuery } from '@tanstack/react-query'
 
type FeedItem = { id: string; author: string; text: string }
 
async function fetchFeedPage({ pageParam = 0 }) {
  const res = await fetch(`/api/feed?cursor=${pageParam}`)
  return res.json() as Promise<{
    items: FeedItem[]
    nextCursor: number | null
  }>
}
 
export function VirtualizedFeed() {
  const parentRef = useRef<HTMLDivElement>(null)
  const sentinelRef = useRef<HTMLDivElement>(null)
 
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useInfiniteQuery({
    queryKey: ['feed'],
    queryFn: fetchFeedPage,
    getNextPageParam: (last) => last.nextCursor ?? undefined,
  })
 
  const items = data?.pages.flatMap((p) => p.items) ?? []
 
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 120,
    overscan: 5,
  })
 
  // Infinite load when sentinel visible
  const onIntersect = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) {
        fetchNextPage()
      }
    },
    [fetchNextPage, hasNextPage, isFetchingNextPage],
  )
 
  useEffect(() => {
    const el = sentinelRef.current
    if (!el) return
 
    const observer = new IntersectionObserver(onIntersect, {
      rootMargin: '200px',
    })
    observer.observe(el)
    return () => observer.disconnect()
  }, [onIntersect])
 
  if (status === 'pending') return <FeedSkeleton />
 
  return (
    <div ref={parentRef} className="h-[80vh] overflow-auto">
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: 'relative',
        }}>
        {virtualizer.getVirtualItems().map((row) => {
          const item = items[row.index]
          return (
            <article
              key={item.id}
              className="feed-card"
              style={{
                position: 'absolute',
                top: 0,
                transform: `translateY(${row.start}px)`,
                height: `${row.size}px`,
              }}>
              <strong>{item.author}</strong>
              <p>{item.text}</p>
            </article>
          )
        })}
      </div>
      <div ref={sentinelRef} aria-hidden className="h-1" />
      {isFetchingNextPage && <p>Loading more…</p>}
    </div>
  )
}
What to explain while coding
  1. Virtualizer computes visible range from scroll offset
  2. Only ~15–20 DOM nodes exist regardless of total items
  3. Sentinel + Intersection Observer triggers fetchNextPage
  4. key={item.id} for stable reconciliation
  5. overscan reduces blank flashes on fast scroll

Interview Answer

I'd virtualize the list with TanStack Virtual so only visible rows mount, use React Query infinite queries for pagination, and Intersection Observer on a sentinel to load more — keeping DOM size constant and scroll performant on large feeds.

Back to index


153. Explain code splitting and lazy loading

Theory

Code splitting breaks your bundle into smaller chunks loaded on demand. Lazy loading defers loading a chunk until it's needed (route, modal, heavy component).

Route-based splitting

jsx
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
 
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
 
function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

Component-level splitting

jsx
const HeavyChart = lazy(() => import('./HeavyChart'))
 
function Analytics() {
  const [show, setShow] = useState(false)
 
  return (
    <>
      <button onClick={() => setShow(true)}>Show Chart</button>
      {show && (
        <Suspense fallback={<Spinner />}>
          <HeavyChart />
        </Suspense>
      )}
    </>
  )
}

Webpack / Vite magic comments

javascript
const Admin = lazy(() => import(/* webpackChunkName: "admin" */ './Admin'))

Best practices

  • Split by route first (biggest wins)
  • Prefetch on hover: import("./Dashboard") in onMouseEnter
  • Always wrap lazy components in <Suspense>
  • Monitor bundle size with webpack-bundle-analyzer or rollup-plugin-visualizer

Back to index


154. How does memoization work in React?

Theory

Memoization caches the result of a computation or component render and returns the cached version when inputs haven't changed. React provides three levels:

ToolCachesSkips
useMemoComputed valueRe-computation
useCallbackFunction referenceNew function creation
React.memoComponent renderRe-render if props unchanged (shallow compare)

React compares previous and next props/state using Object.is (shallow equality).

Pros & Cons

ProsCons
Avoids expensive recalculationsMemory overhead for cached values
Prevents child re-rendersShallow compare misses deep object changes
Profile-guided optimizationOver-memoization adds complexity for no gain

Real-Life Example

jsx
function OrderSummary({ orders, taxRate }) {
  // useMemo — expensive aggregation
  const total = useMemo(() => {
    return orders.reduce((sum, o) => sum + o.amount, 0)
  }, [orders])
 
  const tax = useMemo(() => total * taxRate, [total, taxRate])
 
  return <div>Total: ₹{total + tax}</div>
}
 
const OrderRow = React.memo(function OrderRow({ order, onCancel }) {
  return (
    <div>
      {order.name} — ₹{order.amount}
      <button onClick={() => onCancel(order.id)}>Cancel</button>
    </div>
  )
})
 
function OrderList({ orders }) {
  const handleCancel = useCallback((id) => {
    cancelOrder(id)
  }, []) // stable reference → OrderRow won't re-render unnecessarily
 
  return orders.map((o) => <OrderRow key={o.id} order={o} onCancel={handleCancel} />)
}

Back to index


155. How to optimize performance?

Answer

  • Use React.memo, useMemo, and useCallback
  • Avoid unnecessary re-renders
  • Use lazy loading and code splitting
  • Optimize rendering of large lists with virtualization

Back to index


156. How would you implement infinite scrolling efficiently?

Theory — Core techniques

TechniquePurpose
Intersection ObserverDetect when sentinel enters viewport (no scroll event spam)
VirtualizationRender only visible rows (@tanstack/react-virtual)
Cursor-based paginationStable pagination for large datasets (?cursor=abc&limit=20)
Request deduplicationPrevent duplicate fetches on fast scroll
Prefetch next pageLoad before user reaches bottom
Skeleton / placeholderPerceived performance

Practical Example — Intersection Observer hook

tsx
import { useCallback, useEffect, useRef } from 'react'
 
function useInfiniteScroll(onLoadMore: () => void, hasMore: boolean) {
  const observerRef = useRef<IntersectionObserver | null>(null)
 
  const sentinelRef = useCallback(
    (node: HTMLDivElement | null) => {
      if (observerRef.current) observerRef.current.disconnect()
      if (!node || !hasMore) return
 
      observerRef.current = new IntersectionObserver(
        ([entry]) => {
          if (entry.isIntersecting) onLoadMore()
        },
        { rootMargin: '200px' }, // prefetch before visible
      )
 
      observerRef.current.observe(node)
    },
    [onLoadMore, hasMore],
  )
 
  useEffect(() => () => observerRef.current?.disconnect(), [])
 
  return sentinelRef
}

Practical Example — With TanStack Query

tsx
import { useInfiniteQuery } from '@tanstack/react-query'
 
function useRestaurants() {
  return useInfiniteQuery({
    queryKey: ['restaurants'],
    queryFn: ({ pageParam }) => fetch(`/api/restaurants?cursor=${pageParam ?? ''}`).then((r) => r.json()),
    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
    initialPageParam: undefined as string | undefined,
  })
}
 
function RestaurantFeed() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useRestaurants()
  const sentinelRef = useInfiniteScroll(fetchNextPage, !!hasNextPage)
 
  const items = data?.pages.flatMap((p) => p.items) ?? []
 
  return (
    <div>
      {items.map((r) => (
        <RestaurantCard key={r.id} restaurant={r} />
      ))}
      <div ref={sentinelRef} />
      {isFetchingNextPage && <Spinner />}
    </div>
  )
}

Virtualization for 10k+ items

tsx
import { useVirtualizer } from '@tanstack/react-virtual'
 
function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null)
 
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 80,
    overscan: 5,
  })
 
  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((row) => (
          <div
            key={row.key}
            style={{
              position: 'absolute',
              top: row.start,
              height: row.size,
              width: '100%',
            }}>
            <RestaurantCard restaurant={items[row.index]} />
          </div>
        ))}
      </div>
    </div>
  )
}

Interview answer highlights

Performance

Use cursor pagination, Intersection Observer with rootMargin prefetch, TanStack Query for cache/dedup, and virtualization when lists exceed a few hundred DOM nodes. Always key by stable IDs and preserve scroll position on back navigation.

Back to index


157. How would you optimize a React application with thousands of rows?

Strategy checklist

TechniquePurpose
VirtualizationRender only visible rows (~20 instead of 10,000)
Stable keysPrevent unnecessary unmount/remount
MemoizationReact.memo on row components
Pagination / infinite scrollLoad data in chunks
Web WorkersOffload sorting/filtering from main thread
Avoid inline objects/functionsPrevent memo breakage

Practical Example — Virtual list with TanStack Virtual

tsx
import { useRef } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
 
function VirtualTable({ rows }) {
  const parentRef = useRef(null)
 
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 48,
    overscan: 10,
  })
 
  return (
    <div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualRow) => {
          const row = rows[virtualRow.index]
          return (
            <div
              key={row.id}
              style={{
                position: 'absolute',
                top: virtualRow.start,
                height: virtualRow.size,
                width: '100%',
              }}>
              <Row item={row} />
            </div>
          )
        })}
      </div>
    </div>
  )
}
 
const Row = React.memo(function Row({ item }) {
  return <div className="row">{item.name}</div>
})

Additional optimizations

tsx
// Debounce search filter
const debouncedFilter = useDeferredValue(searchQuery)
 
// Sort in worker or server, not on every keystroke in main thread
const filtered = useMemo(() => rows.filter((r) => r.name.includes(debouncedFilter)), [rows, debouncedFilter])

Interview answer

Note

For thousands of rows, virtualization is non-negotiable — only mount visible DOM nodes. Combine with cursor pagination, memoized row components, useDeferredValue for filters, and optionally Web Workers for heavy transforms. Measure with React DevTools Profiler before optimizing.

Back to index


158. Image Optimization (WebP, AVIF)

Theory

Images are often the largest page weight. Modern formats compress better than JPEG/PNG:

Formatvs JPEGBrowser support
WebP~25–35% smallerUniversal (2024+)
AVIF~50% smallerChrome, Firefox, Safari 16+
JPEGBaselineUniversal

Also use: responsive srcset, lazy loading, explicit dimensions (CLS), CDN image optimization.

Pros & Cons

Modern formats (WebP/AVIF)Legacy JPEG/PNG only
✅ 25–50% smaller files❌ Slower LCP
✅ Better quality at same size❌ Higher bandwidth costs
❌ Need fallback for old browsers

Real-Life Example

html
<!-- Picture element — AVIF → WebP → JPEG fallback -->
<picture>
  <source srcset="/images/hero.avif" type="image/avif" />
  <source srcset="/images/hero.webp" type="image/webp" />
  <img src="/images/hero.jpg" alt="Team collaboration" width="1200" height="600" loading="lazy" decoding="async" />
</picture>
 
<!-- Responsive srcset -->
<img
  src="/product-400.webp"
  srcset="/product-400.webp 400w, /product-800.webp 800w, /product-1200.webp 1200w"
  sizes="(max-width: 600px) 100vw, 400px"
  alt="Product"
  width="400"
  height="300" />
tsx
// Next.js Image — automatic optimization
import Image from 'next/image'
 
;<Image
  src="/product.jpg"
  alt="Product"
  width={400}
  height={300}
  placeholder="blur"
  blurDataURL={blurDataUrl}
  priority={isAboveFold}
/>

Back to index


159. Infinite Scroll

Theory

Infinite scroll automatically loads the next batch of data when the user scrolls near the bottom of the page. Typically uses Intersection Observer to detect when a sentinel element enters the viewport.

Combine with virtualization when lists exceed a few hundred DOM nodes.

Pros & Cons

ProsCons
Seamless UX for feedsHard to access footer
No pagination clicksDOM grows without virtualization
Great for mobileBack-button scroll position issues
Prefetch before bottom reachedAccessibility concerns (no clear end)

Real-Life Example

tsx
import { useInfiniteQuery } from '@tanstack/react-query'
import { useRef, useCallback } from 'react'
 
function useInfiniteScroll(onLoadMore: () => void, hasMore: boolean) {
  const observer = useRef<IntersectionObserver | null>(null)
  const sentinelRef = useCallback(
    (node: HTMLDivElement | null) => {
      if (observer.current) observer.current.disconnect()
      if (!node || !hasMore) return
      observer.current = new IntersectionObserver(
        ([entry]) => entry.isIntersecting && onLoadMore(),
        { rootMargin: '200px' }, // prefetch before visible
      )
      observer.current.observe(node)
    },
    [onLoadMore, hasMore],
  )
  return sentinelRef
}
 
function Feed() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
    queryKey: ['feed'],
    queryFn: ({ pageParam }) => fetch(`/api/feed?cursor=${pageParam ?? ''}`).then((r) => r.json()),
    getNextPageParam: (last) => last.nextCursor ?? undefined,
    initialPageParam: undefined as string | undefined,
  })
 
  const items = data?.pages.flatMap((p) => p.items) ?? []
  const sentinelRef = useInfiniteScroll(fetchNextPage, !!hasNextPage)
 
  return (
    <div>
      {items.map((item) => (
        <FeedCard key={item.id} item={item} />
      ))}
      <div ref={sentinelRef} aria-hidden="true" />
      {isFetchingNextPage && <Spinner />}
    </div>
  )
}

Back to index


160. Intersection Observer & Rendering Optimizations

Theory

Intersection Observer asynchronously reports when an element enters/leaves a viewport (or scroll container). Better than scroll listeners — no main-thread firing on every pixel.

Rendering optimizations checklist
TechniqueWhen
React.memoPure child re-renders from parent
useMemo / useCallbackExpensive compute / stable callbacks to memoized children
Code splittingReact.lazy + Suspense
VirtualizationLong lists
Intersection ObserverLazy images, infinite scroll, analytics impressions
content-visibility: autoCSS native offscreen skip (modern browsers)
Debounce resize handlersLayout-heavy components

Real Example — Lazy image + impression tracking

tsx
function LazyFeedImage({ src, alt }: { src: string; alt: string }) {
  const imgRef = useRef<HTMLImageElement>(null)
  const [loaded, setLoaded] = useState(false)
 
  useEffect(() => {
    const el = imgRef.current
    if (!el) return
 
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          el.src = src
          setLoaded(true)
          observer.disconnect()
        }
      },
      { rootMargin: '100px' },
    )
 
    observer.observe(el)
    return () => observer.disconnect()
  }, [src])
 
  return <img ref={imgRef} alt={alt} className={loaded ? 'opacity-100' : 'opacity-0'} width={400} height={225} />
}
tsx
// Memoize heavy child
const ChartPanel = React.memo(function ChartPanel({ data }: { data: Point[] }) {
  return <ExpensiveChart data={data} />
})
 
function Dashboard({ raw }: { raw: RawRow[] }) {
  const chartData = useMemo(() => aggregate(raw), [raw])
  return <ChartPanel data={chartData} />
}

Interview Answer

I use Intersection Observer for lazy loading and infinite scroll instead of scroll listeners, combine virtualization for long lists, and apply React.memo / useMemo only where profiling shows unnecessary rerenders — not everywhere by default.

Behavioral round — use STAR (Situation, Task, Action, Result). Keep answers 2–3 minutes.

Back to index


161. Lazy Loading

Theory

Lazy loading defers loading resources until needed. In React: React.lazy() + <Suspense> for components. For images: loading="lazy".

Reduces initial bundle size — users download only what they visit.

Interview Answer

Lazy loading defers code until needed — React.lazy for route components with Suspense fallback, and loading="lazy" for images below the fold.

Real Example

tsx
import { lazy, Suspense } from "react";
 
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Reports = lazy(() => import("./pages/Reports"));
const Admin = lazy(() => import("./pages/Admin"));
 
function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/reports" element={<Reports />} />
        <Route path="/admin" element={<Admin />} />
      </Routes>
    </Suspense>
  );
}
 
// Prefetch on hover
<Link to="/reports" onMouseEnter={() => import("./pages/Reports")}>
  Reports
</Link>
 
// Image lazy load
<img src="/chart.png" alt="Analytics" loading="lazy" width={800} height={400} />
#TopicOne-liner
1Key skillsReact, TS, Redux, auth, testing, mentoring
2Why ReactComponents, ecosystem, hooks, job market
3Last project roleSTAR — ownership, impact, metrics
4Auth in ReacthttpOnly cookies, AuthProvider, protected routes
5session vs localTab-scoped vs persistent; never for tokens
6Context APISplit by concern, memoize value
7Auth deep diveLogin → cookie → refresh → RBAC
8React 19 featuresActions, useOptimistic, use hook, no forwardRef
9addEventListenerRegister, capture/bubble, cleanup in useEffect
10useLayoutEffectBefore paint — DOM measure, no flicker
11Debounce/throttlePause vs rate-limit
12Event loopSync → micro → macro
13useStateLocal state + setter, functional updates
14useEffectSide effects + cleanup + deps
15Reduxdispatch → reducer → store
16Redux perfcreateSelector, normalize, RTK Query
17Lazy loadingReact.lazy + Suspense

Engineo tests senior breadth — auth, Redux, hooks, and JS fundamentals in one interview. Prepare short confident answers with one example each.

Back to index


162. Memory Management

Theory

JavaScript uses automatic garbage collection — memory for unreachable objects is reclaimed. The primary algorithm is mark-and-sweep: GC marks all objects reachable from roots (global, call stack, closures), then frees unmarked objects.

V8 uses generational GC: young generation (short-lived objects, frequent Scavenge) and old generation (long-lived, Mark-Sweep-Compact).

Common memory leak causes

  1. Global variables holding references
  2. Forgotten timers and intervals
  3. Event listeners not removed
  4. Closures holding large objects
  5. Detached DOM nodes still referenced in JS
  6. Unbounded caches

Pros & Cons

Automatic GCManual memory management (C/C++)
✅ Developer doesn't free memory✅ Predictable memory usage
❌ GC pauses can affect performance❌ Manual errors (leaks, dangling pointers)
❌ Leaks still possible via references

Real-Life Example

javascript
// ❌ Memory leak — global cache grows forever
const cache = {}
function fetchUser(id) {
  if (!cache[id]) cache[id] = expensiveFetch(id)
  return cache[id]
}
 
// ✅ Bounded cache with LRU or WeakMap
const cache = new Map()
const MAX_SIZE = 100
function fetchUserCached(id) {
  if (cache.has(id)) return cache.get(id)
  const result = expensiveFetch(id)
  if (cache.size >= MAX_SIZE) cache.delete(cache.keys().next().value)
  cache.set(id, result)
  return result
}
 
// ❌ Leak — closure holds large data
function setupHandler() {
  const hugeData = new Array(1e6).fill('x')
  return () => console.log(hugeData.length) // hugeData never freed
}
 
// ✅ Cleanup pattern
function setupPolling(callback) {
  const intervalId = setInterval(callback, 5000)
  return () => clearInterval(intervalId) // caller cleans up
}
 
// WeakMap — doesn't prevent GC of keys
const domMetadata = new WeakMap()
function attachMetadata(element, data) {
  domMetadata.set(element, data)
  // When element is removed from DOM and unreferenced, entry is GC'd
}

Back to index


163. Pagination

Theory

Pagination divides large datasets into discrete pages, fetching and displaying one chunk at a time. Users navigate with page numbers, next/previous buttons, or "load more."

Two common API patterns:

  • Offset-based: ?page=2&limit=20 — simple but slow on deep pages
  • Cursor-based: ?cursor=abc123&limit=20 — stable for large, changing datasets

Pros & Cons

PaginationInfinite scroll
✅ Predictable navigation, shareable URLs✅ Seamless browsing experience
✅ Better for SEO (page URLs)✅ Better for mobile feeds
✅ Lower memory — only one page in DOM❌ Hard to reach footer
❌ Extra clicks for users❌ DOM grows unless virtualized

Real-Life Example

tsx
function ProductList() {
  const [page, setPage] = useState(1)
  const limit = 20
 
  const { data, isLoading } = useQuery({
    queryKey: ['products', page],
    queryFn: () => fetch(`/api/products?page=${page}&limit=${limit}`).then((r) => r.json()),
    keepPreviousData: true, // show old page while loading next
  })
 
  return (
    <>
      <Grid products={data?.items ?? []} />
      <nav aria-label="Pagination">
        <button disabled={page === 1} onClick={() => setPage((p) => p - 1)}>
          Previous
        </button>
        <span>
          Page {page} of {data?.totalPages}
        </span>
        <button disabled={page >= data?.totalPages} onClick={() => setPage((p) => p + 1)}>
          Next
        </button>
      </nav>
    </>
  )
}
javascript
// Cursor-based — better for social feeds / orders
GET /api/orders?cursor=eyJpZCI6MTAwfQ&limit=20
// Response: { items: [...], nextCursor: "eyJpZCI6MTIwfQ", hasMore: true }

Back to index


164. React.memo

Theory

React.memo wraps a component and skips re-render if props haven't changed (shallow compare). Use when a component re-renders often with the same props — especially expensive child components.

Pros & Cons

ProsCons
Skips unnecessary rendersShallow compare misses deep object changes
Pairs with useCallbackOveruse adds comparison overhead

Interview Answer

React.memo memoizes a component — it skips re-render if props are shallowly equal. I use it on expensive list items after profiling shows unnecessary renders.

Real Example

jsx
const ProductRow = React.memo(function ProductRow({ product, onSelect }) {
  return (
    <tr onClick={() => onSelect(product.id)}>
      <td>{product.name}</td>
      <td>₹{product.price}</td>
    </tr>
  )
})

Back to index


165. Suspense (React)

Theory

Suspense lets components wait for something (lazy-loaded code, async data) before rendering, showing a fallback UI in the meantime. It enables declarative loading states instead of manual isLoading flags.

Current uses:

  • React.lazy — code splitting fallback
  • React 19+ / frameworks — async data fetching with Suspense
  • Streaming SSR — send HTML progressively

Pros & Cons

ProsCons
Declarative loading statesData fetching Suspense needs framework support
Enables streaming SSRError handling needs Error Boundaries alongside
Composable fallbacks at any levelLearning curve

Real-Life Example

tsx
import { Suspense, lazy } from 'react'
 
const Dashboard = lazy(() => import('./Dashboard'))
const Settings = lazy(() => import('./Settings'))
 
function App() {
  return (
    <Suspense fallback={<FullPageSpinner />}>
      <Routes>
        <Route
          path="/dashboard"
          element={
            <Suspense fallback={<DashboardSkeleton />}>
              <Dashboard />
            </Suspense>
          }
        />
        <Route
          path="/settings"
          element={
            <Suspense fallback={<SettingsSkeleton />}>
              <Settings />
            </Suspense>
          }
        />
      </Routes>
    </Suspense>
  )
}
 
// Nested Suspense — granular loading
function Page() {
  return (
    <div>
      <Header /> {/* renders immediately */}
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar /> {/* lazy */}
      </Suspense>
      <Suspense fallback={<ContentSkeleton />}>
        <MainContent /> {/* lazy + async data */}
      </Suspense>
    </div>
  )
}

Back to index


166. Throttle promises by batching

Theory

Batching promises groups multiple concurrent requests into controlled batches to avoid overwhelming the server or hitting rate limits. Instead of firing 1000 API calls at once, you process them in chunks of N with optional delay between batches.

This is essential for bulk imports, mass notifications, or syncing large datasets.

Pros & Cons

ProsCons
Prevents server overload and 429 errorsSlower total completion time
Predictable resource usageMore complex code than fire-and-forget
Respects API rate limitsBatch size tuning required per API

Real-Life Example

javascript
async function batchPromises(tasks, batchSize = 5, delayMs = 200) {
  const results = []
 
  for (let i = 0; i < tasks.length; i += batchSize) {
    const batch = tasks.slice(i, i + batchSize)
    const batchResults = await Promise.all(batch.map((task) => task()))
    results.push(...batchResults)
 
    if (i + batchSize < tasks.length) {
      await new Promise((r) => setTimeout(r, delayMs))
    }
  }
 
  return results
}
 
// Real-life: Send push notifications to 10,000 users in batches
async function notifyUsers(userIds, message) {
  const tasks = userIds.map(
    (id) => () =>
      fetch('/api/notify', {
        method: 'POST',
        body: JSON.stringify({ userId: id, message }),
      }),
  )
 
  return batchPromises(tasks, 50, 100) // 50 at a time, 100ms between batches
}

Back to index


167. Throttling implementation

Theory

Throttling ensures a function executes at most once per time window, regardless of how many times the event fires. The first call runs immediately; subsequent calls within the window are ignored until the window expires.

Classic use: scroll handlers, mouse-move tracking, rate-limiting button clicks.

Pros & Cons

ProsCons
Guarantees regular execution during continuous eventsMay miss the final event (unless trailing edge added)
Protects performance on high-frequency eventsLess precise than debounce for "wait until done"
Immediate first responseCan drop important intermediate values

Real-Life Example

javascript
function throttle(fn, limit) {
  let inThrottle = false
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}
 
// Real-life: Track scroll depth for analytics (max once per 200ms)
const trackScrollDepth = throttle(() => {
  const depth = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100)
  analytics.track('scroll_depth', { depth })
}, 200)
 
window.addEventListener('scroll', trackScrollDepth)

Back to index


168. Use React.lazy() and code splitting for better performance

Theory

Code splitting breaks your JavaScript bundle into smaller chunks loaded on demand. React.lazy() dynamically imports a component, and <Suspense> shows a fallback while the chunk loads.

Split by route first (biggest impact), then by heavy components (charts, editors, maps).

Pros & Cons

Code splittingSingle bundle
✅ Faster initial page load❌ User downloads code for pages they never visit
✅ Better Core Web Vitals (LCP, TTI)❌ Longer time-to-interactive on slow networks
✅ Parallel chunk loading—
❌ Brief loading flash without good fallback—

Real-Life Example

tsx
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
 
// Route-level splitting — each page is a separate chunk
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Analytics = lazy(() => import('./pages/Analytics')) // heavy chart library
const Settings = lazy(() => import('./pages/Settings'))
 
function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}
 
// Component-level splitting — load heavy widget only when needed
const RichTextEditor = lazy(() => import('./RichTextEditor'))
 
function BlogEditor() {
  const [showEditor, setShowEditor] = useState(false)
 
  return (
    <>
      <button onClick={() => setShowEditor(true)}>Write Post</button>
      {showEditor && (
        <Suspense fallback={<Spinner />}>
          <RichTextEditor />
        </Suspense>
      )}
    </>
  )
}
 
// Prefetch on hover for instant navigation
;<Link to="/analytics" onMouseEnter={() => import('./pages/Analytics')}>
  Analytics
</Link>

Back to index


169. Web Vitals & Production Monitoring

Theory

You can't optimize what you don't measure. Production monitoring connects real user metrics to code changes.

Tools:

  • web-vitals library → send to analytics
  • Sentry — errors + performance
  • Datadog RUM — Real User Monitoring
  • Lighthouse CI — PR-level regression checks
  • Chrome UX Report (CrUX) — field data from Google

Interview Answer

I instrument LCP, CLS, and INP with the web-vitals library, send them to our analytics pipeline tagged by page and release, set Sentry performance budgets, and run Lighthouse CI on PRs to catch regressions before merge.

Instrument Web Vitals

tsx
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals'
 
function sendToAnalytics({ name, value, id, rating }) {
  // Google Analytics 4
  gtag('event', name, {
    event_category: 'Web Vitals',
    event_label: id,
    value: Math.round(name === 'CLS' ? value * 1000 : value),
    metric_rating: rating, // "good" | "needs-improvement" | "poor"
    non_interaction: true,
  })
 
  // Or custom endpoint
  navigator.sendBeacon('/api/vitals', JSON.stringify({ name, value, page: location.pathname }))
}
 
onCLS(sendToAnalytics)
onINP(sendToAnalytics)
onLCP(sendToAnalytics)
onFCP(sendToAnalytics)
onTTFB(sendToAnalytics)

Sentry Performance

tsx
import * as Sentry from '@sentry/react'
 
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
  tracesSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
})
 
// Custom transaction for slow flows
function CheckoutPage() {
  useEffect(() => {
    const transaction = Sentry.startInactiveSpan({ name: 'checkout-load' })
    loadCheckoutData().finally(() => transaction.end())
  }, [])
}

Lighthouse CI in PRs

yaml
- name: Lighthouse CI
  uses: treosh/lighthouse-ci-action@v11
  with:
    urls: |
      https://staging.example.com/
      https://staging.example.com/products
    budgetPath: ./lighthouse-budget.json
    uploadArtifacts: true
json
// lighthouse-budget.json
[
  {
    "path": "/*",
    "timings": [
      { "metric": "largest-contentful-paint", "budget": 2500 },
      { "metric": "cumulative-layout-shift", "budget": 0.1 },
      { "metric": "interactive", "budget": 3500 }
    ]
  }
]

Alerting Strategy

MetricAlert whenAction
LCP p75> 3s for 1 hourCheck deploy, CDN, image regression
CLS p75> 0.15Find layout shift source in RUM
INP p75> 300msProfile long tasks, check new JS bundle
Error rate> 1% spikeRollback, check Sentry

Back to index


170. What are memory leaks in React and how to detect them?

Theory

A memory leak occurs when memory that is no longer needed is not released. In React, common causes are: uncleared timers/subscriptions, detached DOM nodes held in closures, growing caches, and event listeners not removed on unmount.

Pros & Cons of detection tools

ToolProsCons
Chrome DevTools Memory tabHeap snapshots, comparisonManual, requires reproduction
React DevTools ProfilerComponent render countsDoesn't show JS heap directly
why-did-you-renderCatches unnecessary re-rendersDev-only, noisy
Sentry / monitoringProduction leak patternsIndirect detection

Real-Life Example

jsx
// ❌ Memory leak — subscription not cleaned up
function LiveOrderTracker({ orderId }) {
  const [status, setStatus] = useState('pending')
 
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`)
    ws.onmessage = (e) => setStatus(JSON.parse(e.data).status)
    // Missing cleanup! WebSocket stays open after unmount
  }, [orderId])
 
  return <div>Status: {status}</div>
}
 
// ✅ Fixed — cleanup on unmount
function LiveOrderTracker({ orderId }) {
  const [status, setStatus] = useState('pending')
 
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`)
    ws.onmessage = (e) => setStatus(JSON.parse(e.data).status)
 
    return () => ws.close() // cleanup
  }, [orderId])
 
  return <div>Status: {status}</div>
}

Detection steps:

  1. Chrome DevTools → Memory → Take heap snapshot
  2. Perform the action (navigate away from component)
  3. Force GC → Take another snapshot
  4. Compare — look for detached DOM trees or growing object counts

Back to index


171. What is Debouncing and Throttling?

Theory

Both limit how often a function executes during high-frequency events, but with different strategies:

DebounceThrottle
BehaviorWait until activity stops, then run onceRun at most once per interval during activity
AnalogyElevator waits for everyone to boardTrain departs every 10 minutes regardless
Best forSearch input, resize-end, auto-saveScroll, mousemove, button spam prevention

Pros & Cons

DebounceThrottle
✅ Minimizes total executions✅ Guarantees regular updates during continuous events
✅ Great for API cost reduction✅ Better for scroll analytics
❌ Delays until pause ends❌ May miss final state without trailing edge
❌ Can feel sluggish if delay too long❌ More executions than debounce

Real-Life Example

javascript
// Debounce — search restaurants after user stops typing
function debounce(fn, delay) {
  let timerId
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
}
 
const searchRestaurants = debounce(async (query) => {
  const { data } = await axios.get(`/api/restaurants?q=${query}`)
  setResults(data)
}, 300)
 
// Throttle — track scroll position max once per 200ms
function throttle(fn, limit) {
  let inThrottle = false
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}
 
const trackScroll = throttle(() => {
  analytics.track('scroll_depth', { y: window.scrollY })
}, 200)
jsx
// React usage
function SearchBar() {
  const [query, setQuery] = useState('')
 
  const debouncedSearch = useMemo(() => debounce((q) => fetchResults(q), 300), [])
 
  useEffect(() => {
    if (query) debouncedSearch(query)
    return () => debouncedSearch.cancel?.() // cleanup if using lodash
  }, [query])
}

Back to index


172. What is the difference between debounce and throttle?

Theory

Both limit how often a function runs, but with opposite philosophies:

  • Debounce: "Wait until they stop, then run once."
  • Throttle: "Run at most once per interval, even if they keep going."

Pros & Cons

DebounceThrottle
Best forSearch input, resize-end, auto-saveScroll, mousemove, API rate limiting
ProsMinimizes total executionsGuarantees regular updates during continuous events
ConsDelays until pause endsMay miss final state without trailing option

Real-Life Example

plaintext
User typing "pizza" in search box:
  Debounce (300ms):  p-i-z-z-a → wait 300ms → 1 API call
  Throttle (300ms):  p → call, (300ms) z → call, (300ms) a → call
 
User scrolling a feed:
  Debounce:          No analytics until scroll STOPS (bad — miss scroll data)
  Throttle (200ms):    Analytics fires every 200ms during scroll (good)

Back to index


173. What techniques would you use to improve Core Web Vitals?

Theory — The three Core Web Vitals

MetricMeasuresGood threshold
LCP (Largest Contentful Paint)Loading performance≤ 2.5s
INP (Interaction to Next Paint)Responsiveness (replaced FID)≤ 200ms
CLS (Cumulative Layout Shift)Visual stability≤ 0.1

LCP optimizations

html
<!-- Preload hero image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
 
<!-- Modern formats + responsive images -->
<img
  src="/restaurant.webp"
  srcset="/restaurant-400.webp 400w, /restaurant-800.webp 800w"
  sizes="(max-width: 600px) 100vw, 400px"
  width="400"
  height="300"
  alt="Restaurant"
  loading="eager"
  fetchpriority="high" />
  • Inline critical CSS; defer non-critical CSS
  • Use CDN + HTTP/2 or HTTP/3
  • Server-side render or stream above-the-fold content (RSC/SSR)
  • Reduce TTFB (edge caching, efficient APIs)

INP optimizations

javascript
// Break long tasks
function processChunk(items, index = 0) {
  const CHUNK = 50
  const end = Math.min(index + CHUNK, items.length)
 
  for (let i = index; i < end; i++) {
    processItem(items[i])
  }
 
  if (end < items.length) {
    requestIdleCallback(() => processChunk(items, end))
  }
}
  • Minimize main-thread JS (code splitting, tree shaking)
  • Use useTransition for non-urgent UI updates
  • Debounce expensive handlers; use Web Workers for heavy computation
  • Avoid layout thrashing (batch DOM reads/writes)

CLS optimizations

tsx
// Always reserve space for images/ads
<img width={400} height={300} src="..." alt="..." />
 
// Use aspect-ratio in CSS
.card-image {
  aspect-ratio: 4 / 3;
  width: 100%;
}
 
// Don't inject content above existing content
// Prefer transform animations over layout-changing properties
  • Set explicit dimensions on media and embeds
  • Use font-display: swap with matched fallback metrics (size-adjust)
  • Skeleton loaders with fixed height

Measurement

javascript
import { onLCP, onINP, onCLS } from 'web-vitals'
 
onLCP(console.log)
onINP(console.log)
onCLS(console.log)

Ship RUM data to Datadog, Sentry, or Google Analytics 4. Set performance budgets in CI (Lighthouse CI, bundle analyzer).

Back to index


174. Write a debounce function (cancellable)

Theory

Debouncing delays execution until after a pause in calls. A cancellable debounce also exposes .cancel() to abort pending execution — essential for cleanup on unmount, route change, or explicit user action.

a fintech app asks you to write this from scratch — no lodash.

Pros & Cons

Cancellable debounceBasic debounce
✅ Cleanup on component unmount❌ Pending call may fire after unmount
✅ Cancel on route change
✅ Abort stale API responses

Real-Life Example

javascript
function debounce(fn, delay) {
  let timerId
 
  function debounced(...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
 
  debounced.cancel = function () {
    clearTimeout(timerId)
    timerId = null
  }
 
  debounced.flush = function (...args) {
    clearTimeout(timerId)
    fn.apply(this, args)
  }
 
  return debounced
}
 
// Usage
const search = debounce((query) => {
  fetch(`/api/search?q=${query}`).then(renderResults)
}, 300)
 
searchInput.addEventListener('input', (e) => search(e.target.value))
 
// Cancel on navigate away
router.beforeEach(() => search.cancel())
 
// Flush on form submit (don't wait for debounce)
form.addEventListener('submit', () => search.flush(input.value))
javascript
// TypeScript version — a fintech app-grade
function debounce<T extends (...args: Parameters<T>) => void>(
  fn: T,
  delay: number
): T & { cancel: () => void; flush: (...args: Parameters<T>) => void } {
  let timerId: ReturnType<typeof setTimeout> | null = null;
 
  const debounced = function (this: unknown, ...args: Parameters<T>) {
    if (timerId) clearTimeout(timerId);
    timerId = setTimeout(() => {
      timerId = null;
      fn.apply(this, args);
    }, delay);
  } as T & { cancel: () => void; flush: (...args: Parameters<T>) => void };
 
  debounced.cancel = () => {
    if (timerId) clearTimeout(timerId);
    timerId = null;
  };
 
  debounced.flush = function (this: unknown, ...args: Parameters<T>) {
    debounced.cancel();
    fn.apply(this, args);
  };
 
  return debounced;
}

Back to index


175. Your Next.js application has a large JavaScript bundle size affecting performance. How would you reduce it?

Description

Large bundles increase download, parse, and execute time — hurting LCP and Interaction to Next Paint (INP). Next.js helps by default with Server Components, but client islands and dependencies can still bloat the bundle.

Theory

TechniqueImpact
Server ComponentsKeep heavy logic/libraries on server — zero client JS
next/dynamicLazy-load client components
optimizePackageImportsTree-shake barrel files (lodash, MUI, etc.)
Analyze bundle@next/bundle-analyzer
Replace heavy libsdate-fns over moment, recharts only where needed
next/imageAvoid shipping image processing to client
Route-based splittingAutomatic per-route chunks in App Router

Pros & Cons

Aggressive code splittingSingle large bundle
✅ Faster initial load✅ Simpler imports
✅ Better mobile performance❌ Poor Core Web Vitals
❌ More loading states to design❌ Higher bounce rate

Real Example

js
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
 
module.exports = withBundleAnalyzer({
  experimental: {
    optimizePackageImports: ['lodash-es', '@mui/material', 'lucide-react'],
  },
})
tsx
// ❌ Heavy chart in main bundle
import { LineChart } from 'recharts'
 
// ✅ Lazy load only when tab opens
import dynamic from 'next/dynamic'
 
const LineChart = dynamic(() => import('recharts').then((mod) => mod.LineChart), {
  ssr: false,
  loading: () => <ChartSkeleton />,
})
tsx
// Prefer Server Component for data — no client JS for fetch logic
// app/reports/page.tsx
import { HeavyDataTable } from './heavy-data-table' // Server Component
 
export default async function ReportsPage() {
  const data = await getReportData() // runs on server only
  return <HeavyDataTable rows={data} />
}
bash
ANALYZE=true npm run build
Targets (rule of thumb)
  • First-load JS shared chunk: < 100–150 KB gzipped where possible
  • Audit import from package roots — use direct paths or optimizePackageImports

Interview Answer

I'd analyze the bundle with @next/bundle-analyzer, push data fetching and non-interactive UI to Server Components, dynamically import heavy client libraries, enable optimizePackageImports, and replace or defer dependencies that don't need to be in the critical path.

Back to index


Architecture & Patterns

176. Error Boundaries & Design Patterns

Theory

Error Boundaries

Class components (or react-error-boundary library) that catch render errors in children via componentDidCatch / getDerivedStateFromError. They do not catch event handlers, async, or SSR errors.

Design Patterns

PatternIdeaWhen
HOCFunction wrapping component to inject propsCross-cutting: auth, logging
Render PropsComponent accepts function as childShared behavior, flexible UI
Compound ComponentsRelated components share implicit stateTabs, Accordion, Select
Container/PresentationalSplit logic vs UITesting, reuse

Modern trend: custom hooks replace HOCs and render props for most cases.

Interview Answer

Error boundaries catch render errors and show fallback UI — I use react-error-boundary. For patterns, I prefer custom hooks over HOCs today, but I know compound components for design systems and HOCs in legacy code.

Real Example

jsx
// Error Boundary (react-error-boundary)
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => queryClient.resetQueries()}>
  <Dashboard />
</ErrorBoundary>
 
// HOC
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const { user } = useAuth();
    if (!user) return <Navigate to="/login" />;
    return <WrappedComponent {...props} user={user} />;
  };
}
 
// Render Props
<DataFetcher url="/api/users">
  {({ data, loading }) => loading ? <Spinner /> : <UserList users={data} />}
</DataFetcher>
 
// Compound Components
<Tabs defaultIndex={0}>
  <Tabs.List>
    <Tabs.Tab>Overview</Tabs.Tab>
    <Tabs.Tab>Settings</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panels>
    <Tabs.Panel><Overview /></Tabs.Panel>
    <Tabs.Panel><Settings /></Tabs.Panel>
  </Tabs.Panels>
</Tabs>
 
// Custom Hook — modern replacement
function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be inside AuthProvider");
  return ctx;
}
PatternQuestionsCore idea
Fundamentals & JSX1–5Library, JSX rules, Fragments, events
Components & State6–10Props down, state local, one-way flow
Data Flow & Forms11–14Lift state, controlled inputs
Hooks15–23Stateful logic, rules, custom hooks
Virtual DOM & Rendering24–28Diff, keys, CSR/SSR, Fiber
Context29–31Share without drilling
Router32–35SPA navigation, guards
API Integration36–39fetch, Axios, TanStack Query
Performance40–44memo, lazy, Suspense, transitions
Lifecycle45–46Class methods ↔ useEffect
Redux47–49Global state, RTK, vs Context
Errors & Patterns50Boundaries, HOC, compound, hooks

How to Study Pattern-Wise

  1. One pattern per session — finish Hooks (15–23) before jumping to Redux
  2. Connect patterns — after Context (29), revisit Prop Drilling (31) and Lifting State (11)
  3. Say the one-liner out loud — if you can say it, you can answer in an interview
  4. Code the example — type it, don't just read it
  5. Expect follow-ups — "When would you NOT use useMemo?" → when cost < overhead

50 questions. 12 patterns. Master the patterns and random interview questions become predictable.

Back to index


177. Follow a proper folder structure

Theory

A good folder structure groups code by feature (domain) rather than by file type. Colocate components, hooks, API calls, types, and tests for each feature. Shared code lives in a shared/ or common/ directory.

Pros & Cons

Feature-based structureType-based structure
✅ Everything for a feature in one place❌ Jump between 5 folders to change one feature
✅ Easy to delete/move features❌ Grows unwieldy at scale
✅ Team ownership per feature❌ Merge conflicts across teams
✅ Scales with micro-frontends—

Real-Life Example

plaintext
src/
├── app/                        # App shell
│   ├── App.tsx
│   ├── router.tsx
│   └── providers.tsx           # QueryClient, Theme, Auth providers
│
├── features/                   # Feature modules (preferred)
│   ├── auth/
│   │   ├── components/
│   │   │   ├── LoginForm.tsx
│   │   │   └── ProtectedRoute.tsx
│   │   ├── hooks/
│   │   │   └── useAuth.ts
│   │   ├── api/
│   │   │   └── authService.ts
│   │   └── types.ts
│   │
│   ├── restaurants/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api/
│   │   └── types.ts
│   │
│   └── cart/
│       ├── components/
│       ├── hooks/
│       ├── store/              # cartSlice if using Redux
│       └── types.ts
│
├── shared/                     # Cross-feature reusables
│   ├── components/             # Button, Input, Modal, Spinner
│   ├── hooks/                  # useDebounce, useMediaQuery
│   ├── utils/                  # formatPrice, dateHelpers
│   └── types/                  # Global types
│
├── assets/                     # Images, fonts, icons
└── styles/                     # Global CSS, tokens, variables

Rules:

  • Import from features → shared ✅
  • Import from feature A → feature B ❌ (creates coupling)
  • Each feature exports a public API via index.ts

Back to index


178. How would you design a scalable frontend architecture for a large-scale application?

Theory — Layered architecture

mermaid
flowchart TB
  subgraph Presentation
    Pages[Pages / Routes]
    Components[UI Components]
  end
 
  subgraph Application
    Hooks[Custom Hooks]
    State[State Management]
    Services[API Services]
  end
 
  subgraph Infrastructure
    Router[Router]
    Auth[Auth Layer]
    Analytics[Observability]
    Cache[Cache Layer]
  end
 
  Pages --> Components
  Components --> Hooks
  Hooks --> State
  Hooks --> Services
  Services --> Cache
  Pages --> Router
  Services --> Auth

Core principles

PrincipleImplementation
Modular monorepoapps/web, packages/ui, packages/api-client, packages/utils
Feature-based foldersfeatures/orders/, features/restaurants/ — colocate components, hooks, API, tests
Separation of concernsUI components don't call fetch directly; go through service layer
Design systemShared tokens, primitives, accessibility baked in
Lazy loadingRoute-level code splitting with React.lazy + Suspense
API contractOpenAPI/GraphQL schema, generated TypeScript types
Error boundariesPer-route or per-feature isolation
ObservabilitySentry, Web Vitals RUM, structured logging

Folder structure example

plaintext
src/
├── app/                    # App shell, providers, router
├── features/
│   ├── restaurants/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api/
│   │   └── types.ts
│   └── orders/
├── shared/
│   ├── components/         # Design system primitives
│   ├── hooks/
│   └── utils/
└── services/
    ├── http-client.ts
    └── auth.ts

Interview talking points

  • Start with business domains, not technical layers only
  • Enforce boundaries with ESLint module boundaries or Nx tags
  • CI/CD: preview deploys per PR, visual regression, bundle size budgets
  • Progressive rollout: feature flags (LaunchDarkly, Unleash)

Back to index


179. Micro-frontend Architecture

Theory

Micro-frontends split a large frontend into independently deployable applications owned by different teams. Each handles a business domain (search, checkout, profile).

Integration: Module Federation, iframes, Web Components, single-spa.

Pros & Cons

ProsCons
Team autonomy — independent deploysInconsistent UX across teams
Technology flexibilityShared state / routing complexity
Incremental migrationPerformance overhead (multiple bundles)
Fault isolationDependency duplication without federation

Real-Life Example

plaintext
Shell App (routing, auth, layout)
├── Search MFE      (Team A — React)
├── Checkout MFE    (Team B — React)
├── Profile MFE     (Team C — Vue)
└── Analytics MFE   (Team D — React)
 
Integration via Module Federation:
javascript
// Remote — checkout team exposes widget
new ModuleFederationPlugin({
  name: 'checkout',
  filename: 'remoteEntry.js',
  exposes: { './CheckoutWidget': './src/CheckoutWidget' },
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
})
 
// Host — shell app consumes
const CheckoutWidget = lazy(() => import('checkout/CheckoutWidget'))

Back to index


180. React Architecture

Theory

Production React architecture organizes code by feature, separates concerns, and scales with team size.

Layers

plaintext
┌─────────────────────────────────────┐
│  Presentation — Components, Pages   │
├─────────────────────────────────────┤
│  Application — Hooks, State, Router  │
├─────────────────────────────────────┤
│  Domain — Services, API, Business Logic│
├─────────────────────────────────────┤
│  Infrastructure — HTTP, Auth, Config │
└─────────────────────────────────────┘

Interview Answer

I use feature-based folders, separate UI from business logic via hooks and services, shared design system components, TanStack Query for server state, Redux for global client state, and lazy-loaded routes.

Real Example

plaintext
src/
├── app/                    # Router, providers, shell
│   ├── App.tsx
│   ├── router.tsx
│   └── providers.tsx       # QueryClient, Redux, Theme
├── features/
│   ├── auth/
│   │   ├── components/     # LoginForm, ProtectedRoute
│   │   ├── hooks/          # useAuth
│   │   ├── api/            # authService.ts
│   │   └── types.ts
│   ├── dashboard/
│   └── transactions/
├── shared/
│   ├── components/         # Button, Modal, StarRating
│   ├── hooks/
│   └── utils/
└── services/
    └── httpClient.ts       # Axios instance + interceptors

Data flow: Component → custom hook → service/API → TanStack Query cache → UI

Back to index


181. React vs Vue — Architecture & Performance Tradeoffs

Theory

AreaReactVue
UI modelJSX — JS everywhereTemplates + optional JSX
ReactivityImmutable state + rerenderProxy-based tracking (Vue 3)
Update granularityComponent-level rerender (memo to narrow)Fine-grained by default
StateuseState, Context, Redux, Zustandref/reactive, Pinia
SSR frameworkNext.jsNuxt
Learning curveHooks + ecosystem choicesGentler defaults, more built-in

Pros & Cons

Choose ReactChoose Vue
✅ Largest ecosystem, hiring pool✅ Faster onboarding, less boilerplate
✅ React Native for mobile✅ Built-in transitions, directives
✅ Flexibility (library not framework)✅ SFC colocation (style/template/script)
❌ More decisions (router, state, data)❌ Smaller enterprise footprint in some regions

Performance tradeoffs

ReactVue
Re-renders whole component on state change unless memoizedCompiler + proxies track deps — skip unaffected components
Virtual DOM diff every updatePatch only tracked dependencies
useMemo / React.memo manual opt-incomputed auto-caches
Fiber scheduling for concurrent featuresVue 3 scheduler + async component boundaries

Real Example — Same counter

tsx
// React — explicit state triggers component rerender
function ReactCounter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}
vue
<!-- Vue — ref tracked automatically -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
 
<template>
  <button @click="count++">{{ count }}</button>
</template>

Interview Answer

React rerenders components when state changes and relies on Virtual DOM diffing plus manual memoization for hot paths. Vue 3 tracks dependencies at compile/runtime for finer updates. I'd pick React for ecosystem and cross-platform; Vue for faster delivery with opinionated defaults — both perform well when lists are virtualized and state is colocated.

Back to index


182. Scalable, Maintainable Architecture

Theory

Scalable frontend architecture optimizes for team velocity and change isolation — not premature abstraction.

Principles:

  • Feature-based folders — colocate everything a feature needs
  • Unidirectional data flow — predictable state
  • API layer separation — components never call fetch directly
  • Design system — shared UI primitives
  • Barrel exports sparingly — avoid circular deps and bundle bloat
  • Module boundaries — features don't import from sibling features

Interview Answer

Feature-based architecture with a shared design system, API services layer, TanStack Query for server state, and strict import boundaries — each feature owns its components, hooks, and types.

Folder Structure

plaintext
src/
├── app/                        # Shell, router, providers
│   ├── App.tsx
│   ├── router.tsx
│   └── providers.tsx
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api/
│   │   ├── types.ts
│   │   └── index.ts            # public API only
│   ├── cart/
│   ├── catalog/
│   └── checkout/
├── shared/
│   ├── components/             # Button, Modal, DataTable
│   ├── hooks/
│   ├── utils/
│   └── types/
├── services/
│   └── httpClient.ts           # Axios instance, interceptors
└── design-system/
    ├── tokens/
    └── primitives/

API Layer

tsx
// services/httpClient.ts
export const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
})
 
api.interceptors.request.use((config) => {
  const token = getAccessToken()
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})
 
api.interceptors.response.use(
  (res) => res,
  async (error) => {
    if (error.response?.status === 401) await refreshTokenAndRetry(error.config)
    return Promise.reject(error)
  },
)
 
// features/catalog/api/products.ts
export const productApi = {
  getAll: (filters: ProductFilters) => api.get<Product[]>('/products', { params: filters }).then((r) => r.data),
  getById: (id: string) => api.get<Product>(`/products/${id}`).then((r) => r.data),
}

Import Boundaries (ESLint)

javascript
// eslint.config.js
{
  rules: {
    "no-restricted-imports": ["error", {
      patterns: [
        {
          group: ["../features/*/*"],
          message: "Import from feature public API (index.ts), not internals",
        },
        {
          group: ["features/cart/**"],
          from: "features/checkout",
          message: "Checkout cannot import cart internals directly",
        },
      ],
    }],
  },
}

Composition Over Configuration

tsx
// ❌ God component with 20 props
<DataTable sortable filterable exportable paginated virtualized ... />
 
// ✅ Composable
<DataTable data={products}>
  <DataTable.Toolbar>
    <SearchFilter />
    <ExportButton />
  </DataTable.Toolbar>
  <DataTable.Columns columns={productColumns} />
  <DataTable.Pagination pageSize={20} />
</DataTable>

Back to index


183. Use Error Boundaries to handle unexpected UI crashes

Theory

Error Boundaries are React components that catch JavaScript errors in their child tree during rendering and display a fallback UI instead of crashing the entire app. They do not catch errors in event handlers, async code, or SSR.

Use them at route level, around risky third-party widgets, and around data-heavy sections.

Pros & Cons

Error BoundariesNo error handling
✅ App stays usable when one section fails❌ White screen of death
✅ Log errors to Sentry/Datadog❌ User has no recovery path
✅ Per-feature isolation❌ One bug kills entire app
❌ Class components only (for now)—

Real-Life Example

tsx
class ErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false }
 
  static getDerivedStateFromError() {
    return { hasError: true }
  }
 
  componentDidCatch(error: Error, info: React.ErrorInfo) {
    logToSentry(error, info.componentStack)
  }
 
  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback ?? (
          <div role="alert">
            <h2>Something went wrong</h2>
            <button onClick={() => this.setState({ hasError: false })}>Try again</button>
          </div>
        )
      )
    }
    return this.props.children
  }
}
 
// Wrap each route independently
function App() {
  return (
    <Routes>
      <Route
        path="/orders"
        element={
          <ErrorBoundary fallback={<OrdersErrorPage />}>
            <OrdersPage />
          </ErrorBoundary>
        }
      />
      <Route
        path="/profile"
        element={
          <ErrorBoundary fallback={<ProfileErrorPage />}>
            <ProfilePage />
          </ErrorBoundary>
        }
      />
    </Routes>
  )
}

Back to index


184. What are Error Boundaries in React?

Theory

Error Boundaries are React components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors. They display a fallback UI instead of crashing the entire app.

They do NOT catch: event handler errors, async errors, SSR errors, or errors in the boundary itself.

Pros & Cons

ProsCons
Isolates failures — app stays usableClass components only (no hook equivalent yet)
Better UX with fallback UIDoesn't catch event handler or async errors
Can log errors to monitoring servicesMust be placed intentionally in tree

Real-Life Example

jsx
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null }
 
  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }
 
  componentDidCatch(error, errorInfo) {
    // Send to Sentry / Datadog
    logErrorToService(error, errorInfo.componentStack)
  }
 
  render() {
    if (this.state.hasError) {
      return (
        <div role="alert">
          <h2>Something went wrong</h2>
          <button onClick={() => this.setState({ hasError: false })}>Try again</button>
        </div>
      )
    }
    return this.props.children
  }
}
 
// Real-life: Wrap each route independently
function App() {
  return (
    <ErrorBoundary>
      <Routes>
        <Route
          path="/orders"
          element={
            <ErrorBoundary>
              <OrdersPage />
            </ErrorBoundary>
          }
        />
        <Route
          path="/profile"
          element={
            <ErrorBoundary>
              <ProfilePage />
            </ErrorBoundary>
          }
        />
      </Routes>
    </ErrorBoundary>
  )
}

Back to index


185. What are portals?

Answer

Portals allow rendering a child component into a different part of the DOM tree outside the parent component hierarchy.

jsx
ReactDOM.createPortal(child, document.getElementById('modal-root'))

Back to index


186. What are React Portals? How are modals mounted using them?

Theory

Portals let you render children into a DOM node outside the parent component's DOM hierarchy while keeping the React tree (context, events) connected. ReactDOM.createPortal(child, container) is the API.

Modals use portals to render at document.body level, escaping overflow: hidden, z-index stacking contexts, and parent CSS transforms.

Pros & Cons

ProsCons
Escapes parent CSS constraintsFocus management must be handled manually
Proper z-index layering for overlaysScreen readers need aria-modal, focus trap
Event bubbling still works through React treePortal target must exist in DOM

Real-Life Example

jsx
import { createPortal } from 'react-dom'
import { useEffect, useRef } from 'react'
 
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null)
 
  useEffect(() => {
    if (!isOpen) return
    const handleEsc = (e) => e.key === 'Escape' && onClose()
    document.addEventListener('keydown', handleEsc)
    document.body.style.overflow = 'hidden'
    modalRef.current?.focus()
 
    return () => {
      document.removeEventListener('keydown', handleEsc)
      document.body.style.overflow = ''
    }
  }, [isOpen, onClose])
 
  if (!isOpen) return null
 
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div
        className="modal-content"
        role="dialog"
        aria-modal="true"
        ref={modalRef}
        tabIndex={-1}
        onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.getElementById('modal-root'), // or document.body
  )
}
 
// Usage in a deeply nested component
function ProductPage() {
  const [showCart, setShowCart] = useState(false)
  return (
    <div style={{ overflow: 'hidden' }}>
      <button onClick={() => setShowCart(true)}>View Cart</button>
      <Modal isOpen={showCart} onClose={() => setShowCart(false)}>
        <CartItems />
      </Modal>
    </div>
  )
}

Back to index


187. What is an Error Boundary?

Theory

An Error Boundary is a React class component that catches JavaScript errors in its child component tree during rendering, in lifecycle methods, and in constructors. It displays a fallback UI instead of crashing the entire application.

Does NOT catch:

  • Event handler errors (use try/catch)
  • Async errors (use try/catch or .catch())
  • Errors in the boundary itself
  • SSR errors

Pros & Cons

ProsCons
✅ Isolates failures — app stays usable❌ Class components only (no hook yet)
✅ Better UX with fallback UI❌ Doesn't catch async/event errors
✅ Integrates with error monitoring (Sentry)Must be placed intentionally in tree

Real-Life Example

tsx
class ErrorBoundary extends React.Component<
  {
    children: React.ReactNode
    fallback?: React.ReactNode
    onError?: (e: Error) => void
  },
  { hasError: boolean }
> {
  state = { hasError: false }
 
  static getDerivedStateFromError() {
    return { hasError: true }
  }
 
  componentDidCatch(error: Error, info: React.ErrorInfo) {
    this.props.onError?.(error)
    logToSentry(error, info.componentStack)
  }
 
  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback ?? (
          <div role="alert">
            <p>Something went wrong in this section.</p>
            <button onClick={() => this.setState({ hasError: false })}>Retry</button>
          </div>
        )
      )
    }
    return this.props.children
  }
}
 
// Wrap independent page sections
function DashboardPage() {
  return (
    <div className="dashboard">
      <ErrorBoundary fallback={<SectionError name="Announcements" />}>
        <AnnouncementsSection />
      </ErrorBoundary>
      <ErrorBoundary fallback={<SectionError name="Team" />}>
        <TeamSection />
      </ErrorBoundary>
      <ErrorBoundary fallback={<SectionError name="Projects" />}>
        <ProjectsSection />
      </ErrorBoundary>
    </div>
  )
}

Back to index


188. What is Module Federation?

Theory

Module Federation (Webpack 5) allows multiple independently built and deployed applications to share code at runtime. One app can dynamically import components/modules from another without rebuilding.

A host app consumes remote modules. A remote app exposes modules. Shared dependencies (React) are deduplicated.

Pros & Cons

ProsCons
Independent deploys per teamRuntime coupling — remote down = host breaks
Share components without npm packagesComplex webpack configuration
Runtime code sharingVersion mismatch risks for shared deps

Real-Life Example

javascript
// Remote app (webpack.config.js) — "checkout" team
new ModuleFederationPlugin({
  name: 'checkout',
  filename: 'remoteEntry.js',
  exposes: {
    './CheckoutWidget': './src/CheckoutWidget',
  },
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
})
 
// Host app (webpack.config.js) — "main" team
new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    checkout: 'checkout@https://checkout.example.com/remoteEntry.js',
  },
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
})
 
// Host consumes remote component
const CheckoutWidget = React.lazy(() => import('checkout/CheckoutWidget'))

Back to index


JavaScript Core

189. Async/Await

Theory

async/await is syntactic sugar over Promises. An async function always returns a Promise. await pauses execution until the Promise settles, then resumes with the resolved value.

Code before the first await runs synchronously. Code after await runs as a microtask.

Pros & Cons

async/await.then() chains
✅ Reads like synchronous code✅ More explicit control flow
✅ try/catch for errors✅ Easier to parallelize visually with Promise.all
✅ Easier debugging (stack traces)
❌ Easy to accidentally run sequentially
❌ Sequential await is slower than parallel

Real-Life Example

javascript
// Sequential — slow (each waits for previous)
async function loadCheckoutSlow(userId) {
  const user = await fetchUser(userId)
  const cart = await fetchCart(userId)
  const addresses = await fetchAddresses(userId)
  return { user, cart, addresses }
}
 
// Parallel — fast
async function loadCheckout(userId) {
  const [user, cart, addresses] = await Promise.all([fetchUser(userId), fetchCart(userId), fetchAddresses(userId)])
  return { user, cart, addresses }
}
 
// Error handling
async function placeOrder(orderData) {
  try {
    const response = await fetch('/api/orders', {
      method: 'POST',
      body: JSON.stringify(orderData),
    })
    if (!response.ok) throw new Error(`Order failed: ${response.status}`)
    return await response.json()
  } catch (error) {
    logToSentry(error)
    showToast('Order failed. Please try again.')
    throw error
  }
}
 
// Execution order demo
async function demo() {
  console.log('A')
  await Promise.resolve()
  console.log('B') // microtask — after sync code
}
console.log('C')
demo()
console.log('D')
// C → A → D → B

Back to index


190. async/await and Promises

Theory

A Promise represents a future value: pending → fulfilled or rejected.

async/await is syntactic sugar — async functions return Promises; await pauses until Promise settles.

Code before first await runs synchronously. After await resumes as microtask.

Interview Answer

Promises handle async with then/catch chains. async/await makes it read like sync code. await pauses the function, not the thread — other code keeps running.

Real Example

javascript
// Promise chain
function fetchUser(id) {
  return fetch(`/api/users/${id}`).then((r) => {
    if (!r.ok) throw new Error(`HTTP ${r.status}`)
    return r.json()
  })
}
 
// async/await — cleaner error handling
async function loadDashboard(userId) {
  try {
    const [user, orders, rewards] = await Promise.all([fetchUser(userId), fetchOrders(userId), fetchRewards(userId)])
    return { user, orders, rewards }
  } catch (err) {
    logError(err)
    throw err
  }
}
 
// Execution order
async function demo() {
  console.log('A')
  await Promise.resolve()
  console.log('B')
}
console.log('C')
demo()
console.log('D')
// C → A → D → B

Back to index


191. Call, Apply, Bind — Difference + Polyfill

Theory

All three control this context of a function:

MethodInvokes?ArgumentsReturns
callImmediatelyComma-separatedFunction result
applyImmediatelyArrayFunction result
bindNoComma-separated (+ partial)New function

Pros & Cons

call/applybind
One-time invocationReusable bound function
apply when args in arrayPartial application
Cannot reuse easilySlight memory per bound fn

Interview Answer

call and apply invoke immediately with a set this — apply takes args as array. bind returns a new function with this permanently bound. Arrow functions ignore all three for this.

Polyfill Implementations

javascript
// Polyfill: Function.prototype.call
Function.prototype.myCall = function (thisArg, ...args) {
  const fn = this
  if (typeof fn !== 'function') throw new TypeError('Not a function')
 
  const context = thisArg ?? globalThis
  const uniqueKey = Symbol('fn')
  context[uniqueKey] = fn
 
  const result = context[uniqueKey](...args)
  delete context[uniqueKey]
  return result
}
 
// Polyfill: Function.prototype.apply
Function.prototype.myApply = function (thisArg, argsArray) {
  const fn = this
  if (typeof fn !== 'function') throw new TypeError('Not a function')
 
  const context = thisArg ?? globalThis
  const uniqueKey = Symbol('fn')
  context[uniqueKey] = fn
 
  const result = argsArray ? context[uniqueKey](...argsArray) : context[uniqueKey]()
  delete context[uniqueKey]
  return result
}
 
// Polyfill: Function.prototype.bind
Function.prototype.myBind = function (thisArg, ...boundArgs) {
  const fn = this
  if (typeof fn !== 'function') throw new TypeError('Not a function')
 
  const boundFn = function (...callArgs) {
    const isNew = this instanceof boundFn
    return fn.apply(isNew ? this : thisArg, boundArgs.concat(callArgs))
  }
 
  if (fn.prototype) {
    boundFn.prototype = Object.create(fn.prototype)
  }
 
  return boundFn
}

Real Example

javascript
function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`
}
 
const user = { name: 'Amit' }
 
greet.myCall(user, 'Hello', '!') // "Hello, Amit!"
greet.myApply(user, ['Hi', '.']) // "Hi, Amit."
const sayHello = greet.myBind(user, 'Hey')
sayHello('!!') // "Hey, Amit!!"

Back to index


192. Can call(), apply(), bind() be used with Arrow Functions?

Theory

No. Arrow functions have no own this — they inherit this lexically from the enclosing scope. Since this is fixed at creation time, call, apply, and bind cannot change it.

Calling them on arrow functions doesn't throw — but this stays lexical, arguments are still passed.

Interview Answer

No — arrow functions ignore call, apply, and bind for this binding because they inherit this lexically. Use regular functions when you need dynamic this.

Real Example

javascript
const obj = {
  name: 'a fintech app',
  regular() {
    return this.name
  },
  arrow: () => this?.name,
}
 
obj.regular() // "a fintech app"
obj.regular.call({ name: 'Other' }) // "Other"
 
obj.arrow() // undefined (lexical this)
obj.arrow.call({ name: 'Other' }) // still undefined — this NOT changed
 
// bind on arrow — creates bound function but this unchanged
const bound = obj.arrow.bind({ name: 'Other' })
bound() // undefined
javascript
// Practical rule for React class components (legacy)
this.handleClick = this.handleClick.bind(this); // regular fn — works
this.handleClick = () => { ... };               // arrow — this already correct

Back to index


193. Closures

Theory

A closure is a function bundled together with its lexical environment — the scope in which it was created. The inner function retains access to outer variables even after the outer function has finished executing.

Closures enable: data privacy, factory functions, memoization, debouncing, and module patterns.

Pros & Cons

ProsCons
Encapsulation / private variablesMemory — outer vars stay alive while closure exists
Reusable function factoriesAccidental closures in loops (var bug)
Powerful callback patternsHarder to debug deep closure chains

Real-Life Example

javascript
// Private counter — module pattern
function createCart() {
  let items = [] // private via closure
 
  return {
    add(item) {
      items.push(item)
    },
    remove(id) {
      items = items.filter((i) => i.id !== id)
    },
    getTotal() {
      return items.reduce((sum, i) => sum + i.price * i.qty, 0)
    },
    getCount() {
      return items.length
    },
  }
}
 
const cart = createCart()
cart.add({ id: 1, name: 'Biryani', price: 299, qty: 1 })
console.log(cart.getTotal()) // 299
// cart.items — undefined (private)
 
// Debounce — closure holds timer reference
function debounce(fn, delay) {
  let timerId
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
}
 
const search = debounce((q) => fetch(`/api?q=${q}`), 300)
javascript
// Memoization
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(...args)
    cache.set(key, result)
    return result
  }
}
 
const expensiveFilter = memoize((products, category) => products.filter((p) => p.category === category))

Back to index


194. Currying — Implement sum(1)(2)(3)

Theory

Currying transforms a function taking multiple arguments into a chain of functions each taking one (or more) arguments.

sum(1)(2)(3) should return 6.

Interview Answer

Currying converts f(a,b,c) into f(a)(b)(c). Each call returns a function until enough arguments are collected, then computes the result.

Real Example

javascript
// sum(1)(2)(3) → 6
function sum(a) {
  return function (b) {
    return function (c) {
      return a + b + c
    }
  }
}
console.log(sum(1)(2)(3)) // 6
 
// Generic curry — flexible arity
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args)
    }
    return (...more) => curried(...args, ...more)
  }
}
 
const add = (a, b, c) => a + b + c
const curriedAdd = curry(add)
 
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6
 
// Infinite currying sum — sum(1)(2)(3)...(n)
function infiniteSum(a) {
  const fn = (b) => infiniteSum(a + b)
  fn.valueOf = () => a
  fn.toString = () => String(a)
  return fn
}
console.log(+infiniteSum(1)(2)(3)) // 6
console.log(+infiniteSum(5)(10)(15)) // 30
javascript
// Real-life: partial application for API client
const apiCall = curry((method, baseUrl, endpoint) => fetch(`${baseUrl}${endpoint}`, { method }))
 
const credApi = apiCall('GET')('https://api.a fintech app.club')
credApi('/users/me').then((r) => r.json())

Back to index


195. Currying for Infinite Sum

Theory

Implement sum(10)(20)(30)() → 60 and sum(10)(20)(30)(40)(50)(60)() → 210.

Each call adds a number. Calling with empty parentheses () triggers the final result. This is infinite currying with an empty-call terminator.

Interview Answer

Each call returns a new function that accumulates the sum. When called with no arguments, it returns the total — I detect empty invocation as the signal to compute.

Implementation

javascript
function sum(a) {
  const fn = function (b) {
    // Empty call () → return accumulated sum
    if (arguments.length === 0) return a
    return sum(a + b)
  }
 
  // Allow sum(10)(20)(30)() — empty parens call fn with no args
  fn.valueOf = () => a
  fn.toString = () => String(a)
 
  return fn
}
 
// Tests
console.log(sum(10)(20)(30)()) // 60
console.log(sum(10)(20)(30)(40)(50)(60)()) // 210
console.log(+sum(5)(15)(25)()) // 45
console.log(sum(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)()) // 55

Alternative — explicit empty call detection

javascript
function sum(initial) {
  let total = initial
 
  function accumulator(value) {
    if (value === undefined) {
      // Called as () — return result
      const result = total
      total = 0 // reset for reuse (optional)
      return result
    }
    total += value
    return accumulator
  }
 
  // First call already set total = initial
  return function next(value) {
    if (value === undefined) return total
    total += value
    return next
  }
}
 
// Cleaner version matching interview spec exactly
function sum(a) {
  return function inner(b) {
    if (b === undefined) return a
    return sum(a + b)
  }
}
 
// Usage: must pass undefined or use () which passes undefined
console.log(sum(10)(20)(30)(undefined)) // 60 — less elegant
 
// Best: empty call detection via argument length
function sum(a) {
  const f = (b) => {
    if (arguments.length === 0) return a
    return sum(a + b)
  }
  return f
}
 
console.log(sum(10)(20)(30)()) // 60
console.log(sum(10)(20)(30)(40)(50)(60)()) // 210

Walk-through: sum(10)(20)(30)()

plaintext
sum(10)       → f, a=10
(20)          → sum(10+20=30) → f, a=30
(30)          → sum(30+30=60) → f, a=60
()            → arguments.length===0 → return 60 ✅
#TopicOne-liner
1call/apply/bindInvoke now vs bind this; polyfill via Symbol
2FlattenRecursive reduce or stack; concat if array
35 divs inlinedisplay:inline-block; font-size:0 on parent
4Sum no looparr.reduce((a,b) => a+b, 0)
5Deep vs shallowstructuredClone vs spread; nested shared
6Output puzzleSync → microtasks → macrotask
7First repeatingSet scan left; first char seen twice
8StopwatchsetInterval + elapsed offset; cleanup on stop
9Todo listReact.memo + useCallback + UUID keys
10Infinite sumCurried fn; empty () returns accumulated total

Senior Interview Tips

DoDon't
Write polyfills from scratchSay "I'd use lodash"
Explain output puzzles step by stepGuess the answer
Mention re-render optimization in TodoBuild naive list without memo
Handle edge cases (empty string, empty array)Skip cleanup in stopwatch
Clarify first repeating vs non-repeating charAssume wrong problem

These 10 questions appear repeatedly in Senior React and Tech Lead rounds. Practice writing each from scratch in under 10 minutes.

Back to index


196. defer vs async — which blocks HTML parsing?

Theory

Both <script defer> and <script async> are non-blocking downloads — the browser fetches them in parallel while continuing to parse HTML. The difference is when they execute:

AttributeDownloadExecuteBlocks parsing?
(none)Blocks parserImmediatelyYes — blocks parsing AND download
asyncParallelAs soon as downloadedNo parsing block, but execution blocks parsing
deferParallelAfter HTML fully parsed, before DOMContentLoadedNo — execution after parse complete
type="module"ParallelDeferred by defaultNo

Neither async nor defer blocks HTML parsing during download. The classic blocking script has no attribute.

Execution of async scripts can interrupt parsing when they arrive. defer waits until parsing finishes.

Pros & Cons

deferasync
✅ Preserves script order✅ Executes ASAP
✅ Safe for scripts that depend on DOM✅ Good for independent analytics
✅ Predictable — before DOMContentLoaded❌ Order not guaranteed
❌ Waits for full parse❌ Can interrupt parsing on execute

Real-Life Example

html
<!-- ❌ BLOCKS parsing — worst case -->
<script src="heavy-app.js"></script>
 
<!-- async — download parallel, execute when ready (may interrupt parse) -->
<script async src="analytics.js"></script>
 
<!-- defer — download parallel, execute after parse, in order -->
<script defer src="app.js"></script>
<script defer src="app-init.js"></script>
<!-- always runs after app.js -->
 
<!-- module — deferred by default -->
<script type="module" src="main.js"></script>
plaintext
Timeline — defer:
[Parse HTML=========>][Execute defer scripts][DOMContentLoaded][Load event]
 
Timeline — async (script arrives mid-parse):
[Parse HTML===][EXEC async][Parse HTML===][DOMContentLoaded]
                  ↑ can interrupt parsing
 
Timeline — no attribute:
[STOP parse][Download+Execute script][Resume parse...]

Interview answer (concise)

Note

Neither defer nor async blocks HTML parsing during download — both fetch in parallel. Plain <script> without attributes blocks parsing. async executes as soon as it's downloaded, which can interrupt parsing. defer waits until HTML parsing completes, then runs in order before DOMContentLoaded. Use defer for app scripts, async for independent third-party scripts.

Back to index


197. Difference between Promise.all() and Promise.allSettled()

Theory

Both run multiple promises in parallel, but handle failures differently:

Promise.all()Promise.allSettled()
Resolves whenAll promises fulfillAll promises settle (fulfill OR reject)
Rejects whenAny promise rejectsNever rejects (always resolves)
ResultArray of valuesArray of { status, value/reason }
Use whenAll results required togetherNeed every outcome regardless of failure

Pros & Cons

Promise.allPromise.allSettled
✅ Fail-fast — don't wait for doomed requests✅ Partial success — one failure doesn't block others
✅ Simple array of results✅ Perfect for independent UI sections
❌ One failure kills entire operation❌ Must check each status manually
❌ No partial results on error❌ Waits for all even after failures

Real-Life Example

javascript
// Promise.all — ALL must succeed (checkout needs user + cart + payment)
async function loadCheckout(userId) {
  try {
    const [user, cart, paymentMethods] = await Promise.all([
      fetchUser(userId),
      fetchCart(userId),
      fetchPaymentMethods(userId),
    ])
    return { user, cart, paymentMethods }
  } catch (err) {
    // One API failed — entire checkout unavailable
    showFullPageError('Unable to load checkout')
  }
}
 
// Promise.allSettled — independent sections (the company scenario!)
async function loadDashboard(userId) {
  const results = await Promise.allSettled([
    fetchAnnouncements(),
    fetchTeamMembers(userId),
    fetchRecentProjects(userId),
  ])
 
  return {
    announcements: results[0].status === 'fulfilled' ? results[0].value : null,
    team: results[1].status === 'fulfilled' ? results[1].value : null,
    projects: results[2].status === 'fulfilled' ? results[2].value : null,
    errors: results.map((r, i) => (r.status === 'rejected' ? { section: i, reason: r.reason } : null)).filter(Boolean),
  }
}
javascript
// Output shape of allSettled
;[
  { status: 'fulfilled', value: { title: 'Holiday notice' } },
  { status: 'rejected', reason: Error('503 Service Unavailable') },
  { status: 'fulfilled', value: [{ id: 1, name: 'Project Alpha' }] },
]

Key interview point: the company scenario question maps directly to Promise.allSettled() + per-section error boundaries.

Back to index


198. Event Loop — Microtasks vs Macrotasks

Theory

JavaScript is single-threaded. The event loop decides what runs next:

  1. Execute all synchronous code (call stack)
  2. Drain all microtasks (Job queue)
  3. Run one macrotask (Task queue)
  4. Repeat (render between tasks if needed)
TypeExamples
MicrotasksPromise.then, queueMicrotask, MutationObserver
MacrotaskssetTimeout, setInterval, setImmediate, I/O, UI events

Rule: After every macrotask, ALL microtasks run before the next macrotask.

Interview Answer

Sync code runs first, then all microtasks like Promise.then, then one macrotask like setTimeout. That's why Promise callbacks run before setTimeout even with 0ms delay.

Classic Interview Output

javascript
console.log('1')
 
setTimeout(() => console.log('2'), 0)
 
Promise.resolve().then(() => console.log('3'))
 
console.log('4')
 
// Output: 1 → 4 → 3 → 2
// Sync (1, 4) → Microtask (3) → Macrotask (2)

Advanced Puzzle

javascript
console.log('start')
 
setTimeout(() => console.log('timeout 1'), 0)
 
Promise.resolve()
  .then(() => {
    console.log('promise 1')
    setTimeout(() => console.log('timeout 2'), 0)
  })
  .then(() => console.log('promise 2'))
 
setTimeout(() => console.log('timeout 3'), 0)
 
console.log('end')
 
// start → end → promise 1 → promise 2 → timeout 1 → timeout 3 → timeout 2

a quick-commerce app-Relevant Example

javascript
// Why this matters in React apps
async function addToCart(product) {
  console.log('1: optimistic UI update')
  setCart((prev) => [...prev, product]) // sync state schedule
 
  console.log('2: API call starts')
  await fetch('/api/cart', { method: 'POST', body: JSON.stringify(product) })
  console.log('3: API done')
 
  Promise.resolve().then(() => console.log('4: microtask after await'))
}
 
console.log('A')
addToCart(item)
console.log('B')
 
// A → 1 → 2 → B → (microtasks) → 3 → 4

Back to index


199. Execute N callback-based async tasks in series

Theory

Running async tasks in series means each task starts only after the previous one completes. Unlike Promise.all (parallel), series execution is needed when tasks depend on each other or when the resource (API, DB) cannot handle concurrency.

The classic pattern chains callbacks or uses reduce with promises.

Pros & Cons

ProsCons
Safe for dependent operationsSlower than parallel
Respects rate limits naturallyOne failure blocks the entire chain
Predictable orderHarder to cancel mid-chain

Real-Life Example

javascript
function runSeries(tasks, finalCallback) {
  let index = 0
  const results = []
 
  function next(err, result) {
    if (err) return finalCallback(err)
    if (result !== undefined) results.push(result)
 
    if (index >= tasks.length) return finalCallback(null, results)
 
    const task = tasks[index++]
    task(next)
  }
 
  next()
}
 
// Real-life: Multi-step onboarding — each step depends on previous
runSeries(
  [
    (cb) => createAccount(userData, cb),
    (cb) => sendVerificationEmail(userData.email, cb),
    (cb) => setupDefaultPreferences(userData.id, cb),
    (cb) => redirectToDashboard(userData.id, cb),
  ],
  (err, results) => {
    if (err) console.error('Onboarding failed:', err)
    else console.log('Onboarding complete:', results)
  },
)
 
// Modern promise version
function runSeriesPromises(tasks) {
  return tasks.reduce(
    (chain, task) => chain.then((results) => task().then((result) => [...results, result])),
    Promise.resolve([]),
  )
}

Back to index


200. Explain closures with a practical example

Theory

A closure is when a function remembers and accesses variables from its lexical scope even after the outer function has finished executing.

Closures enable:

  • Data privacy / encapsulation
  • Factory functions
  • Memoization
  • Event handlers with preserved state
  • Module pattern

Memory consideration: Closures hold references to outer variables. Holding large objects in closures can cause memory leaks if not cleaned up.

Practical Example — Counter Module

javascript
function createCounter(initial = 0) {
  let count = initial // private state
 
  return {
    increment() {
      count++
    },
    decrement() {
      count--
    },
    getCount() {
      return count
    },
  }
}
 
const counter = createCounter(10)
counter.increment()
console.log(counter.getCount()) // 11
// count is NOT accessible directly — encapsulated via closure

Practical Example — Memoization (common in interviews)

javascript
function memoize(fn) {
  const cache = new Map() // closed over by returned function
 
  return function (...args) {
    const key = JSON.stringify(args)
    if (cache.has(key)) return cache.get(key)
 
    const result = fn(...args)
    cache.set(key, result)
    return result
  }
}
 
const expensiveSum = memoize((a, b) => {
  console.log('Computing...')
  return a + b
})
 
expensiveSum(2, 3) // Computing... → 5
expensiveSum(2, 3) // 5 (cached, no log)

Practical Example — React-style debounce

javascript
function debounce(fn, delay) {
  let timerId // closure holds timer reference
 
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => fn.apply(this, args), delay)
  }
}
 
const handleSearch = debounce((query) => {
  console.log('API call:', query)
}, 300)

Interview Answer

A closure is a function plus its surrounding lexical environment. The inner function retains access to outer variables after the outer function returns. I use closures for encapsulation, debouncing, memoization, and custom hooks in React.

Back to index


201. Explain currying and function composition

Currying

Transform a function with multiple arguments into a chain of functions each taking one argument.

javascript
// Regular
function add(a, b, c) {
  return a + b + c
}
add(1, 2, 3) // 6
 
// Curried
function curriedAdd(a) {
  return function (b) {
    return function (c) {
      return a + b + c
    }
  }
}
curriedAdd(1)(2)(3) // 6
 
// Generic curry utility
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args)
    }
    return (...more) => curried(...args, ...more)
  }
}
 
const multiply = (a, b, c) => a * b * c
const curriedMultiply = curry(multiply)
curriedMultiply(2)(3)(4) // 24
curriedMultiply(2, 3)(4) // 24
curriedMultiply(2)(3, 4) // 24

Function composition

Combine functions so output of one feeds into the next: compose(f, g)(x) === f(g(x)).

javascript
const compose =
  (...fns) =>
  (value) =>
    fns.reduceRight((acc, fn) => fn(acc), value)
 
const pipe =
  (...fns) =>
  (value) =>
    fns.reduce((acc, fn) => fn(acc), value)
 
const trim = (s) => s.trim()
const toLower = (s) => s.toLowerCase()
const removeSpaces = (s) => s.replace(/\s+/g, '-')
 
const slugify = compose(removeSpaces, toLower, trim)
console.log(slugify('  Hello World  ')) // "hello-world"
 
const slugifyPipe = pipe(trim, toLower, removeSpaces)
console.log(slugifyPipe('  Hello World  ')) // "hello-world"

Real-world use

javascript
// Currying — partial application for reusable handlers
const log = curry((level, message) => console[level](message))
const logError = log('error')
const logInfo = log('info')
 
logError('Something failed')
logInfo('App started')

Back to index


202. Explain lexical scope and closure in detail

Lexical scope

Scope is determined by where code is written (lexical position), not where it is called.

javascript
const globalVar = 'global'
 
function outer() {
  const outerVar = 'outer'
 
  function inner() {
    const innerVar = 'inner'
    console.log(globalVar) // accessible
    console.log(outerVar) // accessible
    console.log(innerVar) // accessible
  }
 
  return inner
}
 
const fn = outer()
fn() // still accesses outerVar — closure

Scope chain

plaintext
inner scope → outer scope → global scope

Closure — formal definition

A closure is a function bundled together with its lexical environment (scope). The environment records all local variables at the time the function was created.

javascript
function makeMultiplier(factor) {
  // `factor` is closed over
  return function (number) {
    return number * factor
  }
}
 
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
 
console.log(double(5)) // 10
console.log(triple(5)) // 15
// Each closure has its own `factor`

Module pattern (classic closure use)

javascript
const bankAccount = (function () {
  let balance = 0 // private via closure
 
  return {
    deposit(amount) {
      balance += amount
    },
    getBalance() {
      return balance
    },
  }
})()
 
bankAccount.deposit(100)
console.log(bankAccount.getBalance()) // 100
// balance is not accessible directly

Loop + closure classic fix

javascript
// Problem
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100) // 3, 3, 3
}
 
// Fix with let (block scope per iteration)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100) // 0, 1, 2
}
 
// Fix with IIFE closure
for (var i = 0; i < 3; i++) {
  ;((j) => {
    setTimeout(() => console.log(j), 100)
  })(i)
}

Back to index


203. Garbage Collection

Theory

JS uses automatic garbage collection. The main algorithm is mark-and-sweep: mark all objects reachable from roots (global, call stack, closures), sweep unmarked objects.

V8 uses generational GC: young generation (Scavenge, frequent) and old generation (Mark-Sweep-Compact, less frequent).

Common leak causes

  • Global variables holding references
  • Forgotten timers / intervals
  • Event listeners not removed
  • Closures holding large objects
  • Detached DOM nodes referenced in JS

Interview Answer

GC frees memory for objects no longer reachable from roots. Leaks happen when we keep references accidentally — timers, listeners, closures holding large data.

Real Example

javascript
// ❌ Leak
const cache = {}
function loadUser(id) {
  cache[id] = fetchHugeUserObject(id) // grows forever
}
 
// ✅ Bounded cache
const cache = new Map()
const MAX = 100
function loadUserCached(id) {
  if (cache.has(id)) return cache.get(id)
  const data = fetchUser(id)
  if (cache.size >= MAX) cache.delete(cache.keys().next().value)
  cache.set(id, data)
  return data
}
 
// ✅ WeakMap — doesn't prevent GC of keys
const domData = new WeakMap()
function attach(el, data) {
  domData.set(el, data)
}
// When el removed from DOM and unreferenced, entry is GC'd

Back to index


204. How do map() and bind() methods work?

Theory

These are different concepts that interviewers often group together:

Array.prototype.map() — creates a new array by transforming each element through a callback. Does not mutate the original array.

Function.prototype.bind() — creates a new function with a fixed this context and optionally pre-filled arguments. Does not invoke the function immediately.

Pros & Cons

map()bind()
✅ Immutable transformation✅ Fixes this in callbacks
✅ Declarative, chainable✅ Partial application
❌ Creates new array (memory)❌ Less needed with arrow functions/hooks
❌ Can't short-circuit early❌ Slight memory overhead per bound function

Real-Life Example — map()

javascript
const orders = [
  { id: 1, item: 'Biryani', price: 299 },
  { id: 2, item: 'Naan', price: 49 },
  { id: 3, item: 'Lassi', price: 79 },
]
 
// Transform API data for UI display
const orderSummary = orders.map((order) => ({
  label: `${order.item} — ₹${order.price}`,
  id: order.id,
}))
// [{ label: "Biryani — ₹299", id: 1 }, ...]
 
// React — map is the primary way to render lists
function OrderList({ orders }) {
  return (
    <ul>
      {orders.map((order) => (
        <li key={order.id}>
          {order.item} — ₹{order.price}
        </li>
      ))}
    </ul>
  )
}

Real-Life Example — bind()

javascript
const deliveryPartner = {
  name: 'Rahul',
  getStatus(orderId) {
    return `${this.name} is delivering order #${orderId}`
  },
}
 
// bind — fix `this` and optionally pre-fill args
const getStatusForOrder = deliveryPartner.getStatus.bind(deliveryPartner)
getStatusForOrder(1042) // "Rahul is delivering order #1042"
 
// Partial application
const greet = function (greeting, name) {
  return `${greeting}, ${name}!`
}
const sayHello = greet.bind(null, 'Hello')
sayHello('Amit') // "Hello, Amit!"
 
// Class component (legacy) — bind in constructor
class OrderForm extends React.Component {
  constructor(props) {
    super(props)
    this.handleSubmit = this.handleSubmit.bind(this)
  }
  handleSubmit() {
    this.props.onSubmit(this.state) // `this` is correct
  }
}
 
// Modern React — arrow functions replace bind
function OrderForm({ onSubmit }) {
  const [data, setData] = useState({})
  const handleSubmit = () => onSubmit(data) // no bind needed
}

Back to index


205. How does garbage collection work in JavaScript?

Theory

JS uses automatic garbage collection — memory for unreachable objects is reclaimed.

Primary algorithm: Mark-and-Sweep

plaintext
1. GC roots: global object, call stack variables, closures
2. Mark: traverse all reachable objects from roots
3. Sweep: free memory of unmarked (unreachable) objects

Generational GC (V8)

GenerationCharacteristicsCollection frequency
Young (nursery)Short-lived objectsFrequent (Scavenge)
OldLong-lived, survived collectionsLess frequent (Mark-Sweep-Compact)

Objects that survive two Scavenge cycles are promoted to old generation.

What causes memory leaks

javascript
// 1. Global variables
window.cache = hugeArray
 
// 2. Forgotten timers
setInterval(() => {
  /* holds closure ref */
}, 1000)
 
// 3. Detached DOM nodes still referenced
const el = document.getElementById('btn')
document.body.removeChild(el)
// el still in memory if referenced in JS
 
// 4. Closures holding large objects
function createHandler() {
  const bigData = new Array(1e6).fill('x')
  return () => console.log(bigData.length) // bigData never freed
}
 
// 5. Event listeners not removed
element.addEventListener('click', handler)
// forgot removeEventListener on cleanup

WeakMap/WeakSet help avoid leaks by not preventing GC of keys.

Back to index


206. How does the JavaScript Event Loop work?

Theory

JavaScript is single-threaded — one call stack. The event loop coordinates:

  1. Call stack — synchronous code execution
  2. Web APIs — setTimeout, fetch, DOM (run outside main thread)
  3. Microtask queue — Promises, queueMicrotask
  4. Macrotask queue — setTimeout, I/O, UI events

Order: Run all sync code → drain all microtasks → run one macrotask → repeat.

Pros & Cons

Understanding event loopIgnoring it
Predict async behaviorSurprised by Promise/setTimeout order
Avoid blocking UILong sync code freezes app
Debug race conditionsStale closure bugs in async

Interview Answer

The event loop manages JS's single thread. Sync code runs first, then all microtasks, then one macrotask. That's why Promises resolve before setTimeout.

Real Example

javascript
console.log('1')
setTimeout(() => console.log('4'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('2')
// Output: 1 → 2 → 3 → 4
javascript
// Senior insight — don't block the loop
function processPayments(transactions) {
  // ❌ Blocks UI for 2 seconds
  transactions.forEach(heavyValidation)
 
  // ✅ Chunk with macrotasks
  let i = 0
  function chunk() {
    const end = Math.min(i + 100, transactions.length)
    for (; i < end; i++) heavyValidation(transactions[i])
    if (i < transactions.length) setTimeout(chunk, 0)
  }
  chunk()
}

Back to index


207. How to control tab order in DOM (explain tabIndex)

Theory

Tab order determines which element receives focus when the user presses Tab. By default, focusable elements (links, buttons, inputs) are tabbed in DOM order.

tabIndex controls this:

  • tabIndex="0" — element is focusable and in natural tab order
  • tabIndex="-1" — focusable programmatically (focus()) but not in tab order
  • tabIndex="1+" — focusable and tabbed before natural order (avoid — confuses users)

Pros & Cons

ApproachProsCons
Natural DOM orderAccessible by defaultLayout changes can break logical order
tabIndex={0} on custom elementsMakes divs/spans keyboard-accessibleMust add keyboard handlers too
tabIndex={-1} for modalsFocus trap without tab pollutionRequires focus management code
Positive tabIndex—Never use — unpredictable order

Real-Life Example

jsx
function CheckoutModal({ isOpen, onClose }) {
  const firstFocusableRef = useRef(null)
  const lastFocusableRef = useRef(null)
 
  useEffect(() => {
    if (isOpen) firstFocusableRef.current?.focus()
  }, [isOpen])
 
  const handleTabKey = (e) => {
    if (e.key !== 'Tab') return
    if (e.shiftKey && document.activeElement === firstFocusableRef.current) {
      e.preventDefault()
      lastFocusableRef.current?.focus()
    } else if (!e.shiftKey && document.activeElement === lastFocusableRef.current) {
      e.preventDefault()
      firstFocusableRef.current?.focus()
    }
  }
 
  return (
    <div role="dialog" aria-modal="true" onKeyDown={handleTabKey}>
      <button ref={firstFocusableRef} tabIndex={0}>
        Continue
      </button>
      <button tabIndex={0}>Cancel</button>
      <button ref={lastFocusableRef} tabIndex={0} onClick={onClose}>
        Close
      </button>
    </div>
  )
}
 
// Skip navigation link (accessibility best practice)
;<a href="#main-content" className="sr-only focus:not-sr-only" tabIndex={0}>
  Skip to main content
</a>

Back to index


208. How to override toString on String.prototype

Theory

String.prototype.toString returns the string value. You can override it, but mutating built-in prototypes is strongly discouraged — it breaks libraries, causes conflicts, and is considered bad practice. Prefer utility functions or wrapper classes instead.

If asked in an interview, show you know how but also know why not to.

Pros & Cons

ProsCons
Global behavior changeBreaks other libraries expecting default behavior
Theoretical knowledgeSecurity risk in shared environments
—Hard to debug, non-standard

Real-Life Example

javascript
// ❌ Don't do this in production
String.prototype.toString = function () {
  return `🎉 ${this.valueOf()}`
}
 
console.log('hello'.toString()) // "🎉 hello"
// Breaks: JSON.stringify, template literals, comparisons
 
// ✅ Better approach — utility function
function displayString(str) {
  return `🎉 ${str}`
}
 
// ✅ Or a wrapper class
class DisplayString {
  constructor(value) {
    this.value = value
  }
  toString() {
    return `🎉 ${this.value}`
  }
}

Back to index


209. Implement Array.prototype.reduce polyfill

Theory

Array.prototype.reduce executes a reducer function on each element, carrying an accumulator from left to right, and returns a single final value. It is the most flexible array method — it can implement map, filter, flat, and more.

The reducer receives (accumulator, currentValue, index, array) and must return the next accumulator.

Pros & Cons

ProsCons
Extremely versatile — one method, many patternsHarder to read than map/filter for beginners
No intermediate arrays (unlike chaining)Easy to write inefficient reducers
Works on sparse arrays with proper checksCannot short-circuit early (unlike for loop with break)

Real-Life Example

javascript
Array.prototype.myReduce = function (callback, initialValue) {
  if (this == null) throw new TypeError('Array is null or undefined')
  if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function')
 
  const arr = Object(this)
  const len = arr.length >>> 0
  let accumulator
  let startIndex = 0
 
  if (arguments.length >= 2) {
    accumulator = initialValue
  } else {
    while (startIndex < len && !(startIndex in arr)) startIndex++
    if (startIndex >= len) {
      throw new TypeError('Reduce of empty array with no initial value')
    }
    accumulator = arr[startIndex++]
  }
 
  for (let i = startIndex; i < len; i++) {
    if (i in arr) {
      accumulator = callback(accumulator, arr[i], i, arr)
    }
  }
 
  return accumulator
}
 
// Real-life: Calculate total order value with tax and discounts
const order = [
  { name: 'Biryani', price: 299, qty: 2 },
  { name: 'Naan', price: 49, qty: 4 },
]
 
const total = order.myReduce((sum, item) => sum + item.price * item.qty, 0)
// 299*2 + 49*4 = 794

Back to index


210. Implement auto-retry for promises

Theory

Auto-retry wraps an async operation to automatically re-attempt on failure, typically with exponential backoff and optional jitter. This handles transient failures — network blips, 503 errors, rate limits — without user intervention.

Key parameters: max retries, base delay, backoff multiplier, which errors are retryable.

Pros & Cons

ProsCons
Improves reliability on flaky networksCan amplify load on failing servers
Better UX — fewer visible errorsNon-idempotent POST requests can duplicate data
Standard pattern in production systemsNeeds careful error classification

Real-Life Example

javascript
async function retry(fn, { retries = 3, delay = 1000, backoff = 2 } = {}) {
  let lastError
 
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn()
    } catch (err) {
      lastError = err
      if (attempt === retries) break
 
      const wait = delay * Math.pow(backoff, attempt) + Math.random() * 100
      await new Promise((r) => setTimeout(r, wait))
    }
  }
 
  throw lastError
}
 
// Real-life: Retry payment status check after checkout
async function checkPaymentStatus(orderId) {
  return retry(
    () =>
      fetch(`/api/orders/${orderId}/status`).then((r) => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`)
        return r.json()
      }),
    { retries: 5, delay: 500, backoff: 2 },
  )
}

Back to index


211. Implement Promise.all polyfill

Theory

Promise.all takes an iterable of promises (or values) and returns a single promise that resolves when all inputs resolve, with results in the same order as inputs. If any promise rejects, the entire operation rejects immediately with that error.

It is the standard pattern for parallel async work where every result is required — like loading user profile, permissions, and preferences before rendering a dashboard.

Pros & Cons

ProsCons
Parallel execution — faster than sequentialFails entirely if one request fails
Preserves result orderNo partial results on failure
Simple mental modelAll requests run even after first failure (until rejection propagates)

Real-Life Example

javascript
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('promises must be an array'))
    }
 
    const results = []
    let completed = 0
    const len = promises.length
 
    if (len === 0) return resolve([])
 
    promises.forEach((p, index) => {
      Promise.resolve(p)
        .then((value) => {
          results[index] = value
          completed++
          if (completed === len) resolve(results)
        })
        .catch(reject)
    })
  })
}
 
// Real-life: Load checkout page data in parallel
async function loadCheckoutPage(userId) {
  const [cart, addresses, paymentMethods] = await promiseAll([
    fetch(`/api/cart/${userId}`).then((r) => r.json()),
    fetch(`/api/addresses/${userId}`).then((r) => r.json()),
    fetch(`/api/payments/${userId}`).then((r) => r.json()),
  ])
  return { cart, addresses, paymentMethods }
}

Back to index


212. Implement Promise.any polyfill

Theory

Promise.any takes an iterable of promises and returns a promise that resolves with the first fulfilled result. It only rejects if all promises reject — with an AggregateError containing all rejection reasons.

Use it when you have multiple fallback sources and only need one success — CDN mirrors, redundant API endpoints, or fastest-wins scenarios.

Pros & Cons

ProsCons
Resilient — one success is enoughWaits for all to fail before rejecting
Great for redundancy / racing sourcesAll requests still consume bandwidth
Ignores individual failuresNo built-in timeout (must add manually)

Real-Life Example

javascript
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises) || promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'))
    }
 
    const errors = []
    let rejectedCount = 0
 
    promises.forEach((p, index) => {
      Promise.resolve(p)
        .then(resolve)
        .catch((err) => {
          errors[index] = err
          rejectedCount++
          if (rejectedCount === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'))
          }
        })
    })
  })
}
 
// Real-life: Fetch image from fastest CDN mirror
async function loadProductImage(productId) {
  const imageUrl = await promiseAny([
    fetch(`https://cdn1.example.com/images/${productId}`),
    fetch(`https://cdn2.example.com/images/${productId}`),
    fetch(`https://cdn3.example.com/images/${productId}`),
  ])
  return imageUrl.blob()
}

Back to index


213. Implement your own version of Promise.all()

Theory

Promise.all(iterable) returns a single promise that:

  • Resolves with an array of results when all input promises resolve (order preserved)
  • Rejects immediately when any input promise rejects

Implementation

javascript
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'))
    }
 
    const results = []
    let completed = 0
    const len = promises.length
 
    if (len === 0) return resolve([])
 
    promises.forEach((promise, index) => {
      Promise.resolve(promise) // handles non-promise values
        .then((value) => {
          results[index] = value // preserve order
          completed++
          if (completed === len) resolve(results)
        })
        .catch(reject) // fail fast
    })
  })
}
 
// Tests
promiseAll([Promise.resolve(1), 42, Promise.resolve(3)]).then(console.log) // [1, 42, 3]
 
promiseAll([Promise.resolve(1), Promise.reject('error')]).catch(console.log) // "error"

Edge cases to mention

  • Empty array → resolves []
  • Non-promise values are wrapped via Promise.resolve()
  • Order of results matches input order, not completion order

Back to index


214. JavaScript Event Loop

Theory

JS is single-threaded. The event loop coordinates: call stack → microtasks (Promises) → macrotasks (setTimeout).

Interview Answer

Sync code runs first, then all microtasks like Promises, then one macrotask like setTimeout. That's why Promise.then runs before setTimeout.

Real Example

javascript
console.log('1')
setTimeout(() => console.log('4'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('2')
// 1 → 2 → 3 → 4

Back to index


215. Polyfill for Array.map()

javascript
Array.prototype.myMap = function (callback, thisArg) {
  if (this == null) {
    throw new TypeError('Array.prototype.myMap called on null or undefined')
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function')
  }
 
  const array = Object(this)
  const len = array.length >>> 0
  const result = new Array(len)
 
  for (let i = 0; i < len; i++) {
    if (i in array) {
      result[i] = callback.call(thisArg, array[i], i, array)
    }
  }
 
  return result
}
 
// Test
console.log([1, 2, 3].myMap((x) => x * 2)) // [2, 4, 6]
console.log([1, , 3].myMap((x) => x * 2)) // [2, empty, 6] — sparse handled

Key details

  • Handle this being array-like (not just arrays)
  • Use >>> 0 for length (handles negatives)
  • Check i in array for sparse arrays
  • Don't mutate original array

Back to index


216. Polyfill for Function.bind()

javascript
Function.prototype.myBind = function (thisArg, ...boundArgs) {
  const fn = this
 
  if (typeof fn !== 'function') {
    throw new TypeError('Bind must be called on a function')
  }
 
  const boundFunction = function (...callArgs) {
    // If called with `new`, `this` should be the new instance
    const isNew = this instanceof boundFunction
    return fn.apply(isNew ? this : thisArg, boundArgs.concat(callArgs))
  }
 
  // Preserve prototype for `new` calls
  if (fn.prototype) {
    boundFunction.prototype = Object.create(fn.prototype)
  }
 
  return boundFunction
}
 
// Test
function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`
}
 
const person = { name: 'Amit' }
const boundGreet = greet.myBind(person, 'Hello')
console.log(boundGreet('!')) // "Hello, Amit!"
 
// Constructor case
function Person(name) {
  this.name = name
}
const BoundPerson = Person.myBind(null)
const p = new BoundPerson('Rahul')
console.log(p.name) // "Rahul"

Back to index


217. Polyfills & Babel

Theory

Babel is a JavaScript compiler/transpiler that converts modern JS (ES2022+) into code that older browsers understand. It handles syntax transformation (arrow functions, classes, async/await).

Polyfills add missing API implementations at runtime (e.g., Promise, Array.prototype.flat, fetch). Babel handles syntax; polyfills handle APIs.

@babel/preset-env uses browserslist to determine which transforms and polyfills are needed.

Pros & Cons

Babel + polyfillsShip modern JS only
✅ Works on older browsers✅ Smaller bundle
✅ Use latest syntax today❌ Breaks on IE/old Safari
❌ Increases bundle size❌ Limits API usage
❌ Build step required

Real-Life Example

javascript
// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "usage",  // auto-import only needed polyfills
      "corejs": 3
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}
javascript
// Modern code you write
const greet = async (name) => {
  const data = await fetch(`/api/users/${name}`).then((r) => r.json())
  return data.items?.flat() ?? []
}
 
// Babel transforms syntax (async/await → regenerator)
// core-js polyfills: Promise, Array.prototype.flat, optional chaining
javascript
// Manual polyfill example — Promise.all
if (!Promise.all) {
  Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
      const results = []
      let done = 0
      promises.forEach((p, i) => {
        Promise.resolve(p)
          .then((v) => {
            results[i] = v
            if (++done === promises.length) resolve(results)
          })
          .catch(reject)
      })
    })
  }
}
json
// browserslist — controls Babel output
{
  "browserslist": [">0.2%", "not dead", "not ie 11"]
}

Babel vs Polyfill:

BabelPolyfill
HandlesSyntax (arrow, class, async)Missing APIs (Promise, fetch, flat)
WhenBuild timeRuntime
Exampleconst fn = () => {} → var fn = function(){}Adds Array.prototype.flat if missing
#TopicOne-liner
1PaginationOffset or cursor; shareable pages
2Infinite scrollIntersection Observer + cursor API
3DebouncingWait for pause before executing
4WebSocketPersistent bidirectional real-time connection
5REST vs GraphQLFixed endpoints vs client-defined queries
6localStorage vs Cookies5MB client-only vs 4KB auto-sent; httpOnly for auth
7AuthN vs AuthZWho are you? vs What can you do?
8Reduxdispatch → reducer → store → UI
9Lazy loadingDefer until needed — lazy(), loading="lazy"
10Code splittingDynamic import → separate chunks
11Bundle optimizationAnalyze, split, import selectively, <200KB
12Tree shakingESM dead code elimination at build time
13MemoizationuseMemo=value, useCallback=fn, memo=component
14CachingHTTP headers, CDN, React Query, Service Worker
15CSR/SSR/SSG/ISRBrowser / server / build / build+revalidate
16Web VitalsLCP≤2.5s, INP≤200ms, CLS≤0.1
17Cross-browserCan I Use, Autoprefixer, polyfills, browserslist
18Optimistic UIUpdate immediately, rollback on failure
19SuspenseDeclarative fallback while loading
20ImagesAVIF → WebP → JPEG; srcset, lazy, dimensions
21a11ySemantic HTML, ARIA, keyboard, WCAG AA
22WebpackModule bundler — entry, loaders, plugins, chunks
23Micro-frontendsIndependent deployable frontend apps
24TestingJest=unit, RTL=component, Playwright=E2E
25Babel/PolyfillsBabel=syntax transform; polyfill=missing APIs

Back to index


218. Promise & Async/Await Output Puzzle

Theory

Remember: sync first → all microtasks (Promises, await) → one macrotask (setTimeout).

Senior interviews give multi-layer puzzles mixing async/await, Promise chains, and setTimeout.

Puzzle 1 — Classic

javascript
console.log('1')
 
setTimeout(() => console.log('2'), 0)
 
Promise.resolve().then(() => console.log('3'))
 
console.log('4')

Output: 1 → 4 → 3 → 2

Why: Sync (1, 4) → microtask (3) → macrotask (2)

Puzzle 2 — async/await

javascript
async function foo() {
  console.log('A')
  await Promise.resolve()
  console.log('B')
}
 
console.log('C')
foo()
console.log('D')

Output: C → A → D → B

Why: await pauses foo after A; D runs synchronously; B resumes as microtask.


Puzzle 3 — Nested promises

javascript
console.log('start')
 
setTimeout(() => console.log('timeout'), 0)
 
Promise.resolve()
  .then(() => {
    console.log('promise 1')
    return Promise.resolve()
  })
  .then(() => console.log('promise 2'))
 
Promise.resolve().then(() => console.log('promise 3'))
 
console.log('end')

Output: start → end → promise 1 → promise 3 → promise 2 → timeout


Puzzle 4 — async function return

javascript
async function getData() {
  return 42
}
 
console.log(getData())
getData().then(console.log)

Output: Promise { 42 } → 42

Why: async functions always return a Promise.


Puzzle 5 — Senior trap

javascript
async function run() {
  console.log(1)
  await null
  console.log(2)
  await null
  console.log(3)
}
 
run()
console.log(4)

Output: 1 → 4 → 2 → 3

Back to index


219. Promises

Theory

A Promise represents a value that may be available now, later, or never. It has three states:

  • Pending — initial state
  • Fulfilled — operation succeeded
  • Rejected — operation failed

Once settled (fulfilled or rejected), a promise cannot change state. .then() handles success, .catch() handles errors, .finally() runs regardless.

Pros & Cons

PromisesCallbacks
✅ Avoid callback hell✅ Simple for one-off async
✅ .then chain is readable❌ Pyramid of doom
✅ Promise.all, race, any❌ Hard error handling
❌ Still need careful error handling

Real-Life Example

javascript
// Creating promises
function fetchOrder(orderId) {
  return fetch(`/api/orders/${orderId}`).then((res) => {
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return res.json()
  })
}
 
// Chaining
fetchOrder('ORD-42')
  .then((order) => enrichWithDriverInfo(order))
  .then((order) => renderOrderUI(order))
  .catch((err) => showErrorToast(err.message))
  .finally(() => hideSpinner())
 
// Promise.all — parallel, all must succeed
const [user, cart, addresses] = await Promise.all([fetchUser(userId), fetchCart(userId), fetchAddresses(userId)])
 
// Promise.race — first to settle wins
const result = await Promise.race([
  fetchFromPrimaryCDN(url),
  timeout(5000), // reject after 5s
])
 
// Custom Promise.all polyfill concept
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = []
    let done = 0
    if (!promises.length) return resolve([])
    promises.forEach((p, i) => {
      Promise.resolve(p)
        .then((val) => {
          results[i] = val
          if (++done === promises.length) resolve(results)
        })
        .catch(reject)
    })
  })
}

Back to index


220. Prototype & Inheritance

Theory

Every JavaScript object has an internal link to another object called its prototype ([[Prototype]], accessible via Object.getPrototypeOf() or __proto__). When you access a property, JavaScript walks the prototype chain until it finds the property or reaches null.

Constructor functions (pre-ES6) and classes (ES6 syntactic sugar) create objects that inherit from a shared prototype.

Pros & Cons

Prototypal inheritanceClassical inheritance (Java/C++)
✅ Flexible — objects inherit from objects❌ Rigid class hierarchies
✅ Dynamic — add methods at runtime
✅ Memory efficient — shared methods on prototype
❌ Confusing this and prototype chain

Real-Life Example

javascript
// Constructor + prototype (ES5 style)
function Restaurant(name, cuisine) {
  this.name = name
  this.cuisine = cuisine
}
 
Restaurant.prototype.getDetails = function () {
  return `${this.name} (${this.cuisine})`
}
 
Restaurant.prototype.isOpen = function () {
  return this.status === 'open'
}
 
const r1 = new Restaurant('Spice Garden', 'North Indian')
const r2 = new Restaurant('Dosa Corner', 'South Indian')
 
console.log(r1.getDetails()) // "Spice Garden (North Indian)"
// getDetails lives on Restaurant.prototype — shared, not duplicated
 
// ES6 class — syntactic sugar over prototypes
class Order {
  constructor(id, items) {
    this.id = id
    this.items = items
  }
 
  getTotal() {
    return this.items.reduce((sum, i) => sum + i.price, 0)
  }
}
 
class DeliveryOrder extends Order {
  constructor(id, items, address) {
    super(id, items)
    this.address = address
  }
 
  getTotal() {
    return super.getTotal() + 40 // delivery fee
  }
}
 
const order = new DeliveryOrder('ORD-1', [{ price: 299 }])
console.log(order.getTotal()) // 339
 
// Prototype chain check
order instanceof DeliveryOrder // true
order instanceof Order // true
order.hasOwnProperty('id') // true
order.hasOwnProperty('getTotal') // false (on prototype)

Back to index


221. Prototypes and Prototype Chain

Theory

Every JS object has an internal link [[Prototype]] (accessed via Object.getPrototypeOf() or __proto__). When you access a property, JS walks the prototype chain until it finds the property or reaches null.

Functions have a prototype property used when called with new.

Interview Answer

Every object inherits from another via its prototype link. Property lookup walks the chain up until found or null — that's prototypal inheritance.

Real Example

javascript
function Transaction(amount, type) {
  this.amount = amount;
  this.type = type;
}
 
Transaction.prototype.getFormatted = function () {
  return `₹${this.amount} (${this.type})`;
};
 
Transaction.prototype.isCredit = function () {
  return this.type === "credit";
};
 
const txn = new Transaction(500, "credit");
console.log(txn.getFormatted()); // "₹500 (credit)"
 
// Prototype chain
txn → Transaction.prototype → Object.prototype → null
 
console.log(txn.hasOwnProperty("amount"));  // true
console.log(txn.hasOwnProperty("getFormatted")); // false — on prototype
 
// ES6 class — syntactic sugar over prototypes
class Payment {
  constructor(amount) { this.amount = amount; }
  getFormatted() { return `₹${this.amount}`; }
}
javascript
// Senior: Object.create for pure prototypal inheritance
const animal = {
  breathe() {
    return 'breathing'
  },
}
const dog = Object.create(animal)
dog.bark = () => 'woof'
dog.breathe() // "breathing" — inherited

Back to index


222. var, let & const

Theory

var, let, and const are three ways to declare variables in JavaScript. They differ in scope, hoisting behavior, and reassignment rules.

  • var — function-scoped, hoisted as undefined, can be redeclared
  • let — block-scoped, hoisted but in TDZ until declared, cannot be redeclared
  • const — block-scoped, same as let but cannot be reassigned (objects/arrays can still be mutated)

TDZ (Temporal Dead Zone): The period between entering a scope and the line where let/const is declared. Accessing the variable during TDZ throws ReferenceError.

Pros & Cons

varletconst
ProsWorks everywhere (legacy)Block scope, no redeclare bugsIntent is clear — won't reassign
ConsHoisting bugs, no block scope—Can't reassign (even if you need to)
Use whenNever in modern codeValue will changeDefault choice

Real-Life Example

javascript
// Block scope
function processOrder() {
  if (true) {
    var oldWay = 'var' // function-scoped
    let newWay = 'let' // block-scoped
    const API = 'v2' // block-scoped, constant binding
  }
  console.log(oldWay) // "var" — leaked out
  // console.log(newWay); // ReferenceError
}
 
// const does NOT freeze objects
const cart = { items: [], total: 0 }
cart.items.push({ id: 1, name: 'Biryani' }) // ✅ mutation OK
cart.total = 299 // ✅ mutation OK
// cart = {};                                 // ❌ TypeError — rebinding
 
// Classic var loop bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100) // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100) // 0, 1, 2
}

Rule: Use const by default → let when reassignment is needed → avoid var.

Back to index


223. var, let, and const

Theory

varletconst
ScopeFunctionBlockBlock
HoistingundefinedTDZTDZ
RedeclareYesNoNo
ReassignYesYesNo (binding)
Global object propYes (window)NoNo

const prevents rebinding, not mutation of objects.

Interview Answer

Use const by default, let when reassignment is needed, avoid var. let/const are block-scoped and have TDZ; const objects can still be mutated.

Real Example

javascript
const config = { apiUrl: '/api/v2', retries: 3 }
config.retries = 5 // ✅ mutation OK
// config = {};      // ❌ TypeError
 
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100) // 0, 1, 2
}
for (var j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100) // 3, 3, 3
}

Back to index


224. What are WeakMap and WeakSet?

Theory

Map / SetWeakMap / WeakSet
KeysAny typeObjects only
GCStrong references — prevent GCWeak references — don't prevent GC
IterableYesNo
Size propertyYesNo

WeakMap

javascript
const wm = new WeakMap()
 
let user = { id: 1, name: 'Amit' }
wm.set(user, { lastLogin: Date.now() })
 
console.log(wm.get(user)) // { lastLogin: ... }
 
user = null // object can now be garbage collected
// WeakMap entry disappears automatically

WeakSet

javascript
const visited = new WeakSet()
 
function processNode(node) {
  if (visited.has(node)) return
  visited.add(node)
  // process...
}

Practical use cases

javascript
// 1. Private data associated with DOM nodes
const privateData = new WeakMap()
 
class Component {
  constructor(el) {
    privateData.set(el, { listeners: [] })
  }
}
 
// 2. Caching metadata without memory leaks
const cache = new WeakMap()
 
function getMetadata(obj) {
  if (!cache.has(obj)) {
    cache.set(obj, computeExpensiveMetadata(obj))
  }
  return cache.get(obj)
}
 
// 3. Track processed objects in recursive algorithms
const seen = new WeakSet()
function deepTraverse(obj) {
  if (seen.has(obj)) return
  seen.add(obj)
  // ...
}

Back to index


225. What happens behind the scenes when async/await executes?

Syntax sugar over Promises + Generators

javascript
async function fetchUser() {
  const res = await fetch('/api/user')
  const data = await res.json()
  return data
}
 
// Roughly equivalent to:
function fetchUser() {
  return fetch('/api/user').then((res) => res.json())
}

Execution flow

plaintext
1. async function ALWAYS returns a Promise
 
2. Code before first `await` runs synchronously
 
3. At `await`:
   - Expression is evaluated
   - If not a Promise, wrapped in Promise.resolve()
   - Execution of async function PAUSES
   - Control returns to caller
   - `.then()` handler registered on the promise
 
4. When awaited promise resolves:
   - Function resumes after await
   - Resolved value assigned to variable
   - Runs on microtask queue
 
5. If awaited promise rejects:
   - Function throws (unless try/catch)
   - Returned promise rejects

Microtask queue demonstration

javascript
console.log('1')
 
async function demo() {
  console.log('2')
  await Promise.resolve()
  console.log('3') // microtask — after sync code
}
 
demo()
console.log('4')
 
// Output: 1, 2, 4, 3

Error handling

javascript
async function risky() {
  try {
    const data = await fetch('/api').then((r) => {
      if (!r.ok) throw new Error('HTTP error')
      return r.json()
    })
    return data
  } catch (err) {
    console.error('Caught:', err.message)
    throw err // re-throw to caller
  }
}

Interview answer

Note

async/await is syntactic sugar over Promises. An async function returns a Promise. Each await pauses execution, registers a microtask, and resumes when the promise settles. Code before the first await runs synchronously; code after runs in the microtask queue.

Back to index


226. What is createAsyncThunk?

Answer

It is used to handle asynchronous logic like API calls. It auto-generates pending, fulfilled, and rejected action types.

Back to index


227. What is a Promise?

Theory

A Promise is an object representing the eventual result of an asynchronous operation. It has three states:

  • Pending — initial state, operation in progress
  • Fulfilled — operation completed successfully
  • Rejected — operation failed

Once settled (fulfilled or rejected), a promise cannot change state. Promises chain with .then(), .catch(), .finally().

Pros & Cons

PromisesCallbacks
✅ Avoid callback hell✅ Simple for one-off async
✅ Composable — all, race, allSettled❌ Error handling is painful
✅ Clear success/error paths❌ Pyramid of doom
❌ Still need discipline for errors

Real-Life Example

javascript
// Creating a promise
function fetchEmployee(id) {
  return new Promise((resolve, reject) => {
    fetch(`/api/employees/${id}`)
      .then((res) => {
        if (!res.ok) reject(new Error(`HTTP ${res.status}`))
        else resolve(res.json())
      })
      .catch(reject)
  })
}
 
// Consuming
fetchEmployee('E101')
  .then((employee) => renderProfile(employee))
  .catch((err) => showErrorToast(err.message))
  .finally(() => hideSpinner())
 
// async/await — syntactic sugar over promises
async function loadProfile(id) {
  try {
    const employee = await fetchEmployee(id)
    renderProfile(employee)
  } catch (err) {
    showErrorToast(err.message)
  } finally {
    hideSpinner()
  }
}

Back to index


228. What is Closure?

Theory

A closure is a function bundled with its lexical environment — the scope where it was created. The inner function retains access to outer variables even after the outer function has finished executing.

Closures enable: data privacy, factory functions, memoization, debouncing, and module patterns.

Pros & Cons

ProsCons
Encapsulation / private stateMemory — outer vars stay alive while closure exists
Powerful callback patternsAccidental leaks if not cleaned up
Module pattern without classesHarder to debug deep chains

Interview Answer

A closure is when a function remembers variables from its outer scope even after that outer function returns. I use closures for privacy, debouncing, and memoization.

Real Example

javascript
function createWallet(initialBalance) {
  let balance = initialBalance // private via closure
 
  return {
    deposit(amount) {
      balance += amount
    },
    withdraw(amount) {
      if (amount > balance) throw new Error('Insufficient funds')
      balance -= amount
    },
    getBalance() {
      return balance
    },
  }
}
 
const wallet = createWallet(1000)
wallet.deposit(500)
console.log(wallet.getBalance()) // 1500
// balance is NOT accessible directly — encapsulated
javascript
// React-relevant: debounce hook uses closure
function useDebounce(callback, delay) {
  let timerId // closed over
  return function (...args) {
    clearTimeout(timerId)
    timerId = setTimeout(() => callback(...args), delay)
  }
}

Back to index


229. What is Hoisting in JavaScript?

Theory

Hoisting is JavaScript's behavior of processing declarations before executing code. During the creation phase of an execution context, the engine registers variable and function declarations in memory — before the first line runs.

Important: Only declarations are hoisted, not initializations.

DeclarationHoisted as
var xundefined
let x / const xuninitialized (TDZ)
function foo(){}full function
var fn = function(){}var fn → undefined
class MyClassuninitialized (TDZ)

Pros & Cons

ProsCons
Function declarations callable before definitionvar hoisting causes subtle bugs
Flexible code organizationMisleading mental model ("moved to top")
Easy to use variables before assignment

Real-Life Example

javascript
// var hoisting
console.log(status) // undefined (not ReferenceError)
var status = 'active'
 
// Equivalent to:
// var status;
// console.log(status);
// status = "active";
 
// Function declaration — fully hoisted
authenticate()
function authenticate() {
  console.log('User authenticated')
}
 
// Function expression — only var hoisted
// processOrder(); // TypeError: processOrder is not a function
var processOrder = function () {
  console.log('Processing...')
}
 
// let/const — hoisted but TDZ
// console.log(theme); // ReferenceError
let theme = 'dark'
javascript
// Classic interview trap
var price = 100
function getPrice() {
  console.log(price) // undefined (not 100!)
  var price = 200
  return price
}
getPrice() // logs undefined, returns 200

Back to index


230. What is Hoisting?

Theory

Hoisting is JavaScript's behavior of moving declarations to the top of their scope during the compilation phase — before code execution.

DeclarationHoisted?Initialized?
var✅ YesAs undefined
let✅ Yes (TDZ)❌ Not until declaration line
const✅ Yes (TDZ)❌ Not until declaration line
function declaration✅ YesFully initialized
function expression (var fn = ...)var onlyundefined until assignment

TDZ (Temporal Dead Zone): Accessing let/const before their declaration throws ReferenceError.

Pros & Cons

ProsCons
Function declarations usable before definitionvar hoisting causes subtle bugs
Flexible function organizationTDZ errors confuse beginners
Easy to accidentally reference uninitialized variables

Real-Life Example

javascript
// var hoisting
console.log(city) // undefined (not ReferenceError)
var city = 'Bangalore'
 
// let hoisting + TDZ
// console.log(area); // ReferenceError — TDZ
let area = 'Koramangala'
 
// Function declaration — fully hoisted
greet('Amit') // works!
function greet(name) {
  console.log(`Hello, ${name}`)
}
 
// Function expression — only var hoisted
// sayBye("Amit"); // TypeError: sayBye is not a function
var sayBye = function (name) {
  console.log(`Bye, ${name}`)
}
 
// Classic interview trap
var x = 10
function foo() {
  console.log(x) // undefined (not 10!)
  var x = 20
}
foo()
// Equivalent to:
function foo() {
  var x // hoisted
  console.log(x) // undefined
  x = 20
}
javascript
// Real-life best practice — avoid hoisting bugs
// ✅ Use const/let (block scoped, TDZ catches errors early)
const API_URL = import.meta.env.VITE_API_URL
let retryCount = 0
 
// ✅ Declare functions before use OR use function declarations intentionally
function formatPrice(amount) {
  return `₹${amount.toFixed(2)}`
}

Back to index


231. What is the difference between bind and apply in JavaScript?

Theory

Both call, apply, and bind let you control the this context of a function. They are methods on Function.prototype.

MethodInvokes immediately?ArgumentsReturns
call✅ YesComma-separatedFunction result
apply✅ YesArray of argumentsFunction result
bind❌ NoComma-separated (partial apply)New function with bound this

Pros & Cons

bindapply
Creates reusable function with fixed thisInvokes immediately — one-time use
Supports partial applicationUseful when args are already in an array
No immediate execution — defer callMust know all args at call time

Real-Life Example

javascript
const restaurant = {
  name: 'Spice Garden',
  getDetails(prefix) {
    return `${prefix}: ${this.name}`
  },
}
 
// apply — invoke immediately with array of args
restaurant.getDetails.call(restaurant, 'Welcome') // "Welcome: Spice Garden"
restaurant.getDetails.apply(restaurant, ['Welcome']) // same result
 
// apply shines with dynamic arrays
const args = ['Order from']
restaurant.getDetails.apply(restaurant, args) // "Order from: Spice Garden"
 
// bind — returns a new function, does NOT invoke
const getWelcome = restaurant.getDetails.bind(restaurant, 'Welcome')
getWelcome() // "Welcome: Spice Garden" — called later
 
// Real React use case — binding event handlers in class components
class SearchBar extends React.Component {
  constructor(props) {
    super(props)
    this.handleSearch = this.handleSearch.bind(this) // ensure `this` is the component
  }
 
  handleSearch(query) {
    this.props.onSearch(query) // `this` correctly refers to component instance
  }
}
 
// Modern React — arrow functions or hooks eliminate bind need
function SearchBar({ onSearch }) {
  const handleSearch = (query) => onSearch(query) // no bind needed
  return <input onChange={(e) => handleSearch(e.target.value)} />
}

Quick comparison

javascript
function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`
}
 
const user = { name: 'Amit' }
 
greet.apply(user, ['Hello', '!']) // "Hello, Amit!" — called now, args as array
greet.call(user, 'Hello', '!') // "Hello, Amit!" — called now, args individually
 
const boundGreet = greet.bind(user, 'Hello')
boundGreet('!') // "Hello, Amit!" — called later, partial args

Back to index


232. What is the difference between synchronous and asynchronous iteration?

Synchronous iteration

Works with iterable objects (Array, Map, Set, strings).

javascript
const arr = [1, 2, 3]
 
// for...of
for (const item of arr) {
  console.log(item)
}
 
// Iterator protocol
const iterator = arr[Symbol.iterator]()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

Asynchronous iteration

Works with async iterables — objects with [Symbol.asyncIterator]().

javascript
async function* asyncGenerator() {
  yield await fetchData(1)
  yield await fetchData(2)
  yield await fetchData(3)
}
 
// for await...of
async function main() {
  for await (const chunk of asyncGenerator()) {
    console.log(chunk)
  }
}

Comparison

SyncAsync
ProtocolSymbol.iteratorSymbol.asyncIterator
Loopfor...offor await...of
Return{ value, done }Promise<{ value, done }>
Use caseIn-memory dataStreams, paginated APIs, file reads

Practical async iterable — paginated API

javascript
const paginatedFetch = {
  async *[Symbol.asyncIterator]() {
    let cursor = null
 
    do {
      const res = await fetch(`/api/items?cursor=${cursor ?? ''}`)
      const { items, nextCursor } = await res.json()
 
      for (const item of items) yield item
      cursor = nextCursor
    } while (cursor)
  },
}
 
for await (const item of paginatedFetch) {
  console.log(item)
}

Back to index


233. What is the Event Loop and how does the Microtask Queue work?

Theory

JavaScript is single-threaded — one call stack. The event loop coordinates execution between the call stack, macrotask queue (tasks), and microtask queue (jobs).

Execution order:

  1. Run all synchronous code (call stack)
  2. Drain all microtasks (promises, queueMicrotask, MutationObserver)
  3. Run one macrotask (setTimeout, setInterval, I/O, UI events)
  4. Repeat

Pros & Cons

ProsCons
Simple concurrency model — no locksLong sync code blocks everything
Microtasks enable fast promise resolutionMicrotask starvation if queue never empties
Non-blocking I/O via callbacks"Callback hell" without async/await

Real-Life Example

javascript
console.log('1: sync')
 
setTimeout(() => console.log('4: macrotask'), 0)
 
Promise.resolve()
  .then(() => console.log('3: microtask 1'))
  .then(() => console.log('3b: microtask 2'))
 
queueMicrotask(() => console.log('3c: microtask 3'))
 
console.log('2: sync')
 
// Output: 1: sync → 2: sync → 3: microtask 1 → 3c: microtask 3 → 3b: microtask 2 → 4: macrotask

Real-life impact: If you await in a loop without yielding, you starve the UI. Batch work with setTimeout(0) or requestAnimationFrame for long computations.

Back to index


234. What is the Event Loop?

Theory

JavaScript runs on a single thread — only one piece of code executes at a time on the call stack. The Event Loop is the mechanism that coordinates execution between:

  • Call Stack — currently running synchronous code
  • Web APIs — browser APIs (setTimeout, fetch, DOM events) that run outside the main thread
  • Callback Queue (Macrotask Queue) — callbacks waiting to enter the call stack
  • Microtask Queue — higher-priority jobs (Promises, queueMicrotask)

Execution order:

  1. Run all synchronous code on the call stack
  2. Drain all microtasks completely
  3. Run one macrotask
  4. Repeat

This is why Promise.then runs before setTimeout, even when both are scheduled at the same time.

Pros & Cons

ProsCons
Simple concurrency model — no thread locksLong synchronous code blocks the entire UI
Non-blocking I/O via callbacks/promisesEasy to create microtask starvation
Predictable single-thread execution"Callback hell" without async/await discipline

Real-Life Example

javascript
console.log('1: User clicks button')
 
setTimeout(() => {
  console.log('4: Analytics event sent')
}, 0)
 
fetch('/api/orders').then(() => console.log('3: Orders loaded'))
 
console.log('2: UI updated immediately')
 
// Output:
// 1: User clicks button
// 2: UI updated immediately
// 3: Orders loaded        ← microtask (Promise)
// 4: Analytics event sent ← macrotask (setTimeout)
jsx
// Real React impact — don't block the event loop
function HeavyDashboard({ data }) {
  // ❌ Blocks UI for 2 seconds — user can't scroll or click
  const processed = processMillionRows(data)
 
  // ✅ Yield to event loop — UI stays responsive
  const [processed, setProcessed] = useState([])
  useEffect(() => {
    const chunk = () => {
      const result = processChunk(data, 0, 1000)
      setProcessed((prev) => [...prev, ...result])
      if (hasMore) requestIdleCallback(chunk)
    }
    chunk()
  }, [data])
}

Interview answer (concise)

Note

The event loop manages JavaScript's single thread. Sync code runs first, then all microtasks (Promises), then one macrotask (setTimeout, I/O). This ordering explains async behavior and why blocking the main thread freezes the UI.

Back to index


235. Why a fintech app specifically

Theory

Generic "fintech is growing" answers fail. a fintech app wants specific motivation — product, design culture, tech challenges, user base.

Real-Life Example — Points to personalize

Generic (avoid)a fintech app-specific (use)
"Fintech is hot""a fintech app rewards responsible credit behavior — rare mission alignment"
"Good salary""a fintech app's design-first culture — consumer fintech UX at Indian scale"
"React job""Complex KYC, payments, real-time credit — frontend depth I want"
"Bangalore startup""Engineering blog / open source / craft standards I've followed"

Note

I'm drawn to a fintech app because you're solving trust and design in Indian fintech — not just transactions. The KYC flows, payment reliability, and premium UX at scale are exactly the problems I want to work on. I've used a fintech app as a user and noticed how much attention goes into micro-interactions and error states — that's the engineering culture I want.

Back to index


TypeScript

236. How does extends work in TypeScript and what is the difference between type and interface?

Theory

Both type and interface define object shapes in TypeScript. extends inherits properties from another type or interface.

typescript
interface Animal {
  name: string
}
 
interface Dog extends Animal {
  breed: string
}
// Dog = { name: string; breed: string }

type vs interface — key differences

Featureinterfacetype
Object shapes✅✅
extends / inheritance✅ extends✅ & intersection
Declaration merging✅ (same name merges)❌ (duplicate = error)
Primitives, unions, tuples❌✅
Computed properties❌✅
implements in class✅✅ (with object types)

When to use which

Use interfaceUse type
Object/class contractsUnions: type Status = "active" | "inactive"
Public API surfaces (libraries)Tuples: type Point = [number, number]
When you need declaration mergingMapped types: type Readonly<T> = { readonly [K in keyof T]: T[K] }
React component props (convention)Utility compositions

Pros & Cons

interfacetype
Extendable, mergeable — good for librariesMore flexible — unions, primitives, computed
Clear OOP semanticsCannot be reopened/merged
Slightly better error messages for objectsRequired for advanced type operations

Real-Life Example

typescript
// interface — component props (the company React project pattern)
interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary";
  onClick: () => void;
}
 
interface IconButtonProps extends ButtonProps {
  icon: React.ReactNode;
  iconPosition?: "left" | "right";
}
 
function IconButton({ icon, iconPosition = "left", label, ...rest }: IconButtonProps) {
  return (
    <button {...rest}>
      {iconPosition === "left" && icon}
      {label}
      {iconPosition === "right" && icon}
    </button>
  );
}
 
// type — unions and API response states
type ApiState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: string };
 
type User = { id: string; name: string; email: string };
type Admin = User & { permissions: string[] }; // intersection via type
 
// type — function signature
type Fetcher<T> = (url: string) => Promise<T>;
 
// interface merging — extending third-party types
interface Window {
  analytics: { track: (event: string, data?: object) => void };
}
// Now window.analytics is typed everywhere

extends with generics

typescript
interface PaginatedResponse<T> {
  data: T[]
  page: number
  totalPages: number
}
 
interface RestaurantListResponse extends PaginatedResponse<Restaurant> {
  city: string
}
// { data: Restaurant[]; page: number; totalPages: number; city: string }

Back to index


237. TypeScript in Large Codebases

Theory

TypeScript at scale is about boundaries, consistency, and maintainability — not sprinkling : any everywhere.

Key practices:

  • Strict mode (strict: true)
  • Shared types between API and UI
  • Discriminated unions for state machines
  • Generic components with constraints
  • Branded types for IDs
  • Zod/io-ts for runtime validation at API boundary

Interview Answer

I enforce strict TypeScript, validate API responses at the boundary with Zod, use discriminated unions for async states, and share types via OpenAPI or a monorepo types package — never duplicate DTO definitions.

API Boundary Validation

tsx
import { z } from 'zod'
 
const ProductSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  price: z.number().positive(),
  category: z.enum(['electronics', 'grocery', 'pharmacy']),
  inStock: z.boolean(),
})
 
type Product = z.infer<typeof ProductSchema>
 
async function fetchProduct(id: string): Promise<Product> {
  const res = await fetch(`/api/products/${id}`)
  const json = await res.json()
  return ProductSchema.parse(json) // runtime + compile-time safety
}

Discriminated Unions — Async State

tsx
type AsyncState<T> =
  { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }
 
function DataView<T>({ state }: { state: AsyncState<T> }) {
  switch (state.status) {
    case 'idle':
      return null
    case 'loading':
      return <Spinner />
    case 'error':
      return <ErrorBanner error={state.error} />
    case 'success':
      return <Content data={state.data} />
  }
}

Generic Components

tsx
interface DataTableProps<T extends { id: string }> {
  data: T[]
  columns: Column<T>[]
  onRowClick?: (row: T) => void
}
 
function DataTable<T extends { id: string }>({ data, columns, onRowClick }: DataTableProps<T>) {
  return (
    <table>
      <thead>
        <tr>
          {columns.map((col) => (
            <th key={col.key}>{col.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row.id} onClick={() => onRowClick?.(row)}>
            {columns.map((col) => (
              <td key={col.key}>{col.render(row)}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  )
}

Branded Types — Prevent ID Mix-ups

tsx
type UserId = string & { readonly __brand: 'UserId' }
type OrderId = string & { readonly __brand: 'OrderId' }
 
function fetchOrders(userId: UserId): Promise<Order[]> {
  /* ... */
}
 
const userId = 'abc' as UserId
fetchOrders(userId) // ✅
fetchOrders('abc') // ❌ Type error — plain string not assignable

Large Codebase Rules

RuleWhy
No any — use unknown + narrowPrevents silent bugs
satisfies over asPreserves literal types
Colocate types with featuresfeatures/cart/types.ts
API types generated from OpenAPISingle source of truth
ESLint @typescript-eslint strictCatch issues in CI

Back to index


238. Why do we use TypeScript?

Theory

TypeScript is a typed superset of JavaScript that compiles to plain JS. It adds static type checking at compile time, catching errors before runtime.

Why teams adopt TypeScript

  1. Catch bugs early — typos, wrong argument types, null access
  2. Better IDE support — autocomplete, refactoring, go-to-definition
  3. Self-documenting code — types serve as inline documentation
  4. Safer refactoring — rename a field, compiler finds all usages
  5. Team scalability — contracts between modules are explicit

Pros & Cons

ProsCons
Fewer runtime type errorsBuild step required
Excellent autocompleteLearning curve for JS developers
Safer large codebasesCan be verbose (any abuse negates benefits)
Interfaces document API contractsSlower initial development for small projects

Real-Life Example

typescript
// JavaScript — bug discovered at runtime in production
function calculateDiscount(price, discountPercent) {
  return price - (price * discountPercent) / 100
}
calculateDiscount('299', '10') // "299" - NaN → broken UI
 
// TypeScript — caught at compile time
function calculateDiscount(price: number, discountPercent: number): number {
  return price - (price * discountPercent) / 100
}
calculateDiscount('299', '10') // ❌ Compile error: string not assignable to number
 
// Real React project — typed API response
interface Restaurant {
  id: string
  name: string
  rating: number
  cuisine: string[]
  isOpen: boolean
}
 
async function fetchRestaurants(city: string): Promise<Restaurant[]> {
  const res = await fetch(`/api/restaurants?city=${city}`)
  return res.json()
}
 
// Autocomplete works: restaurant.rating, restaurant.cuisine
// Typo caught: restaurant.raiting → compile error

Interview answer (concise)

Note

TypeScript adds static types to JavaScript. It catches errors at build time, improves IDE support, and makes large React codebases maintainable. In my projects, it's especially valuable for API response types, Redux state shapes, and component prop contracts.

Back to index


Next.js

239. You are migrating a React SPA to Next.js. What challenges would you expect and how would you handle them?

Description

SPAs rely on client-side routing, browser-only APIs, and often a single index.html. Next.js introduces file-based routing, Server Components, and different data-fetching semantics.

Theory

SPA habitNext.js change
react-routerApp Router file system (app/page.tsx)
useEffect + fetchServer Component async fetch or React Query in client islands
window / localStorage on first render'use client' + useEffect, or dynamic ssr: false
Env vars REACT_APP_*NEXT_PUBLIC_* (client) vs server-only env
Global CSS in index.jsapp/layout.tsx imports
Auth in context onlyMiddleware + cookies for SSR session

Pros & Cons

Incremental migration (Next + existing SPA routes)Big-bang rewrite
✅ Ship value per route❌ Long freeze, high risk
✅ Learn App Router gradually❌ Hard to test everything
❌ Temporary dual patterns❌ Regression bugs

Real Example — Incremental migration plan

Phase 1 — Shell
text
next.config.js → redirects old paths
app/layout.tsx → shared providers (only client wrappers where needed)
Migrate /marketing and /blog first (SSG wins)
Phase 2 — Auth
tsx
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(req: NextRequest) {
  const token = req.cookies.get('session')?.value
  if (!token && req.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', req.url))
  }
  return NextResponse.next()
}
Phase 3 — Replace react-router
tsx
// ❌ SPA
;<Route path="/products/:id" element={<ProductPage />} />
 
// ✅ App Router
// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id)
  return <ProductDetail product={product} />
}
Phase 4 — Client-only widgets
tsx
// Map, charts, rich text editor — mark client, dynamic import
'use client'
// or dynamic(() => import('./map'), { ssr: false })
Common pitfalls
ProblemFix
Hydration mismatchDon't use Date.now() or random IDs in SSR HTML
Double data fetchTrust Server Components; don't refetch same data in useEffect
localStorage in renderMove to useEffect or client-only component
SEO still missingEnsure marketing pages are Server/SSG, not client-only

Interview Answer

I'd migrate incrementally: file-based routes instead of react-router, move data fetching to Server Components, wrap browser-only code in 'use client' or dynamic imports, handle auth in middleware with cookies, and fix hydration mismatches by not rendering client-only values on the server.

Back to index


240. You need to upload large files from the frontend to a cloud storage service. How would you implement the flow in Next.js?

Description

Uploading multi-GB files through your Next.js server wastes memory, hits body-size limits, and times out on serverless. The standard pattern is presigned URLs — client uploads directly to S3/R2/GCS.

Theory

FlowProsCons
Direct to cloud (presigned)Scalable, no server memoryNeed CORS config on bucket
Through Next.js APISimple for small filesBreaks on large files / serverless
Multipart uploadResumable, parallel chunksMore client logic

Steps: Client requests presigned URL → Next.js Route Handler validates auth + file metadata → returns signed PUT URL → client uploads directly → client notifies app to save metadata.

Pros & Cons

Presigned direct uploadProxy through API route
✅ Handles large files✅ Full server control
✅ No serverless timeout❌ 4.5 MB limits on many platforms
❌ Must validate before signing❌ Server RAM bottleneck

Real Example — S3 presigned upload (500 MB video)

tsx
// app/api/uploads/presign/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { NextRequest } from 'next/server'
 
const s3 = new S3Client({ region: process.env.AWS_REGION })
 
const MAX_SIZE = 500 * 1024 * 1024 // 500 MB
const ALLOWED_TYPES = ['video/mp4', 'application/pdf']
 
export async function POST(req: NextRequest) {
  const session = await getSession(req)
  if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
 
  const { fileName, fileType, fileSize } = await req.json()
 
  if (!ALLOWED_TYPES.includes(fileType) || fileSize > MAX_SIZE) {
    return Response.json({ error: 'Invalid file' }, { status: 400 })
  }
 
  const key = `uploads/${session.userId}/${crypto.randomUUID()}-${fileName}`
 
  const command = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    ContentType: fileType,
  })
 
  const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 })
 
  return Response.json({ uploadUrl, key })
}
tsx
// components/file-uploader.tsx
'use client'
 
export function FileUploader() {
  async function handleUpload(file: File) {
    const presign = await fetch('/api/uploads/presign', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        fileName: file.name,
        fileType: file.type,
        fileSize: file.size,
      }),
    }).then((r) => r.json())
 
    await fetch(presign.uploadUrl, {
      method: 'PUT',
      body: file,
      headers: { 'Content-Type': file.type },
    })
 
    await fetch('/api/uploads/complete', {
      method: 'POST',
      body: JSON.stringify({ key: presign.key, size: file.size }),
    })
  }
 
  return <input type="file" onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])} />
}

Large files: Use S3 multipart upload API from the client (or library like tus / @aws-sdk/lib-storage).

Interview Answer

I wouldn't stream large files through Next.js. I'd use a Route Handler to authenticate the user and return a presigned S3 URL, let the client upload directly with progress UI, then call a completion endpoint to store metadata — multipart upload for very large files.

Back to index


Testing & Tooling

241. How would you implement A/B testing in a Next.js application?

Description

A/B tests split users into variants to compare metrics (CTR, conversion). Implementation must assign users consistently (same variant on return visits) without flicker and without hurting SEO.

Theory

ApproachHow it works
Middleware + cookieAssign variant on first visit; edge-fast
Feature flags (LaunchDarkly, Flagsmith)Remote config, gradual rollouts
Vercel Flags / Edge ConfigLow-latency variant lookup
Client-onlySimple but causes flicker — avoid for hero CTA tests

Best practice: Assign variant in middleware, store in cookie, render correct variant on server.

Pros & Cons

Server-side assignment (middleware)Client-side random on mount
✅ No flicker❌ Flash of wrong variant
✅ SEO sees stable content❌ Skews analytics
❌ Needs cookie consent in EU✅ Easier to prototype

Real Example — Homepage CTA A/B test

tsx
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
const VARIANTS = ['control', 'variant-b'] as const
 
function pickVariant(): string {
  return VARIANTS[Math.floor(Math.random() * VARIANTS.length)]
}
 
export function middleware(req: NextRequest) {
  const res = NextResponse.next()
  let variant = req.cookies.get('ab-cta')?.value
 
  if (!variant || !VARIANTS.includes(variant as (typeof VARIANTS)[number])) {
    variant = pickVariant()
    res.cookies.set('ab-cta', variant, {
      maxAge: 60 * 60 * 24 * 30, // 30 days — consistent experience
      path: '/',
    })
  }
 
  res.headers.set('x-ab-cta', variant)
  return res
}
tsx
// app/page.tsx
import { cookies } from 'next/headers'
import { HeroCTA } from '@/components/hero-cta'
 
export default function HomePage() {
  const variant = cookies().get('ab-cta')?.value ?? 'control'
 
  return (
    <main>
      <h1>Welcome</h1>
      <HeroCTA variant={variant} />
    </main>
  )
}
tsx
// components/hero-cta.tsx
'use client'
 
import { trackEvent } from '@/lib/analytics'
 
export function HeroCTA({ variant }: { variant: string }) {
  const isB = variant === 'variant-b'
 
  function handleClick() {
    trackEvent('cta_click', { variant, page: 'home' })
  }
 
  return (
    <a href="/signup" onClick={handleClick} className={isB ? 'btn-green' : 'btn-blue'}>
      {isB ? 'Start free trial' : 'Sign up'}
    </a>
  )
}

Analytics: Send variant with every conversion event; analyze in Amplitude/Mixpanel/GA4.

Feature flag provider (production scale)
tsx
import { getFlag } from '@/lib/flags'
 
export default async function PricingPage() {
  const showAnnual = await getFlag('pricing-annual-default', false)
  return <PricingTable defaultTab={showAnnual ? 'annual' : 'monthly'} />
}

Interview Answer

I'd assign variants in middleware with a persistent cookie, pass the variant into Server Components to avoid flicker, track exposure and conversion with the variant ID in analytics, and use a feature-flag service for rollouts beyond simple A/B tests.

Back to index


242. Testing — RTL, Jest, Playwright

Theory

Frontend testing pyramid:

LayerToolTests
UnitJest / VitestPure functions, reducers, utils
ComponentReact Testing Library (RTL)User behavior, rendering
E2EPlaywright / CypressFull user flows in browser

RTL philosophy: Test how users interact — query by role, label, text. Not implementation details.

Pros & Cons

ToolBest forAvoid for
JestUnit tests, snapshots, mocksVisual layout
RTLComponent behaviorE2E flows
PlaywrightCross-browser E2E, CIUnit logic

Real-Life Example

tsx
// Jest + RTL — component test
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import LoginForm from './LoginForm'
 
describe('LoginForm', () => {
  it('shows error on invalid credentials', async () => {
    const user = userEvent.setup()
    render(<LoginForm onSubmit={mockSubmit} />)
 
    await user.type(screen.getByLabelText(/email/i), 'wrong@test.com')
    await user.type(screen.getByLabelText(/password/i), 'wrong')
    await user.click(screen.getByRole('button', { name: /sign in/i }))
 
    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent(/invalid/i)
    })
  })
})
 
// Jest — reducer unit test
describe('cartReducer', () => {
  it('adds item to cart', () => {
    const state = cartReducer(undefined, addItem({ id: '1', name: 'Biryani', price: 299 }))
    expect(state.items).toHaveLength(1)
    expect(state.items[0].name).toBe('Biryani')
  })
})
typescript
// Playwright — E2E test
import { test, expect } from '@playwright/test'
 
test('user can complete checkout', async ({ page }) => {
  await page.goto('/products')
  await page.getByRole('button', { name: 'Add to Cart' }).first().click()
  await page.getByRole('link', { name: 'Cart' }).click()
  await expect(page.getByText('1 item')).toBeVisible()
  await page.getByRole('button', { name: 'Checkout' }).click()
  await page.getByLabel('Card number').fill('4242424242424242')
  await page.getByRole('button', { name: 'Pay' }).click()
  await expect(page.getByText('Order confirmed')).toBeVisible()
})

Back to index


243. Webpack

Theory

Webpack is a module bundler that takes JavaScript modules (and assets) and bundles them for the browser. It supports code splitting, tree shaking, loaders (CSS, images), and plugins.

Key concepts: entry, output, loaders, plugins, chunks, HMR (Hot Module Replacement).

Modern alternatives: Vite (esbuild dev + Rollup prod), Turbopack (Next.js), Rollup, esbuild.

Pros & Cons

WebpackVite
✅ Mature, huge plugin ecosystem✅ Faster dev server (native ESM)
✅ Highly configurable✅ Simpler config
❌ Slow dev server on large apps❌ Less mature plugin ecosystem
❌ Complex configuration✅ Better DX for new projects

Real-Life Example

javascript
// webpack.config.js — simplified
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js', // cache busting
    clean: true,
  },
  module: {
    rules: [
      { test: /\.jsx?$/, use: 'babel-loader', exclude: /node_modules/ },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
      { test: /\.(png|svg|jpg)$/, type: 'asset/resource' },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }),
  ],
  optimization: {
    splitChunks: { chunks: 'all' },
    runtimeChunk: 'single',
  },
  devServer: { hot: true, port: 3000 },
}

Back to index


244. What is Snapshot Testing?

Theory

Snapshot testing captures the rendered output of a component (or serialized data) and saves it as a snapshot file. On future test runs, the output is compared against the saved snapshot. If they differ, the test fails — you either fix a bug or update the snapshot intentionally.

Common tools: Jest (toMatchSnapshot), Vitest, React Testing Library.

Snapshots can capture:

  • React component HTML/JSON tree
  • Serialized objects
  • Error messages

Pros & Cons

ProsCons
✅ Fast to write — good for regression❌ Developers blindly update snapshots
✅ Catches unintended UI changes❌ Large diffs hard to review
✅ Good for stable, presentational components❌ Not a substitute for behavior tests
✅ Documents component output over time❌ Fragile if snapshots include dynamic data (dates, IDs)

Real-Life Example

tsx
// EmployeeCard.tsx
function EmployeeCard({ name, role, department }) {
  return (
    <div className="employee-card">
      <h3>{name}</h3>
      <p>{role}</p>
      <span className="dept-badge">{department}</span>
    </div>
  )
}
 
// EmployeeCard.test.tsx
import { render } from '@testing-library/react'
import EmployeeCard from './EmployeeCard'
 
describe('EmployeeCard', () => {
  it('matches snapshot', () => {
    const { container } = render(<EmployeeCard name="Priya Sharma" role="Senior Developer" department="Engineering" />)
    expect(container.firstChild).toMatchSnapshot()
  })
})
javascript
// First run creates: __snapshots__/EmployeeCard.test.tsx.snap
exports[`EmployeeCard matches snapshot 1`] = `
<div class="employee-card">
  <h3>Priya Sharma</h3>
  <p>Senior Developer</p>
  <span class="dept-badge">Engineering</span>
</div>
`

Best practices:

  • Use snapshots for stable presentational components
  • Combine with behavior tests (clicks, form submission)
  • Mock dates/IDs to avoid flaky snapshots
  • Review snapshot diffs carefully in PRs — don't auto-accept blindly
tsx
// Better — snapshot + behavior test together
it('calls onSelect when clicked', async () => {
  const onSelect = jest.fn()
  render(<EmployeeCard name="Amit" onSelect={onSelect} />)
  await userEvent.click(screen.getByRole('button', { name: /select/i }))
  expect(onSelect).toHaveBeenCalledWith('Amit')
})

Back to index


Behavioral & Soft Skills

245. Culture Fit & Behavioral Questions

Theory

a quick-commerce app's culture round assesses how you work, not what you know. They evaluate problem-solving approach, ownership, collaboration, and fit for a fast-paced consumer product (10-minute delivery).

Use the STAR method: Situation → Task → Action → Result.

Interview Answer

I describe real projects with measurable impact, explain my debugging process clearly, and show I collaborate well in fast-moving teams — with examples, not generic claims.

Previous Projects — How to Answer

Structure your answer:

  1. What — product/feature in one sentence
  2. Your role — what you specifically built
  3. Tech stack — React, state management, APIs
  4. Impact — metrics if possible (load time, conversion, bug reduction)
  5. Challenge — one hard problem you solved

Example answer:

Note

"At my last role I built the checkout flow for an e-commerce app. I owned the cart page and payment integration using React and Redux. The main challenge was handling partial API failures — if the inventory check failed but payment succeeded. I implemented optimistic UI with rollback and idempotent payment requests. Cart abandonment dropped 12% after we fixed loading states and error messaging."

Problem Solving Approach

Interviewers want your process, not just the answer:

plaintext
1. Clarify requirements    → "Should toggle affect neighbors?"
2. Break down the problem  → State, UI, events separately
3. Start simple            → Basic toggle before row/column
4. Test edge cases         → Empty grid, all selected, rapid clicks
5. Iterate                 → Add a11y, reset, count after MVP works

Example answer:

Note

"When I hit a bug, I reproduce it consistently first, then check the network tab and React DevTools. I add console logs or breakpoints at the state update boundary. For a recent CORS issue, I traced the preflight OPTIONS response and found the server wasn't returning the right Allow-Headers — fixed it with the backend team in one PR."

Engineering Mindset

Traits a quick-commerce app likely values:

TraitExample to mention
Ownership"I didn't wait for QA — I added unit tests for edge cases"
User-first"I optimized skeleton loaders because users on 3G saw blank screens"
Pragmatism"Shipped MVP with long polling, migrated to WebSocket in v2"
Learning"I hadn't used IndexedDB — I read docs, built a POC in a day"
Performance"Virtualized a 500-item product list — scroll went from janky to 60fps"

Team Collaboration

Example questions & answers:

"Tell me about a disagreement with a teammate."

Note

"A teammate wanted to use Context for all global state. I preferred Redux for cart because of DevTools and middleware. We listed requirements — debuggability, async cart sync, team familiarity — and agreed Redux for cart/auth, Context for theme. We documented the decision in ADR format."

"How do you handle tight deadlines?"

Note

"I prioritize MVP scope with the PM — what's blocking launch vs nice-to-have. For a festival sale page, we shipped core catalog + cart first, deferred animations. I communicated trade-offs early so stakeholders weren't surprised."

"How do you code review?"

Note

"I check correctness first, then readability, then edge cases. I ask questions rather than dictate — 'What happens if this API returns null?' I approve quickly when tests cover the happy and error paths."

Questions to Ask Them

Shows genuine interest:

  • "How does the frontend team handle real-time inventory updates across warehouses?"
  • "What's the balance between web and mobile web at a quick-commerce app?"
  • "How do you measure frontend performance in production?"
  • "What does the on-call rotation look like for frontend engineers?"
RoundTopicOne-liner
1CORSServer must allow cross-origin; fix on backend
1WebSocketsPersistent duplex — live orders, wss + reconnect
1Long polling vs WSPolling = held HTTP; WS = true real-time
1Storagelocal = persistent; session = tab; cookies = server; IDB = large
1Event loopSync → all microtasks → one macrotask
2Grid toggle2D boolean array, immutable map toggle
3CultureSTAR stories, process, collaboration examples

Back to index


246. Describe a disagreement within your team and how you resolved it

What interviewers assess

  • Empathy and listening
  • Data-driven decisions
  • Willingness to disagree and commit

Sample answer skeleton

Note

S: Our team debated Redux vs TanStack Query for a new orders dashboard.

Note

T: I advocated for React Query; a senior colleague preferred Redux for consistency with legacy apps.

Performance

A: Instead of arguing opinions, I proposed a spike: build the same feature both ways, measure bundle size, lines of code, and developer time. I also documented how server state in Redux caused stale-data bugs in the legacy app. We presented findings to the team.

Note

R: The team chose React Query for server state and kept Redux only for complex client workflows. We updated our architecture decision record (ADR) so future teams had clear guidance.

Back to index


247. How did you handle a critical production issue?

What interviewers assess

  • Calm under pressure
  • Systematic debugging
  • Communication with stakeholders
  • Post-incident learning (blameless)

Sample answer skeleton

Note

S: During peak dinner hours, checkout failed for ~8% of users after a frontend deploy.

Note

T: I was on-call and led the frontend investigation.

Note

A: I checked error dashboards (Sentry spike on TypeError), correlated with the deploy timeline, identified a null reference in the payment component when a new API field was missing. I coordinated an immediate rollback with the release team, posted status updates in Slack every 15 minutes, and shipped a hotfix with a null guard plus a feature flag within 45 minutes.

Note

R: Error rate returned to baseline in 20 minutes post-rollback. We added a contract test against the API schema and a canary deploy step to prevent recurrence.

Back to index


248. How do you mentor junior developers and review code?

Mentoring pillars

  1. Pair programming on real tickets, not toy examples
  2. Graduated ownership — start with small PRs, grow to feature ownership
  3. Review their reviews — teach them what to look for
  4. Document patterns — ADRs, internal wiki, code examples
  5. Psychological safety — questions are encouraged; mistakes are learning opportunities

Code review checklist I share

plaintext
□ Does it solve the right problem?
□ Is the abstraction appropriate (not over-engineered)?
□ Edge cases: empty, error, loading states
□ Accessibility: keyboard, ARIA, contrast
□ Performance: unnecessary re-renders, large bundles
□ Tests for critical paths
□ Security: XSS, exposed secrets, auth checks

Sample answer skeleton

Note

I treat mentoring as building independent engineers, not dependencies. I assign increasing scope, do weekly 1:1s focused on growth goals, and use PR reviews as teaching moments — I ask questions rather than just dictate changes. I maintain a team playbook with patterns for hooks, error handling, and testing. Two juniors I mentored were promoted to mid-level within 18 months.

TopicOne-liner
var / let / constBlock scope + TDZ for let/const; prefer const
ClosuresFunction + lexical env; enables privacy, memoization, debounce
Custom fetch hookLoading/error/data + AbortController + cleanup
ReconciliationFiber diff → minimal DOM updates; render vs commit phase
KeysStable identity for list items; avoid index on mutable lists
ArchitectureFeature folders, design system, service layer, monorepo
RSCServer-only components; zero client JS; direct data access
Infinite scrollIntersection Observer + cursor pagination + virtualization
Web VitalsLCP (load), INP (responsive), CLS (stable layout)
StateQuery for server, Zustand/Redux for global, URL for shareable
CachingHTTP headers, CDN, React Query, Service Worker
ScaleCDN, SSR, code split, observability, feature flags, resilience
BehavioralSTAR method, quantify impact, show leadership + humility

Back to index


249. Tell us about a challenging project you worked on

Framework

STARWhat to include
SituationBusiness context, scale, constraints
TaskYour specific responsibility
ActionTechnical decisions, trade-offs, collaboration
ResultMetrics: performance, revenue, reliability, team velocity

Sample answer skeleton

Performance

S: Our restaurant listing page had a 6s LCP on mid-range Android devices, hurting conversion in tier-2 cities.

Performance

T: I owned the frontend performance initiative for the listing experience.

Performance

A: I profiled with Lighthouse and Chrome DevTools, introduced route-level code splitting, migrated images to AVIF with responsive srcset, moved data fetching to the server with RSC, and added skeleton loaders with fixed dimensions to fix CLS. I partnered with backend to add cursor pagination and CDN cache headers.

Performance

R: LCP dropped from 6s to 2.1s (p75). Bounce rate fell 12%. The patterns were documented and adopted by two other squads.

Back to index


General & Advanced

250. == vs ===

Theory

  • == — loose equality with type coercion
  • === — strict equality, no coercion

Always prefer === in production. Use == null to check both null and undefined.

Coercion surprises

javascript
0 == false // true
'' == false // true
null == undefined // true
;[] == false // true
'5' == 5 // true
 
0 === false // false
null === undefined // false

Interview Answer

=== compares value and type without coercion. == coerces types which causes subtle bugs — I always use === except for null checks with == null.

Real Example

javascript
function validateAmount(input) {
  // ❌ "0" == 0 is true — wrong for payment validation
  if (input == 0) return 'Invalid amount'
 
  // ✅ Strict check
  if (input === 0 || input === '0') return 'Invalid amount'
  if (Number(input) <= 0) return 'Invalid amount'
}

Back to index


251. A decision you regretted

Theory

Tests humility, learning, and judgment. Structure: decision → why it seemed right → what went wrong → what you do differently now.

Real-Life Example — Answer skeleton

Note

Decision: Chose to build a custom form validation library instead of adopting React Hook Form for a KYC flow.

Note

Why: Wanted zero dependencies and full control. Team was small, timeline seemed comfortable.

Performance

Regret: Spent 3 sprints on edge cases RHF already handles. Introduced subtle re-render bugs we didn't catch until QA. Delayed the feature launch.

Warning

Now: Default to battle-tested libraries for solved problems. Custom code only when there's a specific requirement libraries can't meet. I evaluate "build vs buy" with a time-boxed spike first.

a fintech app tip: Defensiveness ends interviews. Acknowledge feedback, thank them, show what changed.

Back to index


252. Accessibility (a11y)

Theory

Accessibility ensures apps are usable by everyone — screen reader users, keyboard-only users, people with motor/cognitive disabilities. Standard: WCAG 2.1 AA.

Principles (POUR): Perceivable, Operable, Understandable, Robust.

Pros & Cons

Accessible appsIgnoring a11y
✅ 15%+ larger audience❌ Legal liability
✅ Better SEO❌ Fails enterprise RFPs
✅ Better UX for all❌ Lighthouse score penalty

Real-Life Example

tsx
// Semantic, keyboard-accessible form
<form onSubmit={handleSubmit} aria-labelledby="form-title">
  <h2 id="form-title">Contact Us</h2>
 
  <label htmlFor="email">Email</label>
  <input
    id="email"
    type="email"
    required
    aria-describedby="email-error"
    aria-invalid={!!errors.email}
  />
  {errors.email && (
    <p id="email-error" role="alert">{errors.email}</p>
  )}
 
  <button type="submit">Send</button>
</form>
 
// Skip link
<a href="#main" className="sr-only focus:not-sr-only">Skip to content</a>
 
// Live region for dynamic updates
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</div>

Back to index


253. Arrow Functions

Theory

Arrow functions (() => {}) are a concise function syntax introduced in ES6. Key differences from regular functions:

FeatureRegular functionArrow function
thisDynamic — depends on callerLexical — inherited from enclosing scope
argumentsAvailableNot available — use rest ...args
new / constructorCan be used with newCannot
prototypeHas .prototypeNo
SyntaxVerboseConcise, implicit return

Pros & Cons

ProsCons
Concise syntaxNo this binding — wrong for object methods
Implicit return for expressionsCan't be used as constructors
Great for callbacks and array methodsNo arguments object
Lexical this fixes callback bugsNot ideal for prototype methods

Real-Life Example

javascript
// Concise syntax
const double = (x) => x * 2;
const sum = (a, b) => a + b;
const getUser = (id) => ({ id, name: "Amit" }); // implicit return object
 
// Array methods — arrow functions shine
const prices = [299, 49, 79, 150];
const withTax = prices.map((p) => p * 1.05);
const affordable = prices.filter((p) => p < 100);
const total = prices.reduce((sum, p) => sum + p, 0);
 
// Lexical this — fixes callback bug
const store = {
  products: ["Biryani", "Naan"],
  printAll() {
    // ❌ Regular function loses `this`
    this.products.forEach(function (p) {
      // console.log(this.category, p);
    });
    // ✅ Arrow inherits `this`
    this.products.forEach((p) => {
      console.log(this.products.length, p); // works
    });
  },
};
 
// When NOT to use arrow functions
const obj = {
  name: "Test",
  regular() { return this.name; },     // "Test"
  arrow: () => this?.name,             // undefined (lexical this)
};
 
// React event handlers — both work in function components
<button onClick={() => handleClick(id)}>Click</button>
<button onClick={handleClick}>Click</button>

Back to index


254. Authentication vs Authorization

Theory

  • Authentication (AuthN) — Who are you? Verifying identity (login, OAuth, JWT, biometrics).
  • Authorization (AuthZ) — What can you do? Checking permissions after identity is known (roles, ACLs, policies).

Authentication always comes before authorization. You can't authorize an unknown user.

Pros & Cons

AuthenticationAuthorization
HandlesIdentity verificationPermission checking
ExamplesLogin, SSO, MFARBAC, ACL, policy engine
Frontend roleLogin UI, token storageRoute guards, conditional UI
Backend roleValidate credentialsEnforce access rules

Real-Life Example

tsx
// Authentication — login flow
async function login(email: string, password: string) {
  const res = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ email, password }),
  })
  // Server sets httpOnly cookie — frontend never sees raw token
  return res.ok
}
 
// Authorization — role-based UI (UX only — backend MUST enforce)
function AdminPanel() {
  const { user } = useAuth() // authenticated user
 
  // Authorization check
  if (!user?.roles.includes('admin')) {
    return <Navigate to="/unauthorized" />
  }
 
  return <AdminDashboard />
}
 
// Backend always validates — frontend guards are UX only
// DELETE /api/users/99 → 403 Forbidden if not admin
// regardless of what frontend shows
LayerAuthenticationAuthorization
FrontendLogin form, session checkHide/show buttons, route guards
BackendValidate JWT/sessionCheck role on every API call
TokenJWT with sub (user ID)JWT with roles/permissions claims

Back to index


255. Auto Increment/Decrement Counter

Theory

A counter is the classic useState exercise. Requirements:

  • Click Increment → count increases by 1
  • Click Decrement → count decreases by 1
  • Use functional updaters (prev) => prev + 1 to avoid stale closure bugs
  • Optional senior touches: min/max bounds, disable buttons at limits, accessibility

Interview Answer

I use useState with functional updaters for increment and decrement, wire them to buttons, and optionally clamp the value or disable buttons at boundaries.

Basic Implementation

jsx
import { useState } from 'react'
 
function Counter() {
  const [count, setCount] = useState(0)
 
  const increment = () => setCount((prev) => prev + 1)
  const decrement = () => setCount((prev) => prev - 1)
 
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={decrement}>Decrement</button>
      <button onClick={increment}>Increment</button>
    </div>
  )
}

Senior-Level Implementation

jsx
import { useState, useCallback } from 'react'
 
function Counter({ initial = 0, min = -Infinity, max = Infinity, step = 1 }) {
  const [count, setCount] = useState(initial)
 
  const increment = useCallback(() => {
    setCount((prev) => Math.min(prev + step, max))
  }, [step, max])
 
  const decrement = useCallback(() => {
    setCount((prev) => Math.max(prev - step, min))
  }, [step, min])
 
  const reset = useCallback(() => setCount(initial), [initial])
 
  return (
    <div role="group" aria-label="Counter">
      <output aria-live="polite" aria-atomic="true">
        {count}
      </output>
      <button onClick={decrement} disabled={count <= min} aria-label="Decrement">
        −
      </button>
      <button onClick={increment} disabled={count >= max} aria-label="Increment">
        +
      </button>
      <button onClick={reset} aria-label="Reset counter">
        Reset
      </button>
    </div>
  )
}
 
// Usage
;<Counter initial={0} min={0} max={10} step={1} />

useReducer Variant (if interviewer asks)

jsx
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    case 'RESET':
      return action.payload
    default:
      return state
  }
}
 
function Counter() {
  const [count, dispatch] = useReducer(counterReducer, 0)
  return (
    <>
      <p>{count}</p>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>−</button>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
    </>
  )
}

Back to index


256. Batching in React

Theory

Batching groups multiple state updates into a single re-render for performance.

React 18 automatic batching: All updates batch — inside events, timeouts, promises, native handlers.

flushSync() opts out — forces synchronous render.

Interview Answer

React batches multiple setState calls into one re-render. React 18 batches everywhere — events, timeouts, promises. use flushSync to force immediate render when needed.

Real Example

jsx
function Counter() {
  const [a, setA] = useState(0)
  const [b, setB] = useState(0)
 
  const handleClick = () => {
    setA((x) => x + 1)
    setB((x) => x + 1)
    // React 18: ONE re-render, not two
  }
 
  // Also batched in async
  setTimeout(() => {
    setA(1)
    setB(2)
  }, 1000) // single re-render
}
jsx
import { flushSync } from 'react-dom'
 
flushSync(() => setA(1)) // renders immediately
setB(2) // separate render

Back to index


257. Best Practices in React

Theory

Production React follows consistent patterns for maintainability, performance, and reliability.

Core practices checklist

PracticeWhy
const by default, immutable state updatesPredictable renders
Colocate state, lift only when neededLess re-renders
Custom hooks for shared logicDRY
Keys from stable IDsList performance + state
Error Boundaries per route/featureFault isolation
Code split by routeFaster initial load
Profile before memoizingAvoid premature optimization
Separate UI from business logicTestability
TypeScript for large appsCatch bugs early
Cleanup in useEffectNo memory leaks

Interview Answer

I keep components small, state colocated, effects cleaned up, keys stable, routes code-split, and I profile before optimizing. Business logic lives in hooks and services, not in JSX.

Real Example — folder structure

plaintext
src/
├── features/
│   └── checkout/
│       ├── components/   # UI only
│       ├── hooks/        # useCheckout
│       ├── api/          # checkoutService
│       └── types.ts
├── shared/
│   └── components/       # Button, Modal
└── app/
    └── router.tsx
#TopicOne-Line Answer
1ReactComponent-based UI library; state drives view
2JSXHTML-like syntax compiled to createElement
3ComponentsReusable UI building blocks
4Functional vs ClassFunctions + hooks = modern standard
5PropsRead-only inputs from parent
6StateMutable internal data; triggers re-render
7State vs PropsInternal vs external; mutable vs read-only
8Virtual DOMJS tree + diff → minimal DOM updates
9React RouterURL → component, no page reload
10Conditional renderingTernary, &&, early return
11useEffectSide effects after render + cleanup
12Dependency arrayControls when effect re-runs
13useStateLocal state + setter
14useContextRead context without drilling
15Context APIShare theme/auth across tree
16Custom hooksReusable stateful logic
17KeysStable IDs for list identity
18React.memoSkip render if props equal
19useRefMutable ref, no re-render
20useRef vs useStateNo re-render vs triggers re-render
21Code splittingBundle chunks on demand
22Lazy loadingDefer until needed
23SuspenseFallback while loading
24Error BoundariesCatch render errors, show fallback
25Controlled vs UncontrolledState-driven input vs DOM ref
26HOCWrap component for reuse; prefer hooks
27Prop drillingFix with Context/composition
28ReconciliationDiff + minimal DOM patch
29Strict ModeDev double-invoke for bug detection
30Best practicesSmall components, cleanup, split, profile

How to Answer in Interviews

Use this 3-step pattern for any React question:

  1. One sentence definition — what it is
  2. One sentence why it matters — when you'd use it
  3. One real example — from a project or simple code

Example for useEffect:

Performance

"useEffect runs side effects after render — things like API calls or subscriptions that shouldn't happen during render. I always return a cleanup function to prevent memory leaks. In my last project, I used it to fetch user profile on mount and abort the fetch if the user navigated away."


Don't memorize paragraphs. Understand the concept, then explain it simply with one example. That is what interviewers remember.

Back to index


258. Bridging Fundamentals and Real Engineering

The Disconnect Is Real

Interviewers ask flatten arrays. You ship Web Vitals budgets. Both can coexist — the trick is answering the puzzle quickly, then pivoting to depth.

The "Answer + Pivot" Technique

Interviewer: "How do you flatten an array?"

You:

Warning

"Shallow — flat(1) or spread with concat. Deep — recursive loop or flat(Infinity). If no built-ins, I'd use a stack-based iterative approach to avoid recursion depth limits.

Note

In production I've applied similar tree-walking when flattening nested category trees from our CMS into filter options — same pattern, different domain."

You answered the puzzle in 20 seconds and showed senior thinking in 10 more.

When They Ask "What Do You Actually Work On?"

Have 3 prepared stories:

StoryDemonstrates
Performance winLCP/INP improvement with before/after metrics
State/architecture decisionWhy Zustand vs Redux, TanStack Query adoption
Production incidentHow you debugged and fixed a real bug

Questions to Ask Interviewers

Surface whether the role matches real engineering:

  • "How do you measure Core Web Vitals in production?"
  • "What's your approach to server state — Redux, React Query, or both?"
  • "What does your frontend architecture look like at scale?"
  • "How do frontend engineers participate in on-call or incident response?"

Their answers tell you if the team cares about what you've actually been doing for 5 years.

Quick Map — Puzzle → Real Skill

Interview puzzleReal-world parallel
Flatten arrayNested API/CMS tree traversal
DebounceSearch input, auto-save
Event loopAsync UI update ordering
Deep cloneImmutable state updates
Two sumFinding duplicate cart items, matching IDs
LRU cacheTanStack Query cache eviction
TopicOne-liner
Flatten arrayflat(Infinity), recursive, or stack iterative
LCPFast meaningful content — images, fonts, critical JS
CLSReserve space — dimensions, skeletons, font-display
INPResponsive input — defer heavy work, startTransition
State at scaleQuery for server, Zustand/Redux for global, useState local
React QueryHierarchical keys, staleTime, invalidate on mutation
TypeScriptStrict, Zod at boundary, discriminated unions
Monitoringweb-vitals → analytics, Sentry, Lighthouse CI
ArchitectureFeature folders, API layer, import boundaries
DebuggingRUM → reproduce → Profile → fix → verify

Fundamentals get you through the screen. Real engineering skills — Web Vitals, state architecture, production debugging — get you the offer and the job. Prepare for both.

Back to index


259. Browser Storage

Theory

StorageCapacityLifetimeScopeSent to server?
localStorage~5–10 MBUntil clearedPer originNo
sessionStorage~5–10 MBTab sessionPer tabNo
Cookies~4 KBConfigurable expiryPer domainYes (auto)
IndexedDBLarge (50MB+)Until clearedPer originNo

Pros & Cons

StorageBest forAvoid for
localStorageTheme, preferences, recent searchesAuth tokens (XSS risk)
sessionStorageForm wizard state, tab-specific dataCross-tab sync
CookiesSession ID, httpOnly authLarge data
IndexedDBOffline cart, large cache, imagesSimple key-value

Interview Answer

localStorage persists across sessions for preferences. sessionStorage dies with the tab. Cookies go to the server automatically — use httpOnly for auth. IndexedDB handles large structured offline data like cart cache.

Real Example

javascript
// localStorage — user preferences (non-sensitive)
localStorage.setItem('theme', 'dark')
localStorage.setItem('recentSearches', JSON.stringify(['milk', 'bread']))
 
// sessionStorage — checkout flow (tab-scoped)
sessionStorage.setItem('checkoutStep', '2')
sessionStorage.setItem('deliveryAddress', JSON.stringify(address))
 
// Cookies — set by server (httpOnly, Secure, SameSite)
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400
 
// IndexedDB — offline cart
const dbRequest = indexedDB.open('AppCart', 1)
dbRequest.onupgradeneeded = (e) => {
  const db = e.target.result
  db.createObjectStore('cart', { keyPath: 'productId' })
}

React Hook — localStorage

jsx
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch {
      return initialValue
    }
  })
 
  const setStoredValue = (newValue) => {
    const next = newValue instanceof Function ? newValue(value) : newValue
    setValue(next)
    localStorage.setItem(key, JSON.stringify(next))
  }
 
  return [value, setStoredValue]
}

Security Note (Interview Gold)

javascript
// ❌ NEVER — XSS can steal tokens
localStorage.setItem('jwt', token)
 
// ✅ Auth tokens in httpOnly cookies — JS cannot read them
// Server: Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict

Back to index


260. Build a traffic light system in React

Theory

This is a classic state + timing interview problem. Requirements:

  • Cycle: Red → Green → Yellow (repeat)
  • Durations: Red = 5s, Green = 3s, Yellow = 2s
  • Use useState for current light
  • Use useEffect + setTimeout for timing
  • Cleanup timeout on unmount or state change to prevent memory leaks

Pros & Cons of approaches

setTimeout in useEffectsetInterval
✅ Precise per-state duration❌ Fixed interval — awkward for different durations
✅ Easy cleanup❌ Drift over time
✅ Clear state machine

Real-Life Example — Full solution

tsx
import { useEffect, useState } from 'react'
 
type Light = 'red' | 'green' | 'yellow'
 
const LIGHT_CONFIG: Record<Light, { duration: number; next: Light; label: string }> = {
  red: { duration: 5000, next: 'green', label: 'STOP' },
  green: { duration: 3000, next: 'yellow', label: 'GO' },
  yellow: { duration: 2000, next: 'red', label: 'SLOW' },
}
 
function TrafficLight() {
  const [activeLight, setActiveLight] = useState<Light>('red')
  const [countdown, setCountdown] = useState(LIGHT_CONFIG.red.duration / 1000)
 
  useEffect(() => {
    const { duration, next } = LIGHT_CONFIG[activeLight]
    const seconds = duration / 1000
 
    setCountdown(seconds)
 
    // Countdown ticker (updates every second)
    const countdownInterval = setInterval(() => {
      setCountdown((prev) => (prev > 1 ? prev - 1 : seconds))
    }, 1000)
 
    // Switch to next light after duration
    const switchTimer = setTimeout(() => {
      setActiveLight(next)
    }, duration)
 
    // Cleanup — critical for interview points
    return () => {
      clearTimeout(switchTimer)
      clearInterval(countdownInterval)
    }
  }, [activeLight])
 
  return (
    <div className="traffic-light-container">
      <h2>Traffic Light</h2>
 
      <div className="traffic-light" role="status" aria-live="polite">
        <LightBulb color="red" isActive={activeLight === 'red'} />
        <LightBulb color="yellow" isActive={activeLight === 'yellow'} />
        <LightBulb color="green" isActive={activeLight === 'green'} />
      </div>
 
      <p className="status">
        {LIGHT_CONFIG[activeLight].label} — {countdown}s remaining
      </p>
    </div>
  )
}
 
function LightBulb({ color, isActive }: { color: Light; isActive: boolean }) {
  return (
    <div
      className={`bulb bulb--${color} ${isActive ? 'bulb--active' : ''}`}
      aria-label={`${color} light ${isActive ? 'on' : 'off'}`}
    />
  )
}
 
export default TrafficLight
css
.traffic-light-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  font-family: system-ui, sans-serif;
}
 
.traffic-light {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border-radius: 12px;
  background: #222;
  padding: 16px;
}
 
.bulb {
  opacity: 0.2;
  transition:
    opacity 0.3s,
    box-shadow 0.3s;
  border-radius: 50%;
  width: 60px;
  height: 60px;
}
 
.bulb--red {
  background: #ff4444;
}
.bulb--yellow {
  background: #ffcc00;
}
.bulb--green {
  background: #44cc44;
}
 
.bulb--active {
  opacity: 1;
  box-shadow: 0 0 20px currentColor;
}
 
.bulb--red.bulb--active {
  box-shadow: 0 0 24px #ff4444;
}
.bulb--yellow.bulb--active {
  box-shadow: 0 0 24px #ffcc00;
}
.bulb--green.bulb--active {
  box-shadow: 0 0 24px #44cc44;
}
 
.status {
  font-weight: 600;
  font-size: 1.25rem;
}

Alternative — state machine with single effect (cleaner)

tsx
import { useEffect, useReducer } from 'react'
 
type Light = 'red' | 'green' | 'yellow'
 
const SEQUENCE: { light: Light; duration: number }[] = [
  { light: 'red', duration: 5000 },
  { light: 'green', duration: 3000 },
  { light: 'yellow', duration: 2000 },
]
 
function trafficReducer(state: number) {
  return (state + 1) % SEQUENCE.length
}
 
function TrafficLightMachine() {
  const [index, dispatch] = useReducer(trafficReducer, 0)
  const { light, duration } = SEQUENCE[index]
 
  useEffect(() => {
    const timer = setTimeout(() => dispatch(), duration)
    return () => clearTimeout(timer)
  }, [index, duration])
 
  const colors: Light[] = ['red', 'green', 'yellow']
 
  return (
    <div className="traffic-light">
      {colors.map((c) => (
        <div key={c} className={`bulb bulb--${c} ${light === c ? 'bulb--active' : ''}`} />
      ))}
      <p>
        Current: {light.toUpperCase()} ({duration / 1000}s)
      </p>
    </div>
  )
}

What interviewers look for

CheckpointWhy
Correct cycle orderRed → Green → Yellow
Correct timings5s, 3s, 2s
useEffect cleanupclearTimeout on unmount
Dependency array[activeLight] triggers next cycle
Accessible markuparia-live, aria-label on bulbs
No memory leaksDon't stack timers without cleanup

Cycle diagram

plaintext
     ┌──────────────────────────────────────┐
     │                                      │
     ▼                                      │
  ┌──────┐  5s   ┌───────┐  3s   ┌────────┐ │
  │ RED  │ ───► │ GREEN │ ───► │ YELLOW │─┘
  └──────┘       └───────┘       └────────┘
                                      2s

Back to index


261. Caching (Client + Server)

Theory

Caching stores responses to avoid redundant network requests and computation.

Client-side layers:

  • HTTP cache — browser cache via Cache-Control headers
  • Service Worker — offline cache (PWA)
  • React Query / SWR — in-memory cache with stale-while-revalidate
  • localStorage / IndexedDB — persistent client cache

Server-side layers:

  • CDN — edge cache for static assets and SSR pages
  • Redis / Memcached — API response cache
  • ISR — regenerate static pages on interval

Pros & Cons

Aggressive cachingNo caching
✅ Fast repeat visits❌ Every request hits server
✅ Lower server load❌ Poor UX on slow networks
❌ Stale data risk❌ Higher CDN/API costs
❌ Cache invalidation complexity

Real-Life Example

javascript
// HTTP cache headers (server)
// Static assets — immutable
Cache-Control: public, max-age=31536000, immutable
 
// API — short TTL
Cache-Control: private, max-age=60, stale-while-revalidate=30
tsx
// React Query — client cache
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // fresh for 5 min
      gcTime: 30 * 60 * 1000, // garbage collect after 30 min
      refetchOnWindowFocus: true,
    },
  },
})
 
// Prefetch on hover
function ProductLink({ id }) {
  const qc = useQueryClient()
  return (
    <Link
      to={`/product/${id}`}
      onMouseEnter={() =>
        qc.prefetchQuery({
          queryKey: ['product', id],
          queryFn: () => fetchProduct(id),
        })
      }>
      View Product
    </Link>
  )
}
javascript
// Service Worker — stale-while-revalidate
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      caches.open('api-v1').then((cache) =>
        cache.match(event.request).then((cached) => {
          const network = fetch(event.request).then((res) => {
            cache.put(event.request, res.clone())
            return res
          })
          return cached || network
        }),
      ),
    )
  }
})

Back to index


262. Can We Use Hooks inside Conditions?

Theory

No. Hooks must be called at the top level of a React function — never inside if, for, loops, or nested functions.

Why: React stores hooks in a linked list on the Fiber node, indexed by call order. Conditional hooks change the order between renders, causing state to map to the wrong hook.

Interview Answer

No — hooks must run in the same order every render. Conditional hooks break React's internal linked list and cause state bugs. Use conditional logic inside the hook, not around it.

Real Example

jsx
// ❌ WRONG
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null) // breaks rules!
  }
}
 
// ✅ CORRECT — hook always called, condition inside
function Good({ isLoggedIn }) {
  const [user, setUser] = useState(null)
 
  useEffect(() => {
    if (isLoggedIn) {
      fetchUser().then(setUser)
    } else {
      setUser(null)
    }
  }, [isLoggedIn])
}
 
// ✅ CORRECT — early return AFTER all hooks
function Dashboard({ isLoggedIn }) {
  const [data, setData] = useState(null)
  useEffect(() => {
    if (isLoggedIn) loadData()
  }, [isLoggedIn])
 
  if (!isLoggedIn) return <Login /> // OK — after hooks
  return <div>{data}</div>
}

Back to index


263. Class Lifecycle Methods

Theory

PhaseMethods
Mountconstructor, render, componentDidMount
Updaterender, componentDidUpdate
UnmountcomponentWillUnmount
ErrorcomponentDidCatch, getDerivedStateFromError

Deprecated: componentWillMount, componentWillReceiveProps, componentWillUpdate.

Interview Answer

Mount: componentDidMount for API calls. Update: componentDidUpdate when props change. Unmount: componentWillUnmount for cleanup. Error: componentDidCatch.

Real Example

jsx
class UserList extends React.Component {
  componentDidMount() {
    this.fetchUsers()
  }
  componentDidUpdate(prevProps) {
    if (prevProps.teamId !== this.props.teamId) this.fetchUsers()
  }
  componentWillUnmount() {
    this.abortController?.abort()
  }
  componentDidCatch(error) {
    this.setState({ error })
  }
}

Back to index


264. Coding: Find the first non-repeated character

Theory

Find the first character in a string that appears exactly once. Two-pass approach:

  1. Count frequency of each character
  2. Iterate string left-to-right, return first char with count === 1

Time: O(n), Space: O(k) where k = unique characters.

Pros & Cons

Hash map (Map)Array (ASCII)
✅ Works with Unicode✅ O(1) space for limited charset
✅ Handles any character❌ Limited to known charset size
Slightly more memoryFaster for English-only

Real-Life Example

Input: "I love coding"

javascript
function firstNonRepeated(str) {
  const freq = new Map()
 
  for (const char of str) {
    freq.set(char, (freq.get(char) || 0) + 1)
  }
 
  for (const char of str) {
    if (freq.get(char) === 1) return char
  }
 
  return null
}
 
console.log(firstNonRepeated('I love coding')) // "I"

Walk-through:

plaintext
Char frequencies:
  " " → 1
  "I" → 1  ← first non-repeated (index 0)
  "l" → 1  (but "I" comes first)
  "o" → 2
  "v" → 1
  "e" → 2
  "c" → 1
  "d" → 1
  "i" → 1
  "n" → 1
  "g" → 1
 
Scan left to right: "I" at index 0 has freq 1 → return "I"
javascript
// Case-sensitive variant
firstNonRepeated('I love coding') // "I"
firstNonRepeated('i love coding') // "i"
 
// Ignore spaces (if interviewer asks)
function firstNonRepeatedNoSpaces(str) {
  const cleaned = str.replace(/\s/g, '')
  const freq = new Map()
  for (const char of cleaned) freq.set(char, (freq.get(char) || 0) + 1)
  for (const char of cleaned) if (freq.get(char) === 1) return char
  return null
}
firstNonRepeatedNoSpaces('I love coding') // "I" (still first unique)

Back to index


265. Common JavaScript Gotchas

Theory

JavaScript has quirks from its design history. Interviewers test whether you know the traps — not to trick you, but to verify deep understanding.

Top gotchas

Real-Life Example

1. typeof null === "object"

javascript
typeof null // "object" — historical bug
null === null // true — use this instead

2. Floating point precision

javascript
0.1 + 0.2 === 0.3 // false (0.30000000000000004)
// Fix: Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON
// Or: use integers (paise not rupees)
const totalPaise = 10 + 20 // 30 paise
const total = totalPaise / 100 // 0.3

3. == vs ===

javascript
;[] == false // true
;[] === false // false
// Always use ===

4. Closure in loop with var

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0) // 3, 3, 3
}
// Fix: use let

5. this in callbacks

javascript
const obj = {
  name: 'Amit',
  getName() {
    return this.name
  },
}
const fn = obj.getName
fn() // undefined — lost context
// Fix: fn = obj.getName.bind(obj) or arrow in class field

6. Array holes

javascript
const arr = [1, , 3]
arr.map((x) => x * 2) // [2, empty, 6] — skips empty slot
arr.length // 3

7. parseInt without radix

javascript
parseInt('08') // 8 (modern) but was octal in old ES3
parseInt('08', 10) // always safe

8. JSON.stringify limitations

javascript
JSON.stringify({ a: undefined, b: function () {} })
// '{"b":{}}' — undefined and functions omitted
JSON.stringify({ date: new Date() })
// '{"date":"2026-03-15T..."}' — Date becomes string

9. NaN is not equal to itself

javascript
NaN === NaN // false
Number.isNaN(NaN) // true
isNaN('hello') // true (coerces!)
Number.isNaN('hello') // false

10. Sort mutates and is lexicographic by default

javascript
;[10, 2, 1].sort() // [1, 10, 2] — string sort!
;[10, 2, 1].sort((a, b) => a - b) // [1, 2, 10]

Back to index


266. Conditional Rendering

Theory

Render different UI based on state or props using:

  • if/else before return
  • Ternary condition ? <A /> : <B />
  • Logical AND isLoggedIn && <Dashboard />
  • Early return for loading/error states

Interview Answer

I use early returns for loading and error states, ternary for two branches, and && for show/hide — always after all hooks are called.

Real Example

jsx
function UserProfile({ userId }) {
  const { data, isLoading, error } = useUser(userId)
 
  if (isLoading) return <Spinner />
  if (error) return <ErrorMessage error={error} />
  if (!data) return <EmptyState message="User not found" />
 
  return (
    <div>
      <h1>{data.name}</h1>
      {data.isPremium && <PremiumBadge />}
    </div>
  )
}

Back to index


267. const vs let

Theory

Use const by default. Use let only when reassignment is needed. Never use var.

const prevents rebinding but allows object/array mutation.

Interview Answer

const is default — it signals intent that the binding won't change. let when I need reassignment like counters or loop variables. const objects can still be mutated.

Real Example

javascript
const API_URL = '/api/v2' // never reassign
const user = { name: 'Amit' }
user.name = 'Rahul' // ✅ mutation OK
 
let retryCount = 0 // will reassign
retryCount++
 
for (let i = 0; i < 3; i++) {
  // block-scoped loop var
  setTimeout(() => console.log(i), 100)
}

Back to index


268. Cross Browser Compatibility

Theory

Different browsers (Chrome, Firefox, Safari, Edge) and versions implement web standards with varying support. Cross-browser compatibility ensures your app works consistently across target browsers.

Tools: Can I Use, Autoprefixer, Polyfills, Babel, Normalized CSS.

Pros & Cons

Supporting older browsersModern-only
✅ Wider audience✅ Smaller bundles, modern APIs
❌ Polyfills increase bundle❌ Excludes IE/old Android users
❌ More testing surface❌ May lose enterprise clients

Real-Life Example

css
/* Autoprefixer adds vendor prefixes automatically */
.grid {
  display: grid;
  /* Autoprefixer outputs: */
  /* display: -ms-grid; */
  /* display: grid; */
}
 
.card {
  user-select: none;
  /* → -webkit-user-select: none; */
  /* → user-select: none; */
}
javascript
// Feature detection — not browser detection
if ("IntersectionObserver" in window) {
  setupInfiniteScroll();
} else {
  setupPagination(); // fallback
}
 
// browserslist in package.json
{
  "browserslist": [">0.2%", "not dead", "not op_mini all"]
}
tsx
// CSS @supports fallback
@supports (display: grid) {
  .layout { display: grid; }
}
@supports not (display: grid) {
  .layout { display: flex; flex-wrap: wrap; }
}

Back to index


269. CSS Specificity

Theory

Specificity determines which CSS rule wins when multiple rules target the same element. Calculated as weights:

SelectorWeight
Inline style1000
#id100
.class, [attr], :pseudo-class10
element, ::pseudo-element1
* (universal)0

!important overrides specificity (avoid in production).

Interview Answer

Specificity decides which CSS rule applies — inline beats ID beats class beats element. I avoid !important and keep selectors flat to prevent specificity wars.

Real Example

css
/* Specificity: 1 */
p {
  color: black;
}
 
/* Specificity: 10 */
.text {
  color: blue;
}
 
/* Specificity: 100 */
#main {
  color: green;
}
 
/* Specificity: 11 — .text + p */
.text p {
  color: red;
} /* wins over p and .text alone */
 
/* Specificity: 110 — #main + .text */
#main .text {
  color: purple;
} /* wins */
html
<p class="text" id="main">Color is purple (#main .text = 110)</p>

Back to index


270. Data Types

Theory

JavaScript has 8 data types — 7 primitives and 1 non-primitive:

TypetypeofMutableStored
string"string"NoValue
number"number"NoValue
boolean"boolean"NoValue
undefined"undefined"NoValue
null"object" ⚠️NoValue
symbol"symbol"NoValue
bigint"bigint"NoValue
object"object"YesReference

Primitives are immutable and compared by value. Objects (including arrays, functions, dates) are compared by reference.

Pros & Cons

PrimitivesObjects
✅ Fast comparison by value✅ Can hold complex nested data
✅ Immutable — thread-safe conceptually✅ Methods and properties
❌ Limited structure❌ Compared by reference — easy bugs
❌ Mutable — accidental changes

Real-Life Example

javascript
// Primitives — passed by value
let price = 299
let discount = price
discount = 199
console.log(price) // 299 — unchanged
 
// Objects — passed by reference
const orderA = { total: 500 }
const orderB = orderA
orderB.total = 600
console.log(orderA.total) // 600 — same object!
 
// typeof quirks
typeof null // "object" (historical bug)
typeof [] // "object"
typeof function () {} // "function" (special case)
Array.isArray([]) // true — reliable array check
 
// Checking for null/undefined safely
const value = null
value == null // true (checks null OR undefined)
value === null // true
value ?? 'default' // "default" (nullish coalescing)
javascript
// Real-life: API response typing mindset
const user = {
  id: 'u_42',
  name: 'Amit',
  roles: ['admin', 'editor'],
  metadata: { lastLogin: '2026-03-15' },
}
// typeof user === "object" — always check structure, not just type

Back to index


271. Debugging Real-World Performance Issues

Theory

Production performance bugs rarely announce themselves. Systematic debugging:

  1. Reproduce — RUM data, user report, Sentry trace
  2. Measure — Chrome DevTools Performance tab
  3. Identify — long tasks, layout thrashing, memory leaks
  4. Fix — targeted, not premature optimization
  5. Verify — before/after vitals in staging

Interview Answer

I start with RUM data and Sentry traces to find the slow page and long tasks, reproduce in Chrome Performance panel, identify whether it's network, render, or JS blocking, fix with evidence, and verify with Lighthouse before/after.

Debugging Checklist

SymptomLikely causeTool
Slow first loadLarge JS bundle, no code splitCoverage tab, webpack analyzer
Page jumps on loadImages/fonts without dimensionsLayout Shift regions in Performance
Scroll jankToo many DOM nodes, no virtualizationPerformance → Frames
Input lag (bad INP)Long tasks on main threadPerformance → Main thread flame chart
Memory grows over timeLeaked listeners, intervals, closuresMemory tab → heap snapshots
API feels slowWaterfall, no parallelizationNetwork tab
Re-render stormContext value, missing memoReact DevTools Profiler

React DevTools Profiler

jsx
// Find unnecessary re-renders
// 1. Record interaction in Profiler
// 2. Look for components that re-rendered but props didn't change
// 3. Fix: memo, useMemo, split context, move state down
 
// Common fix — unstable reference
// ❌ New object every render
;<Child config={{ theme: 'dark', size: 'lg' }} />
 
// ✅ Stable reference
const config = useMemo(() => ({ theme: 'dark', size: 'lg' }), [])
;<Child config={config} />

Long Task Detection

javascript
// Observe long tasks in production
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      sendToAnalytics({
        type: 'long-task',
        duration: entry.duration,
        page: location.pathname,
      })
    }
  }
})
observer.observe({ type: 'longtask', buffered: true })

Memory Leak Pattern

jsx
// ❌ Leak — listener not removed
useEffect(() => {
  window.addEventListener('scroll', handleScroll)
}, [])
 
// ✅ Cleanup
useEffect(() => {
  window.addEventListener('scroll', handleScroll)
  return () => window.removeEventListener('scroll', handleScroll)
}, [handleScroll])
 
// ❌ Leak — interval in module scope
setInterval(pollStatus, 1000)
 
// ✅ Cleanup + AbortController for fetch
useEffect(() => {
  const id = setInterval(pollStatus, 1000)
  const controller = new AbortController()
  fetch(url, { signal: controller.signal })
  return () => {
    clearInterval(id)
    controller.abort()
  }
}, [])

Case Study — Slow Product List

plaintext
Problem:  LCP 4.2s on /products, INP 450ms on filter
RUM:      80% users on mobile 3G
Step 1:   Network — 1.2MB JS bundle (main culprit)
Fix 1:    Route-level code splitting → bundle 380KB
Step 2:   LCP element — unoptimized category hero image
Fix 2:    WebP + priority preload → LCP 2.1s
Step 3:   Filter input — re-renders 500-item list every keystroke
Fix 3:    useDeferredValue + memo ProductCard → INP 120ms
Verify:   Lighthouse CI passed, RUM p75 LCP < 2.5s after 48h

Back to index


272. Deep Copy vs Shallow Copy

Theory

Shallow CopyDeep Copy
Top levelNew referenceNew reference
Nested objectsSharedIndependent copies
Methodsspread, Object.assign, slicestructuredClone, JSON, recursive
Mutation riskNested changes affect bothFully isolated

Interview Answer

Shallow copy shares nested references — mutating nested object affects both. Deep copy recursively clones everything — use structuredClone in modern JS.

Real Example

javascript
const original = {
  name: 'Amit',
  scores: [90, 85],
  address: { city: 'Bangalore' },
}
 
// Shallow
const shallow = { ...original }
shallow.scores.push(100)
console.log(original.scores) // [90, 85, 100] — mutated!
 
// Deep — structuredClone (modern)
const deep = structuredClone(original)
deep.scores.push(100)
console.log(original.scores) // [90, 85] — unchanged
 
// Deep — JSON (limitations: no Date, Map, undefined, functions)
const jsonDeep = JSON.parse(JSON.stringify(original))
 
// Deep — manual recursive
function deepClone(value, seen = new WeakMap()) {
  if (value === null || typeof value !== 'object') return value
  if (seen.has(value)) return seen.get(value)
  if (value instanceof Date) return new Date(value)
 
  const clone = Array.isArray(value) ? [] : {}
  seen.set(value, clone)
 
  for (const key of Reflect.ownKeys(value)) {
    clone[key] = deepClone(value[key], seen)
  }
  return clone
}
javascript
// React — immutable update is shallow spread per level
setUser((prev) => ({
  ...prev,
  address: { ...prev.address, city: 'Delhi' }, // deep enough for one nested level
}))

Back to index


273. Dependent field state structure

Theory

When field B depends on field A (e.g., city depends on state, verification method depends on ID type), flat state becomes awkward. Structure options:

  1. Derived state — compute B options from A, don't store redundantly
  2. Nested state — { address: { state, city } }
  3. Reset dependent on parent change — when A changes, clear B

Pros & Cons

Derived (computed)Stored separately
✅ Single source of truth❌ Can desync (city invalid for new state)
✅ No stale city when state changes❌ Must manually reset
Must recalculate on renderExtra sync logic

Real-Life Example

tsx
const STATES_CITIES: Record<string, string[]> = {
  Karnataka: ['Bangalore', 'Mysore'],
  Maharashtra: ['Mumbai', 'Pune'],
}
 
function AddressForm() {
  const [state, setState] = useState('')
  const [city, setCity] = useState('')
 
  // Derived — cities depend on state
  const cityOptions = state ? (STATES_CITIES[state] ?? []) : []
 
  const handleStateChange = (newState: string) => {
    setState(newState)
    setCity('') // reset dependent field — CRITICAL
  }
 
  return (
    <>
      <Select label="State" value={state} onChange={handleStateChange} options={Object.keys(STATES_CITIES)} />
      <Select label="City" value={city} onChange={setCity} options={cityOptions} disabled={!state} />
    </>
  )
}
 
// KYC: IFSC depends on bank selection
function BankDetails() {
  const [bankCode, setBankCode] = useState('')
  const [ifsc, setIfsc] = useState('')
 
  const handleBankChange = (code: string) => {
    setBankCode(code)
    setIfsc('') // reset
  }
 
  // Validate IFSC prefix matches bank
  const validateIFSC = (value: string) => {
    if (!bankCode) return 'Select bank first'
    if (!value.startsWith(bankCode.slice(0, 4))) return "IFSC doesn't match selected bank"
    return null
  }
}

State design answer for a fintech app: "Parent field change dispatches SET_FIELD for parent + clears child. Child options are derived, not stored. Cross-field validation runs on submit and on parent blur."

Back to index


274. Destructuring

Theory

Destructuring extracts values from arrays or properties from objects into distinct variables. It works in assignments, function parameters, and nested structures.

Supports defaults, renaming, and rest patterns.

Pros & Cons

ProsCons
Cleaner variable extractionCan be overused on deeply nested data
Self-documenting function paramsUndefined nested destructuring throws
Easy swap without temp variableDefaults only apply for undefined, not null

Real-Life Example

javascript
// Object destructuring
const user = { id: 'u_1', name: 'Amit', email: 'amit@test.com', role: 'admin' }
const { name, email, role = 'user' } = user
const { id: userId } = user // rename
 
// Nested
const order = {
  id: 'ORD-1',
  customer: { name: 'Rahul', city: 'Delhi' },
  items: [{ name: 'Biryani', price: 299 }],
}
const {
  customer: { name: customerName },
  items: [firstItem],
} = order
 
// Array destructuring
const [first, second, ...rest] = [10, 20, 30, 40]
// first=10, second=20, rest=[30,40]
 
// Function parameters
function renderOrder({ id, status, items = [] }) {
  return `Order ${id}: ${status} (${items.length} items)`
}
 
// Swap without temp
let a = 1,
  b = 2
;[a, b] = [b, a]
 
// React — props destructuring
function ProductCard({ name, price, image, onAddToCart }) {
  return (
    <div>
      <img src={image} alt={name} />
      <h3>{name}</h3>
      <p>₹{price}</p>
      <button onClick={onAddToCart}>Add</button>
    </div>
  )
}
 
// API response
const {
  data: restaurants,
  meta: { total, page },
} = await apiResponse

Back to index


275. Difference between unique_ptr and shared_ptr

unique_ptr — exclusive ownership

cpp
#include <memory>
 
auto ptr1 = std::make_unique<int>(10);
// auto ptr2 = ptr1;              // ❌ compile error — not copyable
auto ptr2 = std::move(ptr1);      // ✅ transfer ownership
// ptr1 is now nullptr
  • Zero overhead vs raw pointer (no ref counting)
  • Cannot be copied, only moved
  • Use by default

shared_ptr — shared ownership

cpp
auto sp1 = std::make_shared<int>(20);
auto sp2 = sp1; // ref count = 2
 
{
  auto sp3 = sp1; // ref count = 3
} // sp3 destroyed, ref count = 2
 
// Memory freed when ref count hits 0
  • Reference counted — thread-safe ref count increment/decrement
  • Slight overhead (control block)
  • Risk of circular references → use weak_ptr to break cycles

When to use which

ScenarioChoice
Single ownerunique_ptr
Shared ownership (cache, graph)shared_ptr
Observer (break cycles)weak_ptr
Factory returns ownershipunique_ptr

Back to index


276. Difference between vector and list

Featurestd::vectorstd::list
MemoryContiguous arrayDoubly linked nodes
Random accessO(1) operator[]O(n)
Insert at endO(1) amortizedO(1)
Insert in middleO(n) — shift elementsO(1) with iterator
Cache localityExcellentPoor (pointer chasing)
Memory overheadLowHigh (two pointers per node)
Iterator invalidationOn reallocation / eraseOnly erased element

When to use

cpp
// vector — default choice (cache-friendly, fast iteration)
std::vector<int> nums = {1, 2, 3, 4, 5};
nums.push_back(6);           // fast
int x = nums[2];             // O(1) random access
 
// list — frequent middle insert/erase, no reallocation needed
std::list<int> lst = {1, 2, 3};
auto it = std::next(lst.begin());
lst.insert(it, 99);          // O(1), no invalidation of other iterators

Performance

In practice, vector wins most of the time due to cache locality — even insert-in-middle can be faster on vector for small/medium sizes.

Back to index


277. Difference between Deep Copy and Shallow Copy

Theory

Shallow CopyDeep Copy
Top-levelNew referenceNew reference
Nested objectsShared referenceNew references
MethodsObject.assign, spread {...obj}, Array.slicestructuredClone, recursive fn, JSON.parse/stringify

Practical Example

javascript
const original = {
  name: 'Amit',
  address: { city: 'Delhi', pin: 110001 },
  hobbies: ['cricket', 'coding'],
}
 
// Shallow copy
const shallow = { ...original }
shallow.address.city = 'Mumbai'
console.log(original.address.city) // "Mumbai" — nested object shared!
 
// Deep copy — structuredClone (modern)
const deep = structuredClone(original)
deep.address.city = 'Bangalore'
console.log(original.address.city) // "Delhi" — unchanged
 
// Deep copy — JSON trick (limitations: no Date, Map, undefined, functions)
const jsonDeep = JSON.parse(JSON.stringify(original))

Manual deep copy function

javascript
function deepClone(value, seen = new WeakMap()) {
  if (value === null || typeof value !== 'object') return value
  if (seen.has(value)) return seen.get(value) // handle circular refs
 
  if (value instanceof Date) return new Date(value)
  if (value instanceof RegExp) return new RegExp(value)
 
  const clone = Array.isArray(value) ? [] : {}
  seen.set(value, clone)
 
  for (const key of Reflect.ownKeys(value)) {
    clone[key] = deepClone(value[key], seen)
  }
 
  return clone
}

Back to index


278. Difference between Normal Functions and Arrow Functions

Theory

Both define functions, but differ in syntax, this binding, and capabilities.

FeatureNormal functionArrow function
Syntaxfunction fn() {}const fn = () => {}
thisDynamic — depends on how calledLexical — inherited from enclosing scope
argumentsAvailableNot available — use ...args
new keywordCan be constructorCannot
prototypeHas .prototypeNo
Implicit returnNo (unless IIFE)Yes for single expressions
yield (generators)YesNo

Pros & Cons

Normal functionsArrow functions
✅ Correct this for object methods✅ Concise syntax
✅ Can be constructors✅ Fixes this in callbacks
✅ Has arguments✅ Great for array methods
❌ this lost in callbacks❌ Wrong this for object methods
❌ Not usable as constructors

Real-Life Example

javascript
const orderService = {
  orders: ['ORD-1', 'ORD-2'],
 
  // ✅ Normal — `this` is orderService
  listOrders() {
    return this.orders
  },
 
  // ❌ Arrow — `this` is NOT orderService
  brokenList: () => this?.orders, // undefined
 
  // ✅ Arrow inside normal — inherits `this` from listWithIds
  listWithIds() {
    return this.orders.map((id) => `ID: ${id}`)
  },
}
 
// Callback pattern
button.addEventListener('click', function () {
  this.classList.add('active') // `this` = button element
})
 
button.addEventListener('click', () => {
  // `this` = enclosing scope (e.g., component instance or undefined)
})
tsx
// React — arrow functions are standard in function components
function OrderList({ orders }) {
  const handleCancel = (id: string) => cancelOrder(id) // arrow — no `this` needed
 
  return orders.map((order) => <OrderCard key={order.id} order={order} onCancel={() => handleCancel(order.id)} />)
}

Back to index


279. Difference between PATCH and PUT

Theory

Both are HTTP methods for updating resources, but they differ in scope and idempotency expectations.

  • PUT replaces the entire resource with the request body. Missing fields are typically set to null/default.
  • PATCH applies partial updates — only the fields in the request body are changed.

Pros & Cons

PUTPATCH
ProsIdempotent, simple full replacement semanticsBandwidth-efficient, precise partial updates
ConsMust send full resource, risk overwriting fieldsHarder to implement correctly, less universally supported

Real-Life Example

http
PUT /api/users/42
Content-Type: application/json
 
{
  "name": "Amit Sharma",
  "email": "amit@example.com",
  "phone": "9876543210",
  "address": "Delhi"
}
# If phone was omitted, it gets cleared
 
# PATCH — update only the phone number
PATCH /api/users/42
Content-Type: application/json
 
{
  "phone": "9876543210"
}
# name, email, address remain unchanged
javascript
// Frontend usage
await fetch('/api/users/42', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ phone: newPhone }),
})

Back to index


280. Difference between position: relative, absolute, fixed, and static

Theory

The CSS position property controls how an element is placed in the document and whether it's removed from normal flow.

ValueRemoved from flow?Positioned relative toScroll behavior
staticNo (default)Normal document flowScrolls with page
relativeNoIts own original positionScrolls with page
absoluteYesNearest positioned ancestor (or viewport)Scrolls with ancestor
fixedYesViewportStays fixed on screen

top, right, bottom, left, and z-index only work when position is not static.

Pros & Cons

PositionBest forAvoid when
staticNormal layoutYou need offset control
relativeOffset without leaving flow, containing block for absolute childrenFull-page overlays
absoluteTooltips, badges, dropdowns inside a containerMain page layout
fixedSticky headers, modals, FAB buttonsInside scrollable containers (use sticky)

Real-Life Example

css
/* static — default, no offset */
.card {
  position: static;
}
 
/* relative — nudge element, create positioning context */
.card-badge {
  position: relative;
  top: -8px; /* visual offset, space still reserved in flow */
}
 
.product-card {
  position: relative; /* containing block for absolute children */
}
 
/* absolute — positioned inside .product-card */
.sale-tag {
  position: absolute;
  top: 8px;
  right: 8px;
  background: red;
  padding: 4px 8px;
  color: white;
}
 
/* fixed — stays on screen while scrolling */
.app-header {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 100;
}
 
.floating-cart-btn {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 50;
}
html
<!-- Visual hierarchy -->
<div class="product-card" style="position: relative">
  <img src="biryani.jpg" alt="Biryani" />
  <span class="sale-tag" style="position: absolute; top: 8px; right: 8px">20% OFF</span>
</div>
plaintext
Document flow with absolute:
 
┌─ .product-card (relative) ─────────┐
│  ┌ SALE TAG (absolute) ┐           │
│  │  top:8 right:8     │           │
│  └────────────────────┘           │
│  [Product Image]                   │
│  Product Name — ₹299               │
└────────────────────────────────────┘

Bonus — position: sticky: Acts as relative until scroll threshold, then fixed within its container. Great for table headers and section nav.

Back to index


281. Difference between undefined and not defined

Theory

These are often confused but mean different things:

undefinedNot defined
MeaningVariable exists but has no assigned valueVariable does not exist in scope
Error?No error when accessedReferenceError
typeof"undefined"ReferenceError (can't use typeof on undeclared)
Common causesDeclared but not initialized, missing object property, function with no returnTypo in variable name, accessing before declaration (TDZ)

Pros & Cons

Knowing the differenceConfusing them
✅ Faster debugging❌ Misdiagnose ReferenceError as undefined
✅ Correct typeof checks❌ Use == null incorrectly
✅ Understand optional chaining

Real-Life Example

javascript
// undefined — variable exists, no value
let username
console.log(username) // undefined
console.log(typeof username) // "undefined"
 
const user = { name: 'Amit' }
console.log(user.age) // undefined (property doesn't exist)
console.log(typeof user.age) // "undefined"
 
function getDiscount() {
  // no return statement
}
console.log(getDiscount()) // undefined
 
// Not defined — variable doesn't exist
console.log(usrname) // ReferenceError: usrname is not defined (typo)
 
console.log(notDeclared) // ReferenceError
 
// TDZ — technically "not defined" until declaration line
// console.log(count);        // ReferenceError (TDZ, not undefined)
let count = 5
javascript
// Safe checks in production
if (user.email !== undefined) {
  /* has email field */
}
if (user.email == null) {
  /* null OR undefined */
}
const email = user.email ?? 'guest@example.com' // nullish coalescing
 
// Optional chaining
const city = user?.address?.city // undefined if any part missing, no error

Interview answer (concise)

Warning

undefined means the variable exists but has no value — declared but not assigned, missing property, or function with no return. "Not defined" means the variable was never declared — you get a ReferenceError. A typo like usrname instead of username is not defined; let x; console.log(x) is undefined.

Back to index


282. display: none vs visibility: hidden

Theory

display: nonevisibility: hidden
Visible?NoNo
Takes space?No — removed from layoutYes — blank space remains
AccessibilityHidden from screen readersHidden from screen readers
Triggers reflow?YesNo
Children visible?All hiddenCan override with visible on child
Transitions?No smooth fadeCan animate opacity separately

Interview Answer

display none removes the element from layout entirely. visibility hidden hides it but keeps its space. I use none for conditional UI, visibility for layout-preserving hide or animations.

Real Example

css
/* Remove from layout — modals closed, mobile menu hidden */
.modal-closed {
  display: none;
}
 
/* Hide but preserve space — skeleton placeholders, accessibility */
.sr-only-hidden {
  visibility: hidden;
  width: 0;
  height: 0;
}
 
/* Toggle menu */
.nav-mobile {
  display: none;
}
.nav-mobile.open {
  display: block;
}
 
/* Collapse table column without reflow */
.col-hidden {
  visibility: hidden;
}

Back to index


283. Engineering Quality & Ownership

What they're assessing

Testing, monitoring, on-call mindset, production responsibility.

Ownership signals

SignalExample phrase
Prevent regressions"I added E2E for critical paths in CI"
Monitor production"Dashboards for error rate and Core Web Vitals"
Postmortems"Blameless RCA after checkout outage"
Mentorship"Pairing juniors on hooks and testing"
Security"OWASP review on file upload flow"

Sample answer — Ownership

Warning

"I treat merged code as my responsibility until it's stable in production. For a payment widget, I added unit + integration tests, Sentry alerts on JS errors, and feature flags for rollback. When error rate spiked after a release, I rolled back within 12 minutes, led a blameless postmortem, and added a missing null check plus a test that would have caught it. I don't hand off to QA and disappear."

Interview Answer

Quality means tests on critical paths, observability in production, and owning incidents end-to-end — fix, learn, and harden the system so the same class of bug can't recur.

Back to index


284. Event Bubbling and Capturing

Theory

Events travel in three phases:

  1. Capturing — window → target (top down)
  2. Target — event reaches element
  3. Bubbling — target → window (bottom up)

Default listeners run in bubbling phase. Use { capture: true } for capturing.

Event delegation attaches one listener on parent, uses bubbling + event.target.

Interview Answer

Events bubble from target to root by default. Capturing goes the opposite direction. Delegation uses one parent listener and event.target to handle dynamic children.

Real Example

html
<div id="list">
  <button data-id="1">Pay</button>
  <button data-id="2">Refund</button>
</div>
javascript
// Bubbling — click button → list → body → html
document.getElementById('list').addEventListener('click', (e) => {
  const btn = e.target.closest('button[data-id]')
  if (!btn) return
  handlePayment(btn.dataset.id)
})
 
// stopPropagation — modal content click doesn't close overlay
overlay.addEventListener('click', closeModal)
modalContent.addEventListener('click', (e) => e.stopPropagation())
 
// Capturing — intercept before children
document.addEventListener('click', logAllClicks, { capture: true })

Back to index


285. Event bubbling vs event delegation

Theory

Event bubbling is a mechanism — after an event fires on a target, it propagates upward through ancestors (target → parent → ... → window).

Event delegation is a pattern — attach one listener on a parent and handle events from children using event.target and bubbling.

They are related but not the same. Delegation uses bubbling. You can bubble without delegating (each child has its own listener that bubbles up).

When each actually matters

SituationBubbling mattersDelegation matters
Nested click handlersstopPropagation() to prevent parent handlerOne listener for 100 list items
Modal overlay click-to-closeClick on content bubbles to overlay—
Dynamically added DOM nodes—Delegation works without re-binding
Memory / performanceMany listeners vs one1000 rows → 1 listener

Pros & Cons

Event delegationIndividual listeners
✅ Fewer listeners, less memory✅ Explicit per-element control
✅ Works for dynamic children✅ Easier to debug which handler fired
❌ Must filter with closest() / matches()❌ Must re-bind on DOM changes
❌ Doesn't work for non-bubbling events*❌ 1000 items = 1000 listeners

*Non-bubbling: focus, blur, scroll — use capture phase or direct binding.

Real-Life Example

javascript
// BUBBLING — event travels up
document.getElementById('card').addEventListener('click', () => {
  console.log('card clicked')
})
document.getElementById('btn').addEventListener('click', (e) => {
  console.log('button clicked')
  e.stopPropagation() // prevents "card clicked"
})
 
// DELEGATION — one listener on parent
const transactionList = document.getElementById('transactions')
 
transactionList.addEventListener('click', (e) => {
  const row = e.target.closest('[data-txn-id]')
  if (!row) return
 
  const action = e.target.closest('[data-action]')?.dataset.action
  const txnId = row.dataset.txnId
 
  if (action === 'view') openTransaction(txnId)
  if (action === 'dispute') openDispute(txnId)
})
 
// New transactions appended later — delegation still works, zero re-binding

Interview answer (concise)

Performance

Bubbling is the propagation mechanism — events travel from target to root. Delegation is a pattern that exploits bubbling: one parent listener handles events from all children via event.target. Delegation matters for dynamic lists and performance. Bubbling matters when you need stopPropagation — like preventing modal content clicks from closing the overlay.

Back to index


286. Explain copy constructor and move constructor

Copy constructor — deep copy

cpp
class String {
  char* data;
  size_t len;
 
public:
  String(const char* s) : len(strlen(s)), data(new char[len + 1]) {
    strcpy(data, s);
  }
 
  // Copy constructor
  String(const String& other) : len(other.len), data(new char[other.len + 1]) {
    strcpy(data, other.data); // deep copy
    std::cout << "Copy ctor\n";
  }
 
  ~String() { delete[] data; }
};

Move constructor — steal resources

cpp
  // Move constructor
  String(String&& other) noexcept
    : data(other.data), len(other.len) {
    other.data = nullptr;
    other.len = 0;
    std::cout << "Move ctor\n";
  }

Rule of Five

If you define any of these, consider defining all:

  1. Destructor
  2. Copy constructor
  3. Copy assignment operator
  4. Move constructor
  5. Move assignment operator
cpp
String& operator=(const String& other) { /* copy assign */ }
String& operator=(String&& other) noexcept { /* move assign */ }

= default and = delete

cpp
class NonCopyable {
  NonCopyable() = default;
  NonCopyable(const NonCopyable&) = delete;
  NonCopyable& operator=(const NonCopyable&) = delete;
};

Back to index


287. Explain event delegation with a practical example

Theory

Event delegation attaches a single listener to a parent element instead of many listeners on each child. Events bubble up — the parent handles them using event.target.

Benefits

  • Fewer listeners → better memory and performance
  • Works for dynamically added children
  • Simpler cleanup

Practical Example

html
<ul id="menu">
  <li data-action="edit">Edit</li>
  <li data-action="delete">Delete</li>
  <li data-action="share">Share</li>
</ul>
javascript
const menu = document.getElementById('menu')
 
// ✅ One listener instead of three
menu.addEventListener('click', (event) => {
  const item = event.target.closest('li[data-action]')
  if (!item || !menu.contains(item)) return
 
  const action = item.dataset.action
 
  switch (action) {
    case 'edit':
      console.log('Edit clicked')
      break
    case 'delete':
      console.log('Delete clicked')
      break
    case 'share':
      console.log('Share clicked')
      break
  }
})
 
// Dynamically added items work automatically
const newItem = document.createElement('li')
newItem.dataset.action = 'archive'
newItem.textContent = 'Archive'
menu.appendChild(newItem)

When NOT to delegate

  • Events that don't bubble (focus, blur, scroll) — use capture phase or direct binding
  • Very deep trees where closest() traversal is costly (rare)

Back to index


288. Explain frontend caching strategies

Theory — Cache layers

mermaid
flowchart LR
  Browser[Browser Cache] --> CDN[CDN Edge]
  CDN --> App[App Cache / Service Worker]
  App --> Memory[In-Memory Cache]
  Memory --> API[API / Origin]

1. HTTP caching

http
Cache-Control: public, max-age=31536000, immutable
 
# HTML — short cache or no cache
Cache-Control: no-cache
 
# API responses — private, short TTL
Cache-Control: private, max-age=60
ETag: "abc123"

Use content hashing in filenames: app.a1b2c3.js → safe immutable caching.

2. CDN caching

  • Cache static assets and SSR pages at edge (CloudFront, Cloudflare, Fastly)
  • Use stale-while-revalidate for semi-dynamic content
  • Cache keys should include locale, device type if responses differ

3. Application-level (React Query)

tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 min — data considered fresh
      gcTime: 30 * 60 * 1000, // 30 min — garbage collect unused
      refetchOnWindowFocus: true,
      retry: 2,
    },
  },
})
 
// Prefetch on hover for instant navigation
function RestaurantLink({ id }) {
  const queryClient = useQueryClient()
 
  return (
    <Link
      to={`/restaurant/${id}`}
      onMouseEnter={() =>
        queryClient.prefetchQuery({
          queryKey: ['restaurant', id],
          queryFn: () => fetchRestaurant(id),
        })
      }>
      View
    </Link>
  )
}

4. Service Worker (PWA)

javascript
// Workbox strategy: stale-while-revalidate for API, cache-first for assets
workbox.routing.registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new workbox.strategies.StaleWhileRevalidate({ cacheName: 'api-cache' }),
)

5. Local storage / IndexedDB

  • Persist user preferences, draft forms, offline cart
  • Not for sensitive tokens (use httpOnly cookies)
  • IndexedDB for larger structured offline data

Cache invalidation strategies

StrategyWhen to use
TTL (time-to-live)Predictable staleness tolerance
Event-driven invalidationWebSocket/push updates order status
Versioned keysDeploy new API version → new cache key
Optimistic updates + rollbackInstant UI, reconcile on error

Back to index


289. Explain map, filter, and reduce with examples

Theory

Three essential array methods for immutable data transformation:

MethodReturnsPurpose
mapNew array (same length)Transform each element
filterNew array (0 to n length)Keep elements matching a condition
reduceSingle value (any type)Accumulate into one result

None mutate the original array.

Pros & Cons

ProsCons
Declarative and readableChaining creates intermediate arrays
Immutable by defaultCan't break early (use for...of)
ChainableSlightly slower than manual loop in hot paths

Real-Life Example

javascript
const menuItems = [
  { id: 1, name: 'Biryani', price: 299, category: 'main', inStock: true },
  { id: 2, name: 'Naan', price: 49, category: 'bread', inStock: true },
  { id: 3, name: 'Lassi', price: 79, category: 'drink', inStock: false },
  { id: 4, name: 'Kebab', price: 199, category: 'main', inStock: true },
]
 
// map — transform for dropdown options
const options = menuItems.map((item) => ({
  label: `${item.name} — ₹${item.price}`,
  value: item.id,
}))
// [{ label: "Biryani — ₹299", value: 1 }, ...]
 
// filter — available main courses under ₹250
const affordableMains = menuItems.filter((item) => item.category === 'main' && item.inStock && item.price <= 250)
// [{ id: 4, name: "Kebab", price: 199, ... }]
 
// reduce — calculate total order value
const cart = [
  { id: 1, quantity: 2 },
  { id: 2, quantity: 4 },
]
 
const orderTotal = cart.reduce((total, cartItem) => {
  const product = menuItems.find((m) => m.id === cartItem.id)
  return total + product.price * cartItem.quantity
}, 0)
// 299*2 + 49*4 = 794
 
// reduce — group by category
const byCategory = menuItems.reduce((groups, item) => {
  const cat = item.category
  if (!groups[cat]) groups[cat] = []
  groups[cat].push(item)
  return groups
}, {})
// { main: [...], bread: [...], drink: [...] }
 
// Chaining all three
const summary = menuItems
  .filter((item) => item.inStock)
  .map((item) => item.name)
  .reduce((text, name, index, arr) => {
    return text + name + (index < arr.length - 1 ? ', ' : '')
  }, 'Available: ')
jsx
// React — primary list rendering pattern
function MenuList({ items }) {
  return (
    <ul>
      {items
        .filter((item) => item.inStock)
        .map((item) => (
          <MenuItem key={item.id} item={item} />
        ))}
    </ul>
  )
}

Back to index


290. Explain move semantics

Theory

Move semantics (C++11) transfer resources from a source object to a destination without copying — the source is left in a valid but unspecified state.

Why needed

cpp
std::vector<int> createLargeVector() {
  std::vector<int> v(1'000'000, 42);
  return v; // move, not copy (RVO/NRVO or move constructor)
}

Without move semantics, returning large objects copies all elements.

Move vs Copy

cpp
std::string a = "hello";
std::string b = a;            // copy — both own their data
std::string c = std::move(a); // move — c steals a's buffer, a is empty
 
std::cout << a; // "" (valid but empty)
std::cout << c; // "hello"

std::move is a cast

cpp
// std::move doesn't move anything — it casts to rvalue reference
// enabling move constructor/assignment to be called
void process(std::string&& s); // accepts rvalue
 
std::string name = "Amit";
process(std::move(name)); // explicitly treat as rvalue

Move constructor example

cpp
class Buffer {
  int* data;
  size_t size;
 
public:
  // Move constructor
  Buffer(Buffer&& other) noexcept
    : data(other.data), size(other.size) {
    other.data = nullptr;
    other.size = 0;
  }
 
  // Move assignment
  Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
      delete[] data;
      data = other.data;
      size = other.size;
      other.data = nullptr;
      other.size = 0;
    }
    return *this;
  }
};

Back to index


291. Find Missing Numbers in Array

Theory

Given [1, 2, 3, 5, 7], find missing numbers in the sequence → [4, 6].

Approaches: loop with expected counter, Set comparison, or math range filter.

Interview Answer

I iterate from min to max, check which numbers aren't in the array using a Set for O(1) lookup, and collect missing ones.

Implementation

javascript
function findMissingNumbers(arr) {
  if (!arr.length) return []
 
  const set = new Set(arr)
  const min = Math.min(...arr)
  const max = Math.max(...arr)
  const missing = []
 
  for (let i = min; i <= max; i++) {
    if (!set.has(i)) missing.push(i)
  }
 
  return missing
}
 
console.log(findMissingNumbers([1, 2, 3, 5, 7])) // [4, 6]
console.log(findMissingNumbers([1, 3, 6, 10])) // [2, 4, 5, 7, 8, 9]
javascript
// Alternative — without Set (the company may ask no built-ins)
function findMissingNumbersLoop(arr) {
  const missing = []
  for (let i = 0; i < arr.length - 1; i++) {
    const diff = arr[i + 1] - arr[i]
    for (let j = 1; j < diff; j++) {
      missing.push(arr[i] + j)
    }
  }
  return missing
}
 
console.log(findMissingNumbersLoop([1, 2, 3, 5, 7])) // [4, 6]

Back to index


292. Find the First Non-Repeating Character in a string

Theory

Count character frequencies, then find the first character with count 1.

Approach 1 — Two passes with hash map

javascript
function firstNonRepeating(s) {
  const freq = new Map()
 
  for (const char of s) {
    freq.set(char, (freq.get(char) || 0) + 1)
  }
 
  for (const char of s) {
    if (freq.get(char) === 1) return char
  }
 
  return null // or -1 if no such character
}
 
console.log(firstNonRepeating('leetcode')) // "l"
console.log(firstNonRepeating('aabbcc')) // null
console.log(firstNonRepeating('swiss')) // "w"

Complexity

TimeSpace
Hash mapO(n)O(k) where k = unique chars
Array (ASCII)O(n)O(1) — fixed 26 or 128 size

ASCII-optimized version

javascript
function firstNonRepeatingASCII(s) {
  const freq = new Array(26).fill(0)
 
  for (const char of s) {
    freq[char.charCodeAt(0) - 97]++
  }
 
  for (const char of s) {
    if (freq[char.charCodeAt(0) - 97] === 1) return char
  }
 
  return null
}

C++ version

cpp
#include <string>
#include <unordered_map>
 
char firstNonRepeating(const std::string& s) {
  std::unordered_map<char, int> freq;
  for (char c : s) freq[c]++;
 
  for (char c : s) {
    if (freq[c] == 1) return c;
  }
  return '\0';
}

Back to index


293. First Repeating Character

Theory

Find the first character that appears more than once when scanning left to right.

"success" → "c" (index 2 — first char with count > 1 on second occurrence)

Note: This is different from first non-repeating character ("success" → "u").

Algorithm

  1. Track seen characters in a Set
  2. Scan left to right
  3. If char already in Set → return it (first repeat)
  4. Else add to Set

Interview Answer

I scan left to right with a Set — the first character I see that's already in the Set is the first repeating character.

Implementation

javascript
function firstRepeatingChar(str) {
  const seen = new Set()
 
  for (const char of str) {
    if (seen.has(char)) return char
    seen.add(char)
  }
 
  return null // no repeat
}
 
console.log(firstRepeatingChar('success')) // "c"
console.log(firstRepeatingChar('abcdef')) // null
console.log(firstRepeatingChar('abccba')) // "c" (first repeat at index 2)
console.log(firstRepeatingChar('programming')) // "r"
javascript
// Alternative — frequency map + second pass for first index
function firstRepeatingCharMap(str) {
  const freq = new Map()
 
  for (const char of str) {
    freq.set(char, (freq.get(char) || 0) + 1)
  }
 
  for (const char of str) {
    if (freq.get(char) > 1) return char
  }
 
  return null
}

Walk-through: "success"

plaintext
s → seen={s}
u → seen={s,u}
c → seen={s,u,c}
c → already in seen → return "c" ✅

Back to index


294. Flatten a Nested Array

Theory

Convert [[1, [2, 3]], 4] → [1, 2, 3, 4].

Approach 1 — Recursive

javascript
function flatten(arr) {
  const result = []
 
  for (const item of arr) {
    if (Array.isArray(item)) {
      result.push(...flatten(item))
    } else {
      result.push(item)
    }
  }
 
  return result
}
 
console.log(flatten([1, [2, [3, 4]], 5])) // [1, 2, 3, 4, 5]

Approach 2 — Iterative with stack

javascript
function flattenIterative(arr) {
  const stack = [...arr]
  const result = []
 
  while (stack.length) {
    const next = stack.pop()
    if (Array.isArray(next)) {
      stack.push(...next)
    } else {
      result.push(next)
    }
  }
 
  return result.reverse() // stack reverses order
}

Approach 3 — Built-in (ES2019)

javascript
const flat = [1, [2, [3, 4]], 5].flat(Infinity)

Flatten with depth control

javascript
function flattenDepth(arr, depth = 1) {
  if (depth <= 0) return arr.slice()
 
  return arr.reduce((acc, item) => {
    if (Array.isArray(item)) {
      acc.push(...flattenDepth(item, depth - 1))
    } else {
      acc.push(item)
    }
    return acc
  }, [])
}
 
console.log(flattenDepth([1, [2, [3]]], 1)) // [1, 2, [3]]
console.log(flattenDepth([1, [2, [3]]], 2)) // [1, 2, 3]

C++ version (recursive)

cpp
#include <vector>
#include <variant>
 
// For vector of variants (int or nested vector) — simplified:
std::vector<int> flatten(const std::vector<std::vector<int>>& nested) {
  std::vector<int> result;
  for (const auto& inner : nested) {
    result.insert(result.end(), inner.begin(), inner.end());
  }
  return result;
}

React

TopicOne-liner
Re-render triggersState, context, parent update
FiberInterruptible work units; render + commit phases
RSC vs ClientServer = no client JS; Client = "use client" + hooks
Thousands of rowsVirtualization + memo + pagination
LifecyclesetState → render → commit DOM → layout effects → paint → useEffect
Avoid ContextFast-changing / granular state
useMemo / useCallback / memoValue / function / component memoization
Race conditionsAbortController or request ID
Code splittingReact.lazy + Suspense per route
State update internalsEnqueue → batch → reconcile → commit
Component libraryTokens, primitives, a11y, Storybook, semver

JavaScript

TopicOne-liner
Promise.allAll resolve or first reject; preserve order
WeakMap/WeakSetWeak refs; keys are objects only
Event delegationOne parent listener + event.target
Shallow vs deepNested refs shared vs structuredClone
GCMark-and-sweep from roots; generational in V8
ClosureFunction + lexical environment
Async iterationSymbol.asyncIterator + for await...of
async/awaitPromise sugar; pauses on microtask queue

C++

TopicOne-liner
Smart pointersRAII auto memory management
unique vs sharedExclusive vs ref-counted shared ownership
Move semanticsTransfer resources; std::move casts to rvalue
RAIIAcquire in ctor, release in dtor
vector vs listContiguous/cache-friendly vs linked/flexible
ctor/dtor orderBase → members → body; reverse for destruction
Copy vs move ctorDeep copy vs steal resources
vtablevptr → function pointer array for polymorphism

DSA

ProblemApproach
First non-repeating charFrequency map + second pass
Two SumHash map complement lookup O(n)
DebounceReset timer on each call
ThrottleFlag + cooldown window
Flatten arrayRecursion or stack; flat(Infinity)

Back to index


295. Flatten Array without Array.flat()

Theory

Convert nested arrays into a single flat array by recursively expanding any array element until all values are non-array primitives.

Given input: [1,2,3,[4,5,6,[7,8,[10,11]]],9]
Expected output: [1,2,3,4,5,6,7,8,10,11,9]

Pros & Cons

RecursiveIterative (stack)
✅ Clean, readable✅ No stack overflow on deep nesting
❌ Stack limit on very deep arraysSlightly more code

Interview Answer

I flatten by reducing — if element is array, recursively flatten and concat; otherwise push the value. Iterative version uses a stack to avoid recursion limits.

Implementations

javascript
// Approach 1 — Recursive reduce
function flatten(arr) {
  return arr.reduce((acc, val) => {
    return acc.concat(Array.isArray(val) ? flatten(val) : val)
  }, [])
}
 
// Approach 2 — Iterative with stack
function flattenIterative(arr) {
  const stack = [...arr]
  const result = []
 
  while (stack.length) {
    const next = stack.pop()
    if (Array.isArray(next)) {
      stack.push(...next)
    } else {
      result.push(next)
    }
  }
 
  return result.reverse()
}
 
// Approach 3 — toString trick (ONLY for numbers — not general)
// [1,[2,3]].toString() → "1,2,3" — avoid in interviews unless asked

Test

javascript
const input = [1, 2, 3, [4, 5, 6, [7, 8, [10, 11]]], 9]
console.log(flatten(input))
// [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 9]

Back to index


296. Flatten Array without Built-in Methods

Theory

Client round asked to flatten without built-in array methods (no flat, reduce, concat). Use manual loops and recursion.

Input: [1, 2, [3, [4, 5]], 6] → [1, 2, 3, 4, 5, 6]

Interview Answer

I recursively loop — if element is array, flatten it with a helper; otherwise push to result. Pure for-loop, no reduce or concat.

Implementation

javascript
function flatten(arr) {
  const result = []
 
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      const nested = flatten(arr[i])
      for (let j = 0; j < nested.length; j++) {
        result[result.length] = nested[j]
      }
    } else {
      result[result.length] = arr[i]
    }
  }
 
  return result
}
 
console.log(flatten([1, 2, [3, [4, 5]], 6])) // [1, 2, 3, 4, 5, 6]
console.log(flatten([1, 2, 3, [4, 5, 6, [7, 8, [10, 11]]], 9]))
// [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 9]
javascript
// Iterative — stack, no recursion
function flattenIterative(arr) {
  const stack = []
  for (let i = arr.length - 1; i >= 0; i--) stack[stack.length] = arr[i]
 
  const result = []
 
  while (stack.length > 0) {
    const item = stack.pop()
    if (Array.isArray(item)) {
      for (let i = item.length - 1; i >= 0; i--) stack[stack.length] = item[i]
    } else {
      result[result.length] = item
    }
  }
 
  return result
}
RoundTopicOne-liner
1ClosureFunction + remembered outer scope
1Event loopSync → micro → macro
1Debounce/throttlePause vs rate-limit
1a11ySemantic HTML, ARIA, keyboard
1Flex vs Grid1D vs 2D layout
1Missing numbersSet + loop min to max
1CSR vs SSRBrowser vs server render
1call/apply/bindInvoke now vs bind later
1Star rating10 buttons, hover, ARIA radiogroup
1Deep/shallowstructuredClone vs spread
1Reduxdispatch → reducer → store
1ArchitectureFeature folders, layers
2ObjectsLiterals, class, Map, factory
2const vs letconst default, let to reassign
2Spread/restExpand vs collect
2Specificityinline > id > class > element
2none vs hiddenRemove layout vs hide in place
ClientHooks in ifNever — same order required
ClientuseMemo vs effectuseMemo in render, effect after paint
ClientRender vs commitDiff vs DOM update
ClientBatchingMultiple setState → one render
ClientFlatten no built-insfor-loop + recursion

the company tests breadth across 3 rounds — JS, CSS, live coding, and React internals. The client round especially probes whether you understand how React works under the hood.

Back to index


297. Flatten Nested Array

Theory

Convert [1, [2, [3, 4]], 5] → [1, 2, 3, 4, 5].

Approaches: recursive, iterative with stack, built-in flat(Infinity).

Interview Answer

I flatten recursively by reducing each element — if array, flatten it; otherwise keep it. In production I'd use flat(Infinity) or specify depth for partial flatten.

Real Example

javascript
// Built-in
;[1, [2, [3, 4]], 5].flat(Infinity) // [1, 2, 3, 4, 5]
;[1, [2, [3, 4]], 5].flat(1) // [1, 2, [3, 4], 5] — one level
 
// Recursive — interview implementation
function flatten(arr, depth = Infinity) {
  if (depth <= 0) return arr.slice()
  return arr.reduce((acc, val) => {
    if (Array.isArray(val)) return acc.concat(flatten(val, depth - 1))
    return acc.concat(val)
  }, [])
}
 
// Iterative — stack (no recursion limit issues)
function flattenIterative(arr) {
  const stack = [...arr]
  const result = []
  while (stack.length) {
    const next = stack.pop()
    if (Array.isArray(next)) stack.push(...next)
    else result.push(next)
  }
  return result.reverse()
}
 
console.log(flatten([1, [2, [3, 4]], 5])) // [1, 2, 3, 4, 5]
javascript
// Real-life: flatten nested API categories for search
const categories = ['All', ['Food', ['North Indian', ['Biryani']]], 'Drinks']
const searchable = flatten(categories)
// ["All", "Food", "North Indian", "Biryani", "Drinks"]

Back to index


298. Focus on accessibility

Theory

Accessibility (a11y) ensures your app is usable by everyone — screen reader users, keyboard-only users, people with motor impairments, and users on slow connections. It's also a legal requirement in many countries (ADA, WCAG 2.1).

Key practices: semantic HTML, ARIA attributes where needed, keyboard navigation, focus management, color contrast, and alt text.

Pros & Cons

Accessible appsIgnoring a11y
✅ Larger user base❌ Legal risk
✅ Better SEO (semantic HTML)❌ Excludes disabled users
✅ Better keyboard UX for power users❌ Fails automated audits
✅ Required by enterprise clients—

Real-Life Example

tsx
// ❌ Inaccessible
<div onClick={handleSubmit}>Submit</div>
<input placeholder="Search" />
<div className="modal">...</div>
 
// ✅ Accessible
<button type="submit" onClick={handleSubmit}>Submit</button>
 
<label htmlFor="search-input" className="sr-only">Search restaurants</label>
<input
  id="search-input"
  type="search"
  placeholder="Search restaurants"
  aria-describedby="search-hint"
/>
<span id="search-hint" className="sr-only">Type to search by name or cuisine</span>
 
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  ref={modalRef}
  tabIndex={-1}
>
  <h2 id="modal-title">Confirm Order</h2>
  ...
</div>

Accessibility checklist:

PracticeExample
Semantic HTML<button>, <nav>, <main>, <article>
Labels<label htmlFor="email"> or aria-label
KeyboardAll actions reachable via Tab + Enter/Space
Focus trapModal keeps focus inside until closed
Color contrast4.5:1 ratio for text (WCAG AA)
Alt text<img alt="Chicken Biryani from Spice Garden" />
Skip link"Skip to main content" for screen readers
Live regionsaria-live="polite" for dynamic status updates
bash
npm install -D @axe-core/react eslint-plugin-jsx-a11y
npx playwright test --grep a11y

Back to index


299. Form Handling in React

Theory

React forms typically use controlled components with a single state object or libraries like React Hook Form / Formik for validation and performance.

Pattern: collect values on change, validate on blur/submit, prevent default on submit.

Interview Answer

I use controlled inputs with state or React Hook Form for performance. On submit I preventDefault, validate, then call the API — never rely on native form reload.

Real Example

jsx
function LoginForm({ onSubmit }) {
  const [form, setForm] = useState({ email: '', password: '' })
  const [errors, setErrors] = useState({})
 
  const handleChange = (e) => {
    setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }))
  }
 
  const handleSubmit = (e) => {
    e.preventDefault()
    const errs = validate(form)
    if (Object.keys(errs).length) return setErrors(errs)
    onSubmit(form)
  }
 
  return (
    <form onSubmit={handleSubmit} noValidate>
      <input name="email" value={form.email} onChange={handleChange} />
      {errors.email && <span>{errors.email}</span>}
      <input name="password" type="password" value={form.password} onChange={handleChange} />
      <button type="submit">Login</button>
    </form>
  )
}

Back to index


300. Given an array, how do you find the second largest element?

Theory

Find the second largest unique element in an array. Common approaches:

ApproachTimeSpaceNotes
Sort + scanO(n log n)O(1) or O(n)Simple but overkill
Two variables (one pass)O(n)O(1)Optimal — interview favorite
Set + sortO(n log n)O(n)Handles duplicates easily

Watch for edge cases: duplicates, array length < 2, all same elements.

Pros & Cons

One-pass (O(n))Sort approach
Optimal time complexityEasier to write under pressure
O(1) spaceHandles duplicates naturally with Set
Must handle edge cases carefullySlower for large arrays

Real-Life Example

javascript
// ✅ Optimal — single pass, O(n) time, O(1) space
function findSecondLargest(arr) {
  if (arr.length < 2) return null
 
  let largest = -Infinity
  let secondLargest = -Infinity
 
  for (const num of arr) {
    if (num > largest) {
      secondLargest = largest
      largest = num
    } else if (num > secondLargest && num < largest) {
      secondLargest = num
    }
    // if num === largest, skip (duplicate)
  }
 
  return secondLargest === -Infinity ? null : secondLargest
}
 
console.log(findSecondLargest([10, 5, 20, 8, 20])) // 10
console.log(findSecondLargest([5, 5, 5])) // null (no second largest)
console.log(findSecondLargest([42, 17])) // 17
javascript
// Alternative — sort with dedup (simpler, good for interviews if you explain trade-off)
function findSecondLargestSort(arr) {
  const unique = [...new Set(arr)]
  if (unique.length < 2) return null
  unique.sort((a, b) => b - a)
  return unique[1]
}
javascript
// Real-life use: Find runner-up score in a leaderboard
const scores = [95, 87, 95, 72, 88, 87]
const runnerUp = findSecondLargest(scores) // 88

Walk-through

plaintext
[10, 5, 20, 8, 20]
 num=10: largest=10, second=-∞
 num=5:  largest=10, second=5
 num=20: largest=20, second=10
 num=8:  largest=20, second=10 (8 < 10)
 num=20: skip (duplicate of largest)
 → return 10

Back to index


301. Handling Complex Codebases

What they're assessing

Navigation, refactoring courage, documentation, and pragmatism.

Strategies to describe

StrategyExample
Map before changeDependency graph, feature flags, strangler fig migration
Thin slicesRefactor one route/module per sprint
Tests as safety netCharacterization tests before big moves
ConventionsADRs, folder structure, CODEOWNERS
ToolingTypeScript strict mode, knip for dead code

Sample answer skeleton

Note

"On a 180K-line React codebase with mixed JS/TS, I started with a dependency-cruiser audit to find circular imports. We adopted a feature-folder structure and barrel export limits. I migrated the highest-traffic checkout flow to TypeScript first with RTL tests, behind a feature flag. We didn't freeze the product — every sprint delivered user value while tech debt ratio dropped in SonarQube from 35% to 18% over two quarters."

Interview Answer

I understand complex codebases by mapping dependencies and hot paths first, then improve through small, tested slices — feature flags, typing, and clear ownership — not big-bang rewrites.

Back to index


302. How do you authenticate your React application?

Theory

Round 2 revisits auth with more depth. Cover the full flow: login → token storage → protected routes → refresh → logout → role-based access.

Complete auth flow diagram

plaintext
User submits login
       ↓
POST /api/auth/login
       ↓
Server validates → sets httpOnly cookie (access + refresh)
       ↓
Frontend stores user object in Context (NOT the token)
       ↓
ProtectedRoute checks user → render or redirect
       ↓
API calls include credentials: "include"
       ↓
401 → try /api/auth/refresh → retry or logout
       ↓
Logout → POST /api/auth/logout → clear cookie → clear Context

Interview Answer

Auth flow: login API sets httpOnly cookies, Context holds user profile for UI, ProtectedRoute guards routes, axios interceptor handles 401 with silent refresh, RBAC checks roles for admin features. Server validates every request.

Real Example — Role-based access

tsx
function RequireRole({ role, children }: { role: string; children: React.ReactNode }) {
  const { user } = useAuth()
  if (!user?.roles.includes(role)) return <Navigate to="/unauthorized" />
  return children
}
 
;<Route
  path="/admin"
  element={
    <ProtectedRoute>
      <RequireRole role="admin">
        <AdminPanel />
      </RequireRole>
    </ProtectedRoute>
  }
/>

Back to index


303. How do you ensure the performance of a React application?

Theory

React performance optimization spans rendering, bundling, network, and measurement. Optimize based on data — profile first, then fix the biggest bottleneck.

Key strategies

CategoryTechniques
RenderingReact.memo, useMemo, useCallback, virtualization
BundlingCode splitting, React.lazy, tree shaking
NetworkTanStack Query caching, prefetch, CDN, compression
ImagesWebP/AVIF, lazy load, responsive srcset
StateColocate state, avoid unnecessary context re-renders
MeasurementReact Profiler, Lighthouse, Web Vitals RUM

Pros & Cons

Profile-driven optimizationPremature optimization
✅ Fixes real bottlenecks❌ Wasted dev time
✅ Measurable improvements❌ Added complexity everywhere
✅ Better user experience❌ useMemo on cheap operations

Real-Life Example

tsx
// 1. Code splitting — route level
const Analytics = lazy(() => import('./pages/Analytics'))
 
// 2. Virtualization — 10,000 row table
import { useVirtualizer } from '@tanstack/react-virtual'
 
// 3. Memoization — after profiling shows slow child re-renders
const DataRow = React.memo(function DataRow({ row, onEdit }) {
  return (
    <tr>
      <td>{row.name}</td>
      <td>
        <button onClick={() => onEdit(row.id)}>Edit</button>
      </td>
    </tr>
  )
})
 
// 4. Server state caching
const { data } = useQuery({
  queryKey: ['employees', deptId],
  queryFn: () => fetchEmployees(deptId),
  staleTime: 5 * 60 * 1000,
})
 
// 5. Defer non-urgent updates
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
 
// 6. Measure
import { Profiler } from 'react'
;<Profiler
  id="EmployeeList"
  onRender={(id, phase, duration) => {
    if (duration > 16) console.warn(`Slow: ${id} ${duration}ms`)
  }}>
  <EmployeeList />
</Profiler>

Performance budget example:

MetricTarget
Initial JS bundle< 200 KB gzipped
LCP< 2.5s
Component render< 16ms (60fps)

Back to index


304. How Do You Flatten an Array?

Theory

Flattening converts a nested array into a single-level array.

javascript
[1, [2, [3, 4]], 5]  →  [1, 2, 3, 4, 5]

Interviewers test: recursion, iteration, built-in knowledge, edge cases (empty arrays, non-array items, depth limit).

Interview Answer

For shallow flatten I use flat(1) or concat with spread. For deep nested arrays I use flat(Infinity) in modern JS, or recursive reduce/for-loop when they ask for no built-ins.

Built-in Methods

javascript
const nested = [1, [2, [3, 4]], 5]
 
// Shallow — one level
nested.flat() // [1, 2, [3, 4], 5]
nested.flat(1) // same
 
// Deep — all levels
nested.flat(Infinity) // [1, 2, 3, 4, 5]
 
// Alternative
;[].concat(...nested) // [1, [2, [3, 4]], 5] — shallow only

Recursive — No Built-ins

javascript
function flatten(arr) {
  const result = []
 
  for (const item of arr) {
    if (Array.isArray(item)) {
      result.push(...flatten(item))
    } else {
      result.push(item)
    }
  }
 
  return result
}
 
flatten([1, [2, [3, 4]], 5]) // [1, 2, 3, 4, 5]

reduce

javascript
function flatten(arr) {
  return arr.reduce((acc, item) => acc.concat(Array.isArray(item) ? flatten(item) : item), [])
}

Iterative — Stack (no recursion)

javascript
function flattenIterative(arr) {
  const stack = [...arr].reverse()
  const result = []
 
  while (stack.length) {
    const item = stack.pop()
    if (Array.isArray(item)) {
      stack.push(...item.reverse())
    } else {
      result.push(item)
    }
  }
 
  return result
}

With Depth Limit

javascript
function flattenDepth(arr, depth = 1) {
  if (depth <= 0) return arr.slice()
 
  return arr.reduce((acc, item) => {
    if (Array.isArray(item)) {
      acc.push(...flattenDepth(item, depth - 1))
    } else {
      acc.push(item)
    }
    return acc
  }, [])
}
 
flattenDepth([1, [2, [3]]], 1) // [1, 2, [3]]
flattenDepth([1, [2, [3]]], 2) // [1, 2, 3]

Senior Touch — Connect to Real Work

Note

"In production I'd use flat(Infinity) or a utility from lodash if we're looking for battle-tested edge cases. The recursive version matters when transforming nested API responses — like flattening nested categories into a select dropdown options list."

javascript
// Real-world: flatten nested categories for a filter dropdown
const categories = [
  {
    id: 1,
    name: 'Electronics',
    children: [
      { id: 2, name: 'Phones', children: [] },
      { id: 3, name: 'Laptops', children: [] },
    ],
  },
]
 
function flattenCategories(nodes, result = []) {
  for (const node of nodes) {
    result.push({ id: node.id, name: node.name })
    if (node.children?.length) flattenCategories(node.children, result)
  }
  return result
}

Back to index


305. How do you implement authentication in React?

Theory

React authentication involves: login flow, token/session storage, protected routes, token refresh, and logout. Security-critical logic must be enforced on the backend — frontend guards are for UX only.

Common approaches:

  • JWT in httpOnly cookies (recommended)
  • OAuth 2.0 / OIDC (Google, GitHub SSO)
  • Session-based auth with cookie

Pros & Cons

httpOnly cookieslocalStorage JWT
✅ XSS-safe — JS can't read token❌ Any XSS script steals token
✅ Auto-sent with requests✅ Simple to implement
❌ CSRF risk (use SameSite)❌ Not recommended for production

Interview Answer

I implement auth with a login API that sets httpOnly cookies, wrap the app in an AuthProvider using Context, protect routes with a guard component, and refresh tokens silently before expiry. Backend always validates — frontend guards are UX only.

Real Example

tsx
// auth/AuthContext.tsx
type User = { id: string; name: string; role: string }
 
const AuthContext = createContext<{
  user: User | null
  isLoading: boolean
  login: (email: string, password: string) => Promise<void>
  logout: () => Promise<void>
} | null>(null)
 
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(true)
 
  // Check session on mount
  useEffect(() => {
    fetch('/api/auth/me', { credentials: 'include' })
      .then((r) => (r.ok ? r.json() : null))
      .then(setUser)
      .finally(() => setIsLoading(false))
  }, [])
 
  const login = async (email: string, password: string) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    })
    if (!res.ok) throw new Error('Invalid credentials')
    const userData = await res.json()
    setUser(userData)
  }
 
  const logout = async () => {
    await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
    setUser(null)
  }
 
  return <AuthContext.Provider value={{ user, isLoading, login, logout }}>{children}</AuthContext.Provider>
}
 
export const useAuth = () => {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be inside AuthProvider')
  return ctx
}
tsx
// Protected route
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth()
  const location = useLocation()
 
  if (isLoading) return <FullPageSpinner />
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />
  return children
}
 
// App routing
;<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>
tsx
// Axios interceptor — attach credentials + handle 401
api.interceptors.response.use(
  (res) => res,
  async (error) => {
    if (error.response?.status === 401) {
      // Try refresh token
      const refreshed = await fetch('/api/auth/refresh', { credentials: 'include' })
      if (refreshed.ok) return api.request(error.config)
      window.location.href = '/login'
    }
    return Promise.reject(error)
  },
)

Back to index


306. How do you manage state in large React applications?

Theory — State categories

mermaid
flowchart TD
  S[Application State] --> Server[Server State]
  S --> Client[Client State]
  S --> URL[URL State]
  S --> Form[Form State]
 
  Server --> RQ[TanStack Query / SWR]
  Client --> Local[useState / useReducer]
  Client --> Global[Zustand / Redux Toolkit]
  URL --> Router[URL search params]
  Form --> RHF[React Hook Form]

Decision framework

State typeToolExample
Remote/server dataTanStack QueryRestaurant list, user profile
Global UI stateZustand / ContextTheme, sidebar open, cart
Complex cross-featureRedux ToolkitMulti-step checkout, permissions
URL-shareable stateRouter search paramsFilters, pagination, selected tab
Form stateReact Hook FormCheckout form, settings
Ephemeral localuseStateModal open, hover state

Practical Example — Zustand for cart (a food-delivery platform-relevant)

tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
 
type CartItem = { id: string; name: string; qty: number }
 
type CartStore = {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (id: string) => void
  clear: () => void
}
 
export const useCartStore = create<CartStore>()(
  persist(
    (set) => ({
      items: [],
      addItem: (item) =>
        set((state) => {
          const existing = state.items.find((i) => i.id === item.id)
          if (existing) {
            return {
              items: state.items.map((i) => (i.id === item.id ? { ...i, qty: i.qty + 1 } : i)),
            }
          }
          return { items: [...state.items, item] }
        }),
      removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
      clear: () => set({ items: [] }),
    }),
    { name: 'cart-storage' },
  ),
)

Anti-patterns to avoid

  • Storing server data in Redux when React Query handles it better
  • Prop drilling through 5+ levels instead of composition or context
  • One giant global store with no domain separation
  • Duplicating state across URL, store, and component

Interview answer (concise)

Note

I classify state first: server state goes to TanStack Query, URL state to the router, form state to React Hook Form, and only truly global client state to Zustand or Redux. This avoids over-centralizing and keeps each layer doing one job well.

Back to index


307. How does addEventListener work?

Theory

element.addEventListener(type, handler, options) registers a function to run when an event fires on that element.

Options:

  • capture: true — run in capturing phase (top → target)
  • once: true — remove after first fire
  • passive: true — won't call preventDefault (scroll performance)

Events flow: capturing → target → bubbling. Default listeners use bubbling.

Remove with removeEventListener(type, handler) — must pass the same function reference.

Pros & Cons

addEventListenerinline onclick
✅ Multiple handlers per event❌ One handler only
✅ Capture/bubble control❌ No capture option
✅ Remove with removeEventListener❌ Harder to clean up

Interview Answer

addEventListener registers an event handler on a DOM element. It supports capture and bubble phases, can be removed with removeEventListener, and I always clean up in useEffect return for React.

Real Example

javascript
const button = document.getElementById('submit')
 
// Basic
button.addEventListener('click', handleSubmit)
 
// With options
button.addEventListener('click', handleSubmit, { once: true })
document.addEventListener('keydown', handleEsc, { capture: true })
 
// Remove — same reference required
button.removeEventListener('click', handleSubmit)
tsx
// React — useEffect cleanup pattern
function Modal({ isOpen, onClose }) {
  useEffect(() => {
    if (!isOpen) return
 
    const handleEsc = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose()
    }
 
    document.addEventListener('keydown', handleEsc)
    return () => document.removeEventListener('keydown', handleEsc)
  }, [isOpen, onClose])
}
javascript
// Event delegation with addEventListener
document.getElementById('menu').addEventListener('click', (e) => {
  const item = e.target.closest('[data-action]')
  if (item) handleAction(item.dataset.action)
})

Back to index


308. How does error handling work in React?

Answer

Error Boundaries in React is done using Error Boundaries – special components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors.

react-error-boundary is a lightweight library that allows you to use error boundaries easily with functional components in React. It simplifies error handling and provides a FallbackComponent, reset mechanisms, and more.

Back to index


309. How does JavaScript code execution work?

Theory

JavaScript execution follows a structured pipeline:

plaintext
1. Parsing
   Source code → Tokens → AST (Abstract Syntax Tree)
 
2. Compilation (V8)
   AST → Bytecode (Ignition interpreter) → Optimized machine code (TurboFan)
 
3. Execution
   Global Execution Context created
   → Memory heap (variables, objects)
   → Call stack (function calls)
 
4. Runtime
   Event loop coordinates sync code, microtasks, macrotasks

Execution Context contains:

  • Variable Environment — var, function declarations, let/const
  • Lexical Environment — scope chain for lookups
  • this binding

Phases of execution context:

  1. Creation phase — hoist variables, create scope chain, set this
  2. Execution phase — run code line by line

Pros & Cons

Understanding execution modelIgnoring it
✅ Predict async behavior❌ Surprised by hoisting bugs
✅ Debug closure/TDZ issues❌ Block UI with sync code
✅ Write performant hot paths❌ Memory leaks from closures

Real-Life Example

javascript
// Execution order walkthrough
const API_URL = 'https://api.example.com' // global context
 
function initApp() {
  const userId = getUserId() // execution context for initApp
  fetchOrders(userId)
}
 
function fetchOrders(userId) {
  const endpoint = `/orders/${userId}` // new execution context
  return fetch(`${API_URL}${endpoint}`)
    .then((res) => res.json()) // callback → microtask queue
    .then(renderOrders)
}
 
initApp()
// 1. Global context created
// 2. initApp() pushed to call stack
// 3. fetchOrders() pushed to call stack
// 4. fetch() handed to Web API, fetchOrders pops off stack
// 5. initApp pops off stack
// 6. fetch resolves → .then callback → microtask queue
// 7. renderOrders runs
javascript
// Call stack overflow
function recurse() {
  recurse() // RangeError: Maximum call stack size exceeded
}
 
// Event loop — non-blocking
console.log('1')
setTimeout(() => console.log('3'), 0)
Promise.resolve().then(() => console.log('2'))
console.log('1b')
// 1 → 1b → 2 → 3

Back to index


310. How does React work internally?

Theory

React's internal pipeline has four major stages when state changes:

plaintext
State Change
    ↓
Schedule Update (priority lane assigned)
    ↓
Render Phase (interruptible)
  → Call component functions
  → Run hooks (linked list on Fiber node)
  → Build new Virtual DOM / Fiber tree
  → Diff against previous tree
    ↓
Commit Phase (synchronous)
  → Apply DOM mutations
  → Run useLayoutEffect
    ↓
Browser Paint
    ↓
Passive Effects (useEffect runs after paint)

Fiber is the core data structure — each component instance is a Fiber node with links to parent, child, and sibling. This linked-list tree enables React to pause, resume, and prioritize work.

Pros & Cons of React's internal model

ProsCons
Interruptible rendering keeps UI responsiveMental model is complex for beginners
Double buffering (current ↔ work-in-progress tree)Debugging across async boundaries is hard
Hooks stored as linked list — predictable orderRules of Hooks exist because of this design

Real-Life Example

jsx
function SearchPage() {
  const [query, setQuery] = useState('') // Hook #1 on Fiber
  const [results, setResults] = useState([]) // Hook #2 on Fiber
 
  useEffect(() => {
    // Scheduled AFTER browser paint
    fetch(`/api/search?q=${query}`)
      .then((r) => r.json())
      .then(setResults)
  }, [query])
 
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ResultList items={results} />
    </div>
  )
}
 
// What happens when user types "p":
// 1. onChange fires → setQuery("p") enqueued on Fiber
// 2. Render phase: SearchPage re-runs, hooks return new query
// 3. ResultList may or may not re-render (depends on memo)
// 4. Commit: input value updated in DOM
// 5. Browser paints
// 6. useEffect runs because query changed → API call fires

Key internals to mention

ConceptPurpose
Fiber nodeUnit of work; holds state, props, hooks
ReconciliationDiff algorithm — O(n) heuristic
Update queueCircular linked list of pending state updates
LanesPriority system for concurrent features
Object.isComparison for bailout (same value → skip re-render)

Back to index


311. How does the JavaScript Engine work?

Theory

A JavaScript engine (V8 in Chrome/Node, SpiderMonkey in Firefox, JavaScriptCore in Safari) compiles and executes JS code. Modern engines use a multi-tier compilation pipeline:

  1. Parser — converts source code into an Abstract Syntax Tree (AST)
  2. Ignition interpreter — quickly generates bytecode and starts execution
  3. Sparkplug (baseline compiler) — compiles hot functions to faster baseline machine code
  4. Maglev / TurboFan (optimizing compiler) — recompiles very hot functions with type specialization
  5. Garbage Collector — reclaims unreachable memory (generational mark-and-sweep)

Pros & Cons of JIT compilation

ProsCons
Fast startup (interpret first)Deoptimization penalties if types change
Hot code runs near-native speedMemory overhead for compiled code caches
Adaptive — optimizes what mattersHard to predict performance without profiling

Real-Life Example

javascript
// Engine starts in interpreter (fast startup)
function add(a, b) {
  return a + b
}
 
// Called 10,000 times with numbers → TurboFan optimizes for numbers
for (let i = 0; i < 10000; i++) add(i, i)
 
// Called once with string → DEOPTIMIZATION (bailout to slower path)
add('hello', 'world') // still works, but function may re-optimize

Takeaway for interviews: Write consistent types in hot loops. Avoid mixing types in functions called millions of times.

Back to index


312. How does virtual table (vtable) work?

Theory

Enables runtime polymorphism — call the correct overridden function through a base pointer.

Mechanism

plaintext
Each class with virtual functions has a vtable (array of function pointers)
Each object has a vptr (hidden pointer to its class's vtable)
 
class Animal {
  virtual void speak() { cout << "..."; }
};
 
class Dog : public Animal {
  void speak() override { cout << "Woof"; }
};
 
Animal* a = new Dog();
a->speak(); // "Woof" — resolved via vtable at runtime

Memory layout (simplified)

plaintext
Dog object:
┌──────────┐
│  vptr    │ ──→ Dog vtable: [ speak → Dog::speak, dtor → Dog::~Dog ]
├──────────┤
│  data... │
└──────────┘

Cost of virtual functions

  • Extra memory: vptr per object + vtable per class
  • Indirect function call (cannot inline easily)
  • Prevents some optimizations

Pure virtual — abstract class

cpp
class Shape {
public:
  virtual double area() const = 0; // pure virtual
  virtual ~Shape() = default;
};
 
class Circle : public Shape {
  double radius;
public:
  double area() const override { return 3.14159 * radius * radius; }
};

Interview answer

Note

Virtual functions use a vtable per class and a vptr per object. When calling through a base pointer, the vptr indexes into the vtable to find the correct derived implementation at runtime. Always declare virtual destructors in polymorphic base classes.

Back to index


313. How to handle forms in React?

Answer

You can handle forms using controlled components (state) or uncontrolled components (refs). Controlled forms provide better control over input validation and behavior.

Back to index


314. How to measure performance in a React application?

Theory

React performance measurement spans lab metrics (controlled environment) and field metrics (real users). Key areas: render performance, bundle size, network timing, and Core Web Vitals.

Pros & Cons

ToolProsCons
React DevTools ProfilerFlame charts, render durationDev environment only
LighthouseComprehensive audit, CI-friendlyLab data, not real users
Web Vitals (RUM)Real user dataNeeds instrumentation
Bundle analyzerFinds large dependenciesDoesn't measure runtime

Real-Life Example

jsx
import { Profiler } from 'react'
import { onLCP, onINP, onCLS } from 'web-vitals'
 
// 1. React Profiler — measure component render time
function onRenderCallback(id, phase, actualDuration) {
  if (actualDuration > 16) {
    // longer than one frame
    console.warn(`Slow render: ${id} took ${actualDuration}ms`)
  }
}
 
;<Profiler id="RestaurantList" onRender={onRenderCallback}>
  <RestaurantList />
</Profiler>
 
// 2. Web Vitals in production
onLCP((metric) => analytics.track('LCP', metric))
onINP((metric) => analytics.track('INP', metric))
onCLS((metric) => analytics.track('CLS', metric))
 
// 3. Bundle analysis (package.json script)
// "analyze": "source-map-explorer build/static/js/*.js"

Performance budget example:

  • Initial JS bundle: < 200KB gzipped
  • LCP: < 2.5s on 4G
  • Component render: < 16ms (60fps)

Back to index


315. How would you build a tool like Create React App?

Theory

Create React App (CRA) is a CLI tool that scaffolds a production-ready React project with zero configuration. Building one requires: project templating, bundler setup, dev server, build pipeline, and extensibility.

Core components to build

ComponentTechnology
CLI scaffoldingcommander or yargs
Project templateCopied files (package.json, src/, public/)
BundlerWebpack or Vite
Dev serverHMR via webpack-dev-server or Vite
TranspilationBabel (JSX, modern syntax) or esbuild
CSS handlingPostCSS, CSS Modules, Sass loader
Build optimizationMinification, tree shaking, code splitting
Testing setupJest + React Testing Library

Pros & Cons

ProsCons
Zero-config developer experienceHides complexity — hard to customize
Consistent project structureEjected config is irreversible (CRA)
Fast onboardingOpinionated — may not fit all projects

Real-Life Example (simplified CLI)

javascript
#!/usr/bin/env node
const { program } = require('commander')
const fs = require('fs-extra')
const path = require('path')
const { execSync } = require('child_process')
 
program
  .name('create-my-app')
  .argument('<project-name>')
  .action(async (projectName) => {
    const targetDir = path.join(process.cwd(), projectName)
    const templateDir = path.join(__dirname, '../template')
 
    // 1. Copy template
    await fs.copy(templateDir, targetDir)
 
    // 2. Customize package.json
    const pkg = await fs.readJson(path.join(targetDir, 'package.json'))
    pkg.name = projectName
    await fs.writeJson(path.join(targetDir, 'package.json'), pkg, {
      spaces: 2,
    })
 
    // 3. Install dependencies
    execSync('npm install', { cwd: targetDir, stdio: 'inherit' })
 
    console.log(`\n✅ Created ${projectName}`)
    console.log(`   cd ${projectName} && npm start`)
  })
 
program.parse()
plaintext
template/
├── package.json        # react, react-dom, vite
├── vite.config.js      # bundler config
├── index.html
├── src/
│   ├── main.jsx
│   └── App.jsx
└── public/

Modern alternative: Use Vite instead of Webpack for faster dev experience (what most CRA successors do).

Back to index


316. How would you design a frontend system serving millions of users daily?

Theory — Full-system view

mermaid
flowchart TB
  Users[Millions of Users] --> CDN[Global CDN]
  CDN --> LB[Load Balancer]
  LB --> SSR[SSR / Edge Nodes]
  LB --> Static[Static Asset Origin]
 
  SSR --> BFF[BFF / API Gateway]
  BFF --> Services[Microservices]
 
  subgraph Client
    PWA[PWA + Service Worker]
    RUM[RUM / Analytics]
  end
 
  Users --> Client
  Client --> CDN

Key design decisions

1. Delivery & performance

  • Multi-region CDN with edge caching
  • SSR/ISR/RSC for fast first paint; static where possible
  • Code splitting + route-based chunks; bundle budgets in CI
  • Image optimization pipeline (WebP/AVIF, responsive, lazy load)
  • HTTP/3, Brotli compression, early hints (103)

2. Resilience

  • Graceful degradation — core flows work if recommendations API fails
  • Circuit breakers on client (stop retrying failing endpoints)
  • Error boundaries per feature; fallback UI
  • Retry with exponential backoff + jitter
  • Offline support for critical paths (view past orders)

3. Scalability patterns

  • BFF (Backend for Frontend) — tailor APIs per client, reduce over-fetching
  • GraphQL or typed REST with field selection
  • Feature flags — gradual rollouts, kill switches
  • Micro-frontends (only if teams are truly independent) — Module Federation or iframe shells

4. Observability

  • Real User Monitoring (RUM) for Web Vitals percentiles (p75, p95)
  • Distributed tracing (correlation IDs from client → API)
  • Error tracking (Sentry) with release tags
  • Synthetic monitoring from multiple regions

5. Security

  • CSP headers, Subresource Integrity for third-party scripts
  • httpOnly secure cookies for auth
  • Rate limiting awareness on client (debounce, disable double-submit)
  • XSS prevention — never dangerouslySetInnerHTML without sanitization

6. Release & operations

  • Blue-green or canary deploys
  • Preview environments per PR
  • Automated rollback on error rate spike
  • Load testing (k6) simulating peak lunch/dinner traffic

Practical SLA thinking (a food-delivery platform context)

Performance

For a food delivery app, lunch and dinner peaks are 10–50× baseline traffic. The frontend must: cache restaurant listings aggressively, stream menu pages, optimistically update cart, and degrade non-critical widgets (recommendations) under load. Target p75 LCP under 2.5s on 4G devices in tier-2 cities.

Numbers to mention in interviews

  • Bundle size budget: e.g. < 200KB gzipped initial JS
  • API p95 latency budget: < 300ms for critical paths
  • Error rate SLO: < 0.1% client-side unhandled errors
  • Cache hit ratio on CDN: > 90% for static assets

Use the STAR method: Situation → Task → Action → Result.

Keep answers 60–90 seconds. Quantify impact where possible.

Back to index


317. Implement Lodash's flatten method

Theory

Lodash's flatten converts a nested array into a flatter structure. flatten(array) flattens one level deep. flattenDeep(array) flattens all levels recursively.

This is common when APIs return nested category trees, nested form field groups, or deeply nested menu structures that need a flat list for rendering or search.

Pros & Cons

ProsCons
Simplifies nested data for UI renderingLoses structural hierarchy unless you keep depth metadata
Enables flat search/filter across nested dataDeep recursion can hit stack limits on very deep nesting
Native flat(Infinity) exists in modern JSflat creates a new array (memory cost)

Real-Life Example

javascript
function flatten(arr, depth = 1) {
  if (!Array.isArray(arr)) return arr
 
  return depth > 0
    ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val, depth - 1) : val), [])
    : arr.slice()
}
 
function flattenDeep(arr) {
  return flatten(arr, Infinity)
}
 
// Real-life: Flatten nested menu categories for search autocomplete
const menu = ['Home', ['Food', ['North Indian', ['Biryani', 'Kebab']], 'South Indian'], ['Drinks', 'Desserts']]
 
const searchableItems = flattenDeep(menu)
// ["Home", "Food", "North Indian", "Biryani", "Kebab", "South Indian", "Drinks", "Desserts"]

Back to index


318. Implement responsive design from the beginning

Theory

Responsive design ensures your UI works across mobile, tablet, and desktop. Build mobile-first (start with small screens, add breakpoints up) using CSS Grid, Flexbox, relative units (rem, %, vw), and container queries.

Don't treat responsive as an afterthought — retrofitting is expensive.

Pros & Cons

Mobile-first responsiveDesktop-only then retrofit
✅ Works on 60%+ mobile traffic❌ Expensive rework
✅ Forces content prioritization❌ Broken layouts on phones
✅ Better Core Web Vitals on mobile❌ Lost users on mobile

Real-Life Example

css
/* Mobile-first — base styles for small screens */
.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 1rem;
}
 
/* Tablet */
@media (min-width: 768px) {
  .product-grid {
    grid-template-columns: repeat(2, 1fr);
    padding: 1.5rem;
  }
}
 
/* Desktop */
@media (min-width: 1024px) {
  .product-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 1.5rem;
    padding: 2rem;
  }
}
tsx
// React — responsive with custom hook
function ProductPage() {
  const isMobile = useMediaQuery('(max-width: 768px)')
 
  return <div className="product-page">{isMobile ? <MobileProductLayout /> : <DesktopProductLayout />}</div>
}
 
// Tailwind example
;<div className="grid grid-cols-1 gap-4 p-4 md:grid-cols-2 md:p-6 lg:grid-cols-4">
  {products.map((p) => (
    <ProductCard key={p.id} product={p} />
  ))}
</div>

Back to index


319. Inline 5 Divs without Flex/Margin/Padding

Theory

Without flex, margin, or padding, place 5 divs in a horizontal row using display: inline-block. Inline-block elements sit side-by-side like text, respecting width/height unlike pure inline.

Watch for: whitespace gaps between inline-block elements (font-size zero hack on parent, or remove whitespace in HTML).

Pros & Cons

inline-blockfloat (alternative)
✅ Simple, in document flow❌ Removed from normal flow
✅ Respects width/heightNeeds clearfix
❌ Whitespace gap between itemsNo gap issue

Interview Answer

Set display inline-block on each div with a fixed width. Parent gets font-size 0 to remove whitespace gaps, then reset font-size on children.

Real Example

html
<div class="row">
  <div class="box">1</div>
  <!--
  -->
  <div class="box">2</div>
  <!--
  -->
  <div class="box">3</div>
  <!--
  -->
  <div class="box">4</div>
  <!--
  -->
  <div class="box">5</div>
</div>
css
/* Method 1 — font-size zero on parent */
.row {
  font-size: 0; /* removes whitespace gap */
}
 
.box {
  display: inline-block;
  box-sizing: border-box;
  border: 1px solid #333;
  background: #4a90d9;
  width: 20%;
  height: 100px;
  font-size: 16px; /* reset for content */
  line-height: 100px;
  text-align: center;
}
 
/* Method 2 — HTML comments remove whitespace (shown above) */
 
/* Method 3 — float (if inline-block not accepted) */
.box-float {
  float: left;
  width: 20%;
  height: 100px;
}
.row-float::after {
  display: table;
  clear: both;
  content: '';
}
html
<!-- Alternative: calc width -->
<style>
  .box-calc {
    display: inline-block;
    box-sizing: border-box;
    background: coral;
    width: calc(100% / 5);
    height: 80px;
  }
</style>

Back to index


320. Is JavaScript tightly coupled or loosely coupled?

Theory

Coupling measures how dependent one module is on another's internal details.

  • Tightly coupled — modules know each other's internals; changing one breaks others
  • Loosely coupled — modules interact through well-defined interfaces; changes are isolated

JavaScript as a language is flexible and supports both styles. By default, without discipline, JS code tends toward tight coupling — global variables, direct DOM manipulation, shared mutable state.

With good patterns, JS achieves loose coupling — modules, dependency injection, event-driven architecture, interfaces (TypeScript).

Pros & Cons

Tight CouplingLoose Coupling
Faster to write initiallyMore upfront design
Direct access — less boilerplateEasier to test in isolation
Hard to maintain at scaleEasier to swap implementations
Changes ripple across codebaseBetter team parallelization

Real-Life Example

javascript
// ❌ Tightly coupled — OrderService directly depends on Stripe internals
class OrderService {
  placeOrder(cart) {
    const stripe = new Stripe('sk_live_xxx')
    stripe.charges.create({ amount: cart.total, currency: 'inr' })
    document.getElementById('status').innerText = 'Paid' // DOM coupling too
  }
}
 
// ✅ Loosely coupled — depends on abstractions (interfaces)
class OrderService {
  constructor(paymentGateway, notifier) {
    this.paymentGateway = paymentGateway // injected
    this.notifier = notifier
  }
 
  async placeOrder(cart) {
    await this.paymentGateway.charge(cart.total)
    this.notifier.notify('order_placed', cart)
  }
}
 
// Swap Stripe for Razorpay without touching OrderService
const orderService = new OrderService(new RazorpayGateway(), new EmailNotifier())
tsx
// React loose coupling — composition over inheritance
function CheckoutPage() {
  return (
    <PaymentProvider gateway="razorpay">
      <CartSummary />
      <PaymentForm /> {/* doesn't know which gateway */}
      <OrderConfirmation />
    </PaymentProvider>
  )
}

Interview answer (concise)

Warning

JavaScript doesn't enforce coupling — it's up to the developer. Without patterns, JS tends to be tightly coupled. We achieve loose coupling through modules, dependency injection, React's component composition, TypeScript interfaces, and separating UI from business logic.

Back to index


321. Join/Merge Objects from Object List

Theory

Given an array of objects, merge them into one object. Common variants:

VariantBehavior
Shallow mergeLater keys overwrite earlier (Object.assign / spread)
Deep mergeNested objects merged recursively
Join by keyGroup array items by a property into { [key]: [...] }

the company likely asks shallow merge or deep merge of an object array.

Interview Answer

I reduce the array with spread or Object.assign — later objects override earlier keys. For nested objects I recursively merge each key.

Shallow Merge — Later Wins

javascript
const list = [
  { a: 1, b: 2 },
  { b: 3, c: 4 },
  { c: 5, d: 6 },
]
 
function mergeObjects(list) {
  return list.reduce((acc, obj) => ({ ...acc, ...obj }), {})
}
 
console.log(mergeObjects(list))
// { a: 1, b: 3, c: 5, d: 6 }
javascript
// Alternative — Object.assign
function mergeObjects(list) {
  return Object.assign({}, ...list)
}

Deep Merge

javascript
function isPlainObject(value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value)
}
 
function deepMerge(target, source) {
  const result = { ...target }
 
  for (const key of Object.keys(source)) {
    const sourceVal = source[key]
    const targetVal = result[key]
 
    if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
      result[key] = deepMerge(targetVal, sourceVal)
    } else {
      result[key] = sourceVal
    }
  }
 
  return result
}
 
function mergeObjectList(list) {
  return list.reduce((acc, obj) => deepMerge(acc, obj), {})
}
 
const list = [
  { user: { name: 'Amit', age: 28 }, role: 'dev' },
  { user: { city: 'Mumbai' }, role: 'senior' },
  { tags: ['react', 'ts'] },
]
 
console.log(mergeObjectList(list))
// {
//   user: { name: "Amit", age: 28, city: "Mumbai" },
//   role: "senior",
//   tags: ["react", "ts"]
// }

Join/Group by Key (alternate interpretation)

javascript
// Merge array of { id, ...fields } into { [id]: fields }
const users = [
  { id: 'u1', name: 'Amit', role: 'dev' },
  { id: 'u2', name: 'Priya', role: 'qa' },
  { id: 'u1', team: 'frontend' }, // same id — merge fields
]
 
function joinById(list, key = 'id') {
  return list.reduce((acc, item) => {
    const id = item[key]
    const { [key]: _, ...rest } = item
    acc[id] = { ...acc[id], ...rest }
    return acc
  }, {})
}
 
console.log(joinById(users))
// { u1: { name: "Amit", role: "dev", team: "frontend" }, u2: { name: "Priya", role: "qa" } }

React Usage

jsx
function MergedConfig({ configSources }) {
  const merged = useMemo(() => configSources.reduce((acc, cfg) => ({ ...acc, ...cfg }), {}), [configSources])
 
  return <AppSettings theme={merged.theme} apiUrl={merged.apiUrl} />
}

Back to index


322. Keep dependencies updated and remove unused packages

Theory

Outdated dependencies carry security vulnerabilities, miss performance improvements, and create compatibility issues. Unused packages bloat your bundle and increase attack surface.

Maintain a regular update cadence: weekly npm audit, monthly minor updates, careful major version upgrades with changelog review.

Pros & Cons

Updated dependenciesNeglected dependencies
✅ Security patches applied❌ Known CVEs in production
✅ Smaller bundles (tree-shaking improvements)❌ Breaking changes pile up
✅ New features and bug fixes❌ Harder to upgrade later
❌ Update time / testing cost—

Real-Life Example

bash
npm audit
npm audit fix
 
# Find outdated packages
npm outdated
 
# Remove unused dependencies
npx depcheck
 
# Analyze bundle size
npx source-map-explorer build/static/js/*.js
 
# Safe update workflow
npm update                    # minor/patch updates
npx npm-check-updates -u      # see major updates available
npm install                   # apply
npm test                      # verify nothing broke
json
// package.json — CI scripts
{
  "scripts": {
    "audit": "npm audit --audit-level=moderate",
    "depcheck": "depcheck",
    "analyze": "source-map-explorer build/static/js/*.js"
  }
}

Dependency hygiene checklist:

ActionFrequency
npm auditEvery PR / weekly
Remove unused packagesMonthly
Update patch/minor versionsMonthly
Review major version changelogsQuarterly
Lock file committed (package-lock.json)Always
Dependabot / Renovate in CIAutomated

Back to index


323. Largest Frontend Systems Built

What they're assessing

Scale (users, teams, code volume), your role (lead vs contributor), and impact.

Framework

text
Situation — product, users, team size
Task      — your ownership area
Action    — architecture, migrations, standards you drove
Result    — metrics (LCP, crash rate, delivery speed, revenue)

Sample answer skeleton

Performance

"I worked on a multi-tenant audit dashboard used by 2,000+ consultants across 12 regions. I owned the React + TypeScript shell, design system, and module federation for team micro-frontends. I introduced route-based code splitting and React Query for server state, cutting initial load from 4.2s to 1.8s LCP. I also set up CI visual regression and ESLint boundaries so five squads could ship without breaking shared contracts. Result: 40% fewer production incidents and 2× release frequency."

Topics to mention if true for you

  • Micro-frontends / monorepo (Nx, Turborepo)
  • Design system at scale
  • Auth (SSO, RBAC)
  • Accessibility compliance (WCAG)
  • Cross-browser / legacy IE migration

Interview Answer

Lead with user scale and business domain, then your specific ownership, one technical decision you drove, and a measurable outcome — not a list of technologies.

Back to index


324. Lift state up only when required

Theory

Lifting state up means moving shared state to the closest common ancestor of components that need it. Only lift when siblings need to share data or a parent must coordinate children.

Don't lift state prematurely — colocate state as close as possible to where it's used. Local state that only one component needs should stay local.

Pros & Cons

Lift when neededLift too early
✅ Siblings stay in sync❌ Unnecessary re-renders of parent tree
✅ Single source of truth❌ Props passed through components that don't care
✅ Predictable data flow❌ Harder to refactor later

Real-Life Example

tsx
// ❌ Over-lifted — theme in App when only Settings page needs it
function App() {
  const [fontSize, setFontSize] = useState(16) // only Settings uses this
  return (
    <>
      <Header fontSize={fontSize} />
      <Settings fontSize={fontSize} setFontSize={setFontSize} />
    </>
  )
}
 
// ✅ Colocated — state lives where it's used
function Settings() {
  const [fontSize, setFontSize] = useState(16)
  return <FontSizeSlider value={fontSize} onChange={setFontSize} />
}
 
// ✅ Correctly lifted — two siblings share selected tab
function Dashboard() {
  const [activeTab, setActiveTab] = useState('overview')
 
  return (
    <>
      <TabBar activeTab={activeTab} onTabChange={setActiveTab} />
      <TabPanel activeTab={activeTab} />
    </>
  )
}

Back to index


325. Live search box — vanilla JavaScript

Theory

a fintech app explicitly tests vanilla JS — reaching for React is a red flag. Build a live search with:

  1. Debounced API calls — don't hit server on every keystroke
  2. Keyboard navigation — ArrowUp/Down, Enter, Escape
  3. Click outside to close — mousedown on document
  4. Accessibility — ARIA combobox pattern

Pros & Cons

Vanilla JSReact for this task
✅ Shows DOM fundamentals❌ a fintech app Round 2 red flag
✅ No build step in interviewOverkill for 90-min test
✅ Proves you understand events

Real-Life Example — Full implementation

html
<div class="search" id="search-root">
  <label for="search-input" class="sr-only">Search transactions</label>
  <input
    id="search-input"
    type="search"
    role="combobox"
    aria-expanded="false"
    aria-controls="search-results"
    aria-autocomplete="list"
    placeholder="Search transactions..."
    autocomplete="off" />
  <ul id="search-results" role="listbox" hidden></ul>
</div>
javascript
function debounce(fn, delay) {
  let timer
  const debounced = (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
  debounced.cancel = () => clearTimeout(timer)
  return debounced
}
 
class LiveSearch {
  constructor(root) {
    this.root = root
    this.input = root.querySelector('#search-input')
    this.list = root.querySelector('#search-results')
    this.items = []
    this.activeIndex = -1
    this.abortController = null
 
    this.debouncedSearch = debounce(this.search.bind(this), 300)
    this.bindEvents()
  }
 
  bindEvents() {
    this.input.addEventListener('input', () => {
      const q = this.input.value.trim()
      if (!q) return this.close()
      this.debouncedSearch(q)
    })
 
    this.input.addEventListener('keydown', (e) => this.handleKeydown(e))
 
    // Click outside to close
    document.addEventListener('mousedown', (e) => {
      if (!this.root.contains(e.target)) this.close()
    })
  }
 
  async search(query) {
    if (this.abortController) this.abortController.abort()
    this.abortController = new AbortController()
 
    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
        signal: this.abortController.signal,
      })
      const data = await res.json()
      this.items = data.results ?? []
      this.render()
      this.open()
    } catch (err) {
      if (err.name !== 'AbortError') console.error(err)
    }
  }
 
  render() {
    this.list.innerHTML = this.items
      .map(
        (item, i) =>
          `<li role="option" id="option-${i}" aria-selected="${i === this.activeIndex}">
            ${item.label}
          </li>`,
      )
      .join('')
 
    this.list.querySelectorAll('li').forEach((li, i) => {
      li.addEventListener('mousedown', (e) => {
        e.preventDefault() // prevent input blur before click
        this.select(i)
      })
    })
  }
 
  handleKeydown(e) {
    if (!this.items.length) return
 
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault()
        this.activeIndex = Math.min(this.activeIndex + 1, this.items.length - 1)
        this.highlight()
        break
      case 'ArrowUp':
        e.preventDefault()
        this.activeIndex = Math.max(this.activeIndex - 1, 0)
        this.highlight()
        break
      case 'Enter':
        e.preventDefault()
        if (this.activeIndex >= 0) this.select(this.activeIndex)
        break
      case 'Escape':
        this.close()
        break
    }
  }
 
  highlight() {
    this.list.querySelectorAll('li').forEach((li, i) => {
      li.setAttribute('aria-selected', i === this.activeIndex)
      li.classList.toggle('active', i === this.activeIndex)
    })
    this.input.setAttribute('aria-activedescendant', `option-${this.activeIndex}`)
  }
 
  select(index) {
    this.input.value = this.items[index].label
    this.close()
    // navigate or callback
    console.log('Selected:', this.items[index])
  }
 
  open() {
    this.list.hidden = false
    this.input.setAttribute('aria-expanded', 'true')
    this.activeIndex = 0
    this.highlight()
  }
 
  close() {
    this.list.hidden = true
    this.input.setAttribute('aria-expanded', 'false')
    this.activeIndex = -1
    this.debouncedSearch.cancel()
  }
 
  destroy() {
    this.debouncedSearch.cancel()
    if (this.abortController) this.abortController.abort()
  }
}
 
new LiveSearch(document.getElementById('search-root'))

What a fintech app evaluates: debounce + cancel, AbortController, keyboard UX, click-outside, no framework dependency.

Back to index


326. Loading, Error & Empty States

Theory

Every data-fetching UI needs four states: loading, error, empty, success. Handle all explicitly — never show a blank screen.

Pattern: early returns or a unified AsyncBoundary component.

Interview Answer

I always handle loading, error, empty, and success states explicitly — early returns or a shared component so users never see a blank broken UI.

Real Example

jsx
function DataView({ query }) {
  const { data, isLoading, error, isFetching } = query
 
  if (isLoading) return <Skeleton rows={5} />
  if (error) return <ErrorBanner message={error.message} onRetry={query.refetch} />
  if (!data?.length) return <EmptyState title="No results" action={<CreateButton />} />
 
  return (
    <>
      {isFetching && <RefreshIndicator />}
      <ItemList items={data} />
    </>
  )
}

Back to index


327. Local Storage vs Cookies

Theory

Both store data in the browser, but with different capabilities and security models.

localStorageCookies
Capacity~5–10 MB~4 KB per cookie
Sent with requestsNoYes (every HTTP request to domain)
ExpiryNever (until cleared)Set via Expires / Max-Age
Accessible from JSYesYes (unless httpOnly)
Secure for tokens❌ XSS vulnerable✅ httpOnly + Secure + SameSite
APISynchronousDocument.cookie or server Set-Cookie

Also consider: sessionStorage (tab-scoped), IndexedDB (large structured data).

Pros & Cons

localStoragehttpOnly Cookies
✅ Large capacity✅ Not accessible to JS — XSS safe
✅ Simple API✅ Auto-sent — good for auth
❌ XSS can steal data❌ CSRF risk (mitigate with SameSite)
❌ Synchronous — blocks main thread❌ 4KB limit

Real-Life Example

javascript
// localStorage — user preferences (non-sensitive)
localStorage.setItem('theme', 'dark')
localStorage.setItem('recentSearches', JSON.stringify(['react', 'typescript']))
 
const theme = localStorage.getItem('theme')
 
// ❌ NEVER store auth tokens in localStorage
localStorage.setItem('token', jwt) // XSS can steal this
 
// ✅ Auth via httpOnly cookie (set by server)
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/
 
// sessionStorage — form draft (cleared on tab close)
sessionStorage.setItem('draftOrder', JSON.stringify(cartItems))
tsx
// Custom hook for localStorage
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key)
    return stored ? JSON.parse(stored) : initial
  })
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])
  return [value, setValue] as const
}

Back to index


328. Map, Filter & Reduce

Theory

These are higher-order array methods that transform data without mutating the original array.

MethodReturnsPurpose
mapNew array (same length)Transform each element
filterNew array (≤ length)Keep elements matching condition
reduceSingle valueAccumulate into one result

All accept a callback (element, index, array) and optional thisArg.

Pros & Cons

ProsCons
Declarative, readableCan't short-circuit (use for loop with break)
Immutable — no side effectsChaining creates intermediate arrays
ChainableSlightly slower than manual loop for hot paths

Real-Life Example

javascript
const products = [
  { id: 1, name: 'Biryani', price: 299, category: 'main', rating: 4.5 },
  { id: 2, name: 'Naan', price: 49, category: 'bread', rating: 4.0 },
  { id: 3, name: 'Lassi', price: 79, category: 'drink', rating: 4.2 },
  { id: 4, name: 'Kebab', price: 199, category: 'main', rating: 4.8 },
]
 
// map — transform for UI
const menuItems = products.map((p) => ({
  label: `${p.name} — ₹${p.price}`,
  value: p.id,
}))
 
// filter — show only highly rated mains under ₹300
const affordable = products.filter((p) => p.category === 'main' && p.rating >= 4.5 && p.price <= 300)
 
// reduce — calculate cart total
const cart = [
  { id: 1, qty: 2 },
  { id: 2, qty: 4 },
]
const total = cart.reduce((sum, item) => {
  const product = products.find((p) => p.id === item.id)
  return sum + product.price * item.qty
}, 0)
// 299*2 + 49*4 = 794
 
// Chaining
const summary = products
  .filter((p) => p.rating >= 4.0)
  .map((p) => p.name)
  .reduce((acc, name) => acc + name + ', ', '')
 
// reduce can implement map and filter
const doubled = [1, 2, 3].reduce((acc, n) => [...acc, n * 2], [])
jsx
// React — primary list rendering pattern
function ProductList({ products }) {
  return (
    <ul>
      {products
        .filter((p) => p.inStock)
        .map((p) => (
          <ProductCard key={p.id} product={p} />
        ))}
    </ul>
  )
}

Back to index


329. Measure performance using React DevTools and Lighthouse

Theory

You can't optimize what you don't measure. Use React DevTools Profiler for component render performance and Lighthouse for overall web vitals (LCP, INP, CLS, TTI, bundle size).

Set performance budgets and enforce them in CI to prevent regressions.

Pros & Cons

Measuring performanceGuessing performance
✅ Data-driven optimization❌ Premature memoization everywhere
✅ Catch regressions in CI❌ Users report slowness first
✅ Prioritize biggest wins❌ Waste time on non-issues
❌ Tooling setup time—

Real-Life Example

React DevTools Profiler

plaintext
1. Install React DevTools browser extension
2. Open Profiler tab → click Record
3. Interact with your app (navigate, type, scroll)
4. Stop recording → analyze:
   - Flame chart: which components rendered and how long
   - Ranked chart: slowest components first
   - "Why did this render?" (React 19+)
tsx
// Programmatic profiling in development
import { Profiler } from 'react'
 
function onRenderCallback(id: string, phase: 'mount' | 'update', actualDuration: number) {
  if (actualDuration > 16) {
    // slower than 1 frame at 60fps
    console.warn(`[Profiler] ${id} (${phase}): ${actualDuration.toFixed(1)}ms`)
  }
}
 
;<Profiler id="ProductList" onRender={onRenderCallback}>
  <ProductList products={products} />
</Profiler>

Lighthouse

bash
npx lighthouse https://your-app.com --view
 
# CI integration
npm install -D @lhci/cli
# lighthouserc.js — fail CI if scores drop below threshold

Web Vitals in production

tsx
import { onLCP, onINP, onCLS } from 'web-vitals'
 
function sendToAnalytics(metric) {
  // Google Analytics, Datadog, Sentry
  analytics.track(metric.name, {
    value: metric.value,
    page: window.location.pathname,
  })
}
 
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)

Performance budget example:

MetricBudget
Initial JS bundle< 200 KB gzipped
LCP< 2.5s
INP< 200ms
CLS< 0.1
Component render< 16ms (60fps)

Quick Revision Cheat Sheet

#PracticeOne-liner
1Small componentsOne responsibility, ~150 lines max
2Lift state wiselyColocate first; lift only for shared siblings
3Custom hooksExtract duplicate stateful logic
4Immutable stateAlways new reference: spread, map, filter
5Unique keysStable IDs from data, not index
6Re-rendersState, parent, context trigger renders
7MemoizationProfile first; useMemo/useCallback when needed
8API statesAlways handle loading, error, empty, success
9Code splittingReact.lazy per route + Suspense fallback
10Separate logicutils + services + hooks + UI components
11Folder structureFeature-based, not type-based
12Error BoundariesPer-route fallback, log to Sentry
13Prop drillingContext/composition/Zustand when >2 levels
14useEffect cleanupClose WS, clear timers, abort fetch
15Unit testsTest behavior, critical paths, RTL
16AccessibilitySemantic HTML, ARIA, keyboard, contrast
17ResponsiveMobile-first, Grid/Flexbox, breakpoints
18Env variablesVITE_ prefix, never secrets in frontend
19Dependenciesnpm audit, depcheck, regular updates
20PerformanceReact Profiler + Lighthouse + Web Vitals RUM

These 20 practices form the foundation of production-grade React development. Apply them from day one of a project — retrofitting is always harder than building right.

Back to index


330. Multi-field form — PAN, Aadhaar

Theory

a fintech app Round 3 tests KYC-style forms — PAN, Aadhaar, with strict validation, error states, and state design before coding. They watch your architecture decisions before you write the first <input>.

Validation rules (typical):

  • PAN: AAAAA9999A — 5 letters, 4 digits, 1 letter
  • Aadhaar: 12 digits, Verhoeff checksum (or format-only in interviews)

Pros & Cons — form libraries vs custom

React Hook FormManual useState
✅ Less re-renders (uncontrolled)✅ Full control in interview
✅ Built-in validation❌ More boilerplate
Interview: either works if state design is clean

Real-Life Example

tsx
// validators.ts — pure functions, easy to test
const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/
const AADHAAR_REGEX = /^\d{12}$/
 
export function validatePAN(value: string): string | null {
  if (!value) return 'PAN is required'
  if (!PAN_REGEX.test(value.toUpperCase())) return 'Invalid PAN format (e.g. ABCDE1234F)'
  return null
}
 
export function validateAadhaar(value: string): string | null {
  const digits = value.replace(/\s/g, '')
  if (!digits) return 'Aadhaar is required'
  if (!AADHAAR_REGEX.test(digits)) return 'Aadhaar must be 12 digits'
  return null
}
 
export function validateFullName(value: string): string | null {
  if (!value.trim()) return 'Full name is required'
  if (value.trim().length < 3) return 'Name too short'
  return null
}
tsx
// KYCForm.tsx — controlled fields with per-field errors
type FormState = {
  fullName: string
  pan: string
  aadhaar: string
  consent: boolean
}
 
type FormErrors = Partial<Record<keyof FormState, string>>
 
const INITIAL: FormState = {
  fullName: '',
  pan: '',
  aadhaar: '',
  consent: false,
}
 
function KYCForm() {
  const [values, setValues] = useState<FormState>(INITIAL)
  const [errors, setErrors] = useState<FormErrors>({})
  const [touched, setTouched] = useState<Partial<Record<keyof FormState, boolean>>>({})
 
  const validators: Record<string, (v: string) => string | null> = {
    fullName: validateFullName,
    pan: validatePAN,
    aadhaar: validateAadhaar,
  }
 
  const setField = (field: keyof FormState, value: string | boolean) => {
    setValues((prev) => ({ ...prev, [field]: value }))
    if (touched[field] && typeof value === 'string') {
      const error = validators[field]?.(value) ?? null
      setErrors((prev) => ({ ...prev, [field]: error ?? undefined }))
    }
  }
 
  const handleBlur = (field: keyof FormState) => {
    setTouched((prev) => ({ ...prev, [field]: true }))
    const val = values[field]
    if (typeof val === 'string') {
      const error = validators[field]?.(val)
      setErrors((prev) => ({ ...prev, [field]: error ?? undefined }))
    }
  }
 
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    const newErrors: FormErrors = {}
    ;(['fullName', 'pan', 'aadhaar'] as const).forEach((field) => {
      const err = validators[field](values[field] as string)
      if (err) newErrors[field] = err
    })
    if (!values.consent) newErrors.consent = 'Consent is required'
    setErrors(newErrors)
    setTouched({ fullName: true, pan: true, aadhaar: true, consent: true })
    if (Object.keys(newErrors).length === 0) submitKYC(values)
  }
 
  return (
    <form onSubmit={handleSubmit} noValidate>
      <Field
        label="Full Name"
        id="fullName"
        value={values.fullName}
        error={touched.fullName ? errors.fullName : undefined}
        onChange={(v) => setField('fullName', v)}
        onBlur={() => handleBlur('fullName')}
      />
      <Field
        label="PAN"
        id="pan"
        value={values.pan}
        error={touched.pan ? errors.pan : undefined}
        onChange={(v) => setField('pan', v.toUpperCase())}
        onBlur={() => handleBlur('pan')}
        maxLength={10}
        placeholder="ABCDE1234F"
      />
      <Field
        label="Aadhaar"
        id="aadhaar"
        value={values.aadhaar}
        error={touched.aadhaar ? errors.aadhaar : undefined}
        onChange={(v) => setField('aadhaar', v.replace(/\D/g, '').slice(0, 12))}
        onBlur={() => handleBlur('aadhaar')}
        inputMode="numeric"
      />
      <button type="submit">Verify & Continue</button>
    </form>
  )
}

Back to index


331. Never mutate state directly

Theory

React detects state changes by reference comparison (Object.is). Mutating an object or array in place doesn't change its reference, so React won't re-render — and even if you force a re-render, other components holding the old reference see stale data.

Always create new objects/arrays when updating state. React 18's useState setter does not auto-clone your data.

Pros & Cons

Immutable updatesDirect mutation
✅ Predictable re-renders❌ Silent bugs — UI doesn't update
✅ Time-travel debugging works❌ Stale references in child components
✅ Easier change detection❌ Breaks React.memo comparisons
✅ Safer concurrent rendering—

Real-Life Example

tsx
function Cart() {
  const [items, setItems] = useState<CartItem[]>([])
 
  // ❌ WRONG — mutates state directly
  const addItemBad = (item: CartItem) => {
    items.push(item) // mutates array in place
    setItems(items) // same reference → React may skip re-render
  }
 
  // ✅ CORRECT — new array reference
  const addItem = (item: CartItem) => {
    setItems((prev) => [...prev, item])
  }
 
  // ✅ Update nested object
  const updateQuantity = (id: string, qty: number) => {
    setItems((prev) => prev.map((item) => (item.id === id ? { ...item, quantity: qty } : item)))
  }
 
  // ✅ Remove item
  const removeItem = (id: string) => {
    setItems((prev) => prev.filter((item) => item.id !== id))
  }
 
  return <CartList items={items} onUpdate={updateQuantity} onRemove={removeItem} />
}
tsx
// Redux Toolkit uses Immer internally — looks like mutation but is safe
const cartSlice = createSlice({
  name: 'cart',
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload) // Immer creates draft — safe
    },
  },
})

Back to index


332. New features in latest React versions

Theory

Stay current with React 18 and React 19 features — Engineo asks this to verify you follow the ecosystem.

React 18 (2022)

FeatureWhat it does
Concurrent renderingInterruptible rendering, keeps UI responsive
Automatic batchingBatch state updates in timeouts, promises, native events
TransitionsuseTransition, useDeferredValue — mark updates low priority
Suspense improvementsSSR streaming with renderToPipeableStream
Strict Mode changesDouble-invoke effects in dev
createRootReplaces ReactDOM.render

React 19 (2024–2025)

FeatureWhat it does
ActionsuseActionState, form actions with async
useOptimisticOptimistic UI updates built-in
use() hookRead promises and context in render
Ref as propNo more forwardRef needed
Document metadata<title>, <meta> in components
Server ComponentsStable in frameworks (Next.js)
Improved hydrationBetter mismatch error messages

Interview Answer

React 18 brought concurrent rendering, automatic batching, and useTransition. React 19 adds Actions, useOptimistic, the use hook, and drops forwardRef. I use transitions for search filters and Suspense for code splitting.

Real Example

tsx
// React 18 — useTransition for non-urgent updates
function SearchPage() {
  const [query, setQuery] = useState('')
  const [isPending, startTransition] = useTransition()
  const [results, setResults] = useState([])
 
  const handleChange = (value: string) => {
    setQuery(value) // urgent — input stays responsive
    startTransition(() => {
      setResults(filterProducts(value)) // non-urgent — can be interrupted
    })
  }
 
  return (
    <>
      <input value={query} onChange={(e) => handleChange(e.target.value)} />
      {isPending && <Spinner />}
      <ProductList products={results} />
    </>
  )
}
 
// React 19 — useOptimistic
function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimistic] = useOptimistic(todos, (state, newTodo) => [
    ...state,
    { ...newTodo, pending: true },
  ])
 
  async function handleAdd(text) {
    addOptimistic({ id: crypto.randomUUID(), text })
    await addTodo(text)
  }
}

Back to index


333. Normal Functions vs Arrow Functions

Theory

FeatureNormalArrow
thisDynamic (call site)Lexical (enclosing scope)
argumentsYesNo — use rest ...args
new / constructorYesNo
prototypeYesNo
SyntaxVerboseConcise, implicit return

Interview Answer

Normal functions have dynamic this and can be constructors. Arrow functions inherit this lexically — perfect for callbacks, wrong for object methods and constructors.

Real Example

javascript
const paymentService = {
  transactions: [100, 200, 300],
 
  // ✅ Normal — this = paymentService
  getTotal() {
    return this.transactions.reduce((a, b) => a + b, 0)
  },
 
  // ❌ Arrow — this is NOT paymentService
  brokenTotal: () => this?.transactions?.reduce((a, b) => a + b, 0),
 
  // ✅ Arrow inside normal — inherits correct this
  getFormatted() {
    return this.transactions.map((t) => `₹${t}`)
  },
}

Back to index


334. Normal vs Arrow Functions

Theory

NormalArrow
thisDynamicLexical
argumentsYesNo
new / constructorYesNo
SyntaxVerboseConcise

Interview Answer

Normal functions have dynamic this and can be constructors. Arrow functions inherit this lexically — ideal for callbacks, wrong for object methods.

Real Example

javascript
const service = {
  items: [1, 2, 3],
  sumRegular() {
    return this.items.reduce((a, b) => a + b, 0)
  }, // ✅
  sumArrow: () => this?.items?.reduce((a, b) => a + b, 0), // ❌
}

Back to index


335. Object Methods

Theory

JavaScript provides built-in static methods on Object and instance methods on Object.prototype for creating, copying, comparing, and iterating objects.

Key methods

MethodPurpose
Object.keys(obj)Array of own enumerable property names
Object.values(obj)Array of own enumerable property values
Object.entries(obj)Array of [key, value] pairs
Object.assign(target, ...sources)Shallow copy/merge
Object.freeze(obj)Prevent all mutations
Object.seal(obj)Prevent add/delete, allow modify
Object.hasOwn(obj, key)Safe own-property check
Object.fromEntries(arr)Inverse of entries
Object.create(proto)Create with specific prototype

Pros & Cons

Object static methodsManual iteration
✅ Standard, well-supported❌ Verbose
✅ Composable (entries → map → fromEntries)❌ Easy to miss non-enumerable props
Object.freeze for immutabilityfreeze is shallow only

Real-Life Example

javascript
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
}
 
// Iterate
Object.keys(config).forEach((key) => console.log(key, config[key]))
Object.entries(config).map(([k, v]) => `${k}=${v}`)
 
// Merge configs
const defaults = { timeout: 3000, retries: 1 }
const finalConfig = { ...defaults, ...config }
 
// Transform object
const inverted = Object.fromEntries(Object.entries(config).map(([k, v]) => [k.toUpperCase(), v]))
 
// Safe property check
Object.hasOwn(config, 'apiUrl') // true
Object.hasOwn(config, 'toString') // false (inherited)
 
// Freeze for constants
const TAX_RATES = Object.freeze({
  GST: 0.05,
  SERVICE: 0.1,
})
// TAX_RATES.GST = 0.08; // silent fail (strict: TypeError)
 
// Clone
const shallow = { ...config }
const deep = structuredClone(config)
javascript
// Real-life: Form state to API payload
const formState = { name: 'Amit', email: 'a@b.com', phone: '' }
const payload = Object.fromEntries(Object.entries(formState).filter(([_, v]) => v !== ''))
// { name: "Amit", email: "a@b.com" }

Back to index


336. Object vs Map differences in JavaScript

Theory

Both Object and Map store key-value pairs, but they differ in key types, iteration, performance, and API design. Object is the general-purpose data structure with prototype inheritance. Map is a dedicated hash map with any type of key and guaranteed insertion order.

Pros & Cons

ObjectMap
ProsJSON-serializable, familiar syntax, literal shorthandAny key type, size property, better frequent add/delete perf
ConsKeys only string/symbol, prototype pollution risk, no sizeNot JSON-serializable, slightly more verbose

Real-Life Example

javascript
// Object — config/settings (string keys, JSON-friendly)
const appConfig = {
  apiUrl: 'https://api.a food-delivery platform.com',
  timeout: 5000,
  retries: 3,
}
 
// Map — caching DOM elements or object-keyed lookups
const elementCache = new Map()
function getCachedElement(node) {
  if (!elementCache.has(node)) {
    elementCache.set(node, {
      height: node.offsetHeight,
      width: node.offsetWidth,
    })
  }
  return elementCache.get(node)
}
 
// Map — counting occurrences with non-string keys
const requestCounts = new Map()
function trackRequest(requestObj) {
  requestCounts.set(requestObj, (requestCounts.get(requestObj) || 0) + 1)
}

Back to index


337. Objects in JavaScript

Theory

In JavaScript, almost everything is an object (or can behave like one). Objects are collections of key-value pairs — properties and methods. Keys are strings or symbols; values can be any type.

Objects are reference types — assigned and compared by reference, not value.

Interview Answer

Objects store key-value pairs and are passed by reference. They're the foundation of JS — arrays, functions, and even primitives have object wrappers.

Real Example

javascript
const user = {
  id: 'u_1',
  name: 'Amit',
  role: 'admin',
  greet() {
    return `Hello, ${this.name}`
  },
}
 
user.greet() // "Hello, Amit"
typeof user // "object"

Back to index


338. Optimistic UI Updates

Theory

Optimistic UI updates the interface immediately before the server confirms the action. If the server request fails, the UI rolls back to the previous state.

Use for: likes, cart add/remove, todo toggle, message send — actions where success is likely and instant feedback matters.

Pros & Cons

ProsCons
Instant perceived responseRollback UX can confuse users
Better UX on slow networksComplex state management
Feels native/app-likeWrong for critical actions (payments)

Real-Life Example

tsx
function LikeButton({ postId, initialLiked, initialCount }) {
  const queryClient = useQueryClient()
 
  const mutation = useMutation({
    mutationFn: (liked: boolean) =>
      fetch(`/api/posts/${postId}/like`, {
        method: liked ? 'POST' : 'DELETE',
      }),
 
    // Optimistic update — before server responds
    onMutate: async (liked) => {
      await queryClient.cancelQueries({ queryKey: ['post', postId] })
      const previous = queryClient.getQueryData(['post', postId])
 
      queryClient.setQueryData(['post', postId], (old: Post) => ({
        ...old,
        liked,
        likeCount: old.likeCount + (liked ? 1 : -1),
      }))
 
      return { previous } // snapshot for rollback
    },
 
    // Rollback on error
    onError: (_err, _liked, context) => {
      queryClient.setQueryData(['post', postId], context?.previous)
      toast.error('Failed to update like')
    },
 
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['post', postId] })
    },
  })
 
  return (
    <button onClick={() => mutation.mutate(!initialLiked)}>
      {initialLiked ? '❤️' : '🤍'} {initialCount}
    </button>
  )
}

Back to index


339. Output prediction for tricky JavaScript snippets

Theory

Output prediction questions test your understanding of hoisting, closures, this binding, type coercion, event loop ordering, and reference vs value. Interviewers want you to explain why, not just guess the output.

Pros & Cons of these questions in interviews

ProsCons
Reveals deep language understandingCan feel academic vs real-world
Fast to ask in live codingSome edge cases rarely appear in production

Real-Life Example — 12 Tricky Snippets

Snippet 1 — Hoisting + TDZ

javascript
console.log(a)
var a = 1
console.log(b)
let b = 2

Output: undefined → ReferenceError (b is in TDZ)

Why: var is hoisted and initialized as undefined. let is hoisted but not initialized.

Snippet 2 — Closure in loop

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 3, 3, 3

Why: var is function-scoped; all closures share the same i which is 3 after the loop.


Snippet 3 — Closure with let

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 0, 1, 2

Why: let creates a new binding per iteration.


Snippet 4 — Event loop order

javascript
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')

Output: 1, 4, 3, 2

Why: Sync first → microtasks (promises) → macrotasks (setTimeout).


Snippet 5 — typeof null

javascript
console.log(typeof null)
console.log(null instanceof Object)

Output: "object" → false

Why: Historical bug — typeof null returns "object". instanceof checks prototype chain.


Snippet 6 — Implicit coercion

javascript
console.log([] + [])
console.log([] + {})
console.log({} + [])

Output: "" → "[object Object]" → "[object Object]"

Why: + with arrays/objects triggers toString().


Snippet 7 — this in arrow vs regular function

javascript
const obj = {
  name: 'Amit',
  regular() {
    console.log(this.name)
  },
  arrow: () => console.log(this.name),
}
obj.regular()
obj.arrow()

Output: "Amit" → undefined (or global name in non-strict)

Why: Arrow functions don't have their own this — they inherit from enclosing scope.


Snippet 8 — Promise chaining

javascript
Promise.resolve(1)
  .then((v) => {
    console.log(v)
    return v + 1
  })
  .then((v) => {
    console.log(v)
    throw new Error('fail')
  })
  .then((v) => console.log(v))
  .catch((e) => console.log('caught'))

Output: 1 → 2 → caught

Why: Thrown error skips next .then, goes to .catch.


Snippet 9 — async/await order

javascript
async function foo() {
  console.log('A')
  await Promise.resolve()
  console.log('B')
}
console.log('C')
foo()
console.log('D')

Output: C, A, D, B

Why: await pauses foo and schedules resume as microtask after sync code.


Snippet 10 — Object reference

javascript
const a = { n: 1 }
const b = a
b.n = 2
console.log(a.n)

Output: 2

Why: Objects are passed by reference — a and b point to the same object.


Snippet 11 — == vs ===

javascript
console.log(0 == false)
console.log(0 === false)
console.log('' == 0)
console.log(null == undefined)
console.log(null === undefined)

Output: true, false, true, true, false

Why: == does type coercion; === does not.


Snippet 12 — IIFE + closure

javascript
;(function () {
  try {
    throw new Error('x')
  } catch (e) {
    var e = 2
    console.log(e)
  }
})()

Output: 2

Why: var e in catch is function-scoped and hoisted, shadowing the catch parameter.

Back to index


340. Output-based JavaScript Questions

Theory

Senior interviews test deep understanding through output prediction. Explain why, not just the answer. Topics: hoisting, closures, event loop, coercion, this, prototypes.

Q1 — Hoisting + var

javascript
console.log(a)
var a = 1
console.log(a)

Output: undefined → 1


Q2 — TDZ

javascript
console.log(b)
let b = 2

Output: ReferenceError


Q3 — Closure + var loop

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 3, 3, 3


Q4 — Closure + let loop

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 0, 1, 2


Q5 — Event loop

javascript
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')

Output: 1 → 4 → 3 → 2


Q6 — async/await order

javascript
async function foo() {
  console.log('A')
  await Promise.resolve()
  console.log('B')
}
console.log('C')
foo()
console.log('D')

Output: C → A → D → B


Q7 — this + arrow

javascript
const obj = {
  name: 'Senior',
  regular() {
    return this.name
  },
  arrow: () => this?.name,
}
console.log(obj.regular())
console.log(obj.arrow())

Output: "Senior" → undefined


Q8 — Prototype

javascript
function Person(name) {
  this.name = name
}
Person.prototype.greet = function () {
  return this.name
}
const p = new Person('Amit')
console.log(p.greet())
console.log(p.hasOwnProperty('greet'))

Output: "Amit" → false


Q9 — Coercion

javascript
console.log([] + [])
console.log([] + {})
console.log({} + [])

Output: "" → "[object Object]" → "[object Object]"


Q10 — Promise chain

javascript
Promise.resolve(1)
  .then((v) => v + 1)
  .then((v) => {
    throw new Error('fail')
  })
  .then((v) => console.log(v))
  .catch((e) => console.log('caught'))
  .then(() => console.log('done'))

Output: caught → done


Q11 — typeof null

javascript
console.log(typeof null)
console.log(null instanceof Object)

Output: "object" → false


Q12 — Currying output

javascript
function multiply(a) {
  return function (b) {
    return a * b
  }
}
const double = multiply(2)
console.log(double(5))
console.log(multiply(3)(4))

Output: 10 → 12


DoDon't
Explain why behind outputGuess without reasoning
Connect JS to React (closures → hooks, event loop → useEffect)Treat JS and React as separate
Write code from scratch (debounce, curry, flatten)Only know definitions
Mention edge cases (TDZ, shallow copy, arrow this)Give textbook paragraphs

Quick Revision — All 20

#TopicOne-liner
1ClosureFunction + remembered outer scope
2Event loopSync → microtasks → macrotask
3Micro vs macroPromises first, setTimeout later
4HoistingDeclarations registered before run
5TDZlet/const inaccessible until declared
6var/let/constBlock scope; const default
7== vs ===Coercion vs strict — always ===
8call/apply/bindInvoke now vs bind this for later
9Arrow + bindNo — lexical this, bind ignored
10Normal vs arrowDynamic vs lexical this
11Debounce/throttlePause vs rate-limit
12Curryingf(a)(b)(c) — partial application
13PrototypesProperty lookup chain
14Bubbling/capturingTarget → up vs window → down
15GCMark-and-sweep unreachable objects
16Deep vs shallowNested shared vs full clone
17async/awaitPromise sugar, microtask resume
18map/filter/reduceTransform, select, accumulate
19FlattenRecursive or flat(Infinity)
20Output questionsHoisting, loop, event loop, this

Senior React interviews are won on JavaScript depth. Know the concept, explain it in 30 seconds, prove it with one example.

Back to index


341. Output-based tricky JS questions (Bonus)

Theory

the company interviews also test output prediction — hoisting, TDZ, closures, and event loop. Explain why, not just the answer.

Real-Life Example — 8 snippets

Q1 — Hoisting + var

javascript
console.log(a)
var a = 10
console.log(a)

Output: undefined → 10

Q2 — TDZ

javascript
console.log(b)
let b = 20

Output: ReferenceError (TDZ — not undefined)


Q3 — Closure + var in loop

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}

Output: 3, 3, 3


Q4 — Closure + let in loop

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}

Output: 0, 1, 2


Q5 — Event loop

javascript
console.log('A')
setTimeout(() => console.log('B'), 0)
Promise.resolve().then(() => console.log('C'))
console.log('D')

Output: A → D → C → B


Q6 — typeof undeclared vs TDZ

javascript
console.log(typeof notDeclared) // "undefined"
let x = 1
// console.log(typeof y);      // ReferenceError — y in TDZ
let y = 2

Q7 — undefined vs not defined

javascript
let user
console.log(user) // undefined
console.log(user.name) // TypeError (can't read property of undefined)
// console.log(profile);     // ReferenceError (not defined)

Q8 — this in arrow vs regular

javascript
const obj = {
  name: 'the company',
  regular() {
    return this.name
  },
  arrow: () => this?.name,
}
console.log(obj.regular()) // "the company"
console.log(obj.arrow()) // undefined

AreaTopics in this guide
JavaScript coreExecution, hoisting, TDZ, undefined vs not defined
CSS layoutposition, Flexbox, Grid
React patternsHOC, traffic light state + timing
Array methodsmap, filter, reduce
AccessibilityARIA, semantic HTML, keyboard
Output questionsHoisting, closure, event loop

Quick Revision Cheat Sheet

#TopicOne-liner
1Web AccessibilityWCAG, semantic HTML, keyboard, ARIA, contrast
2CSS positionstatic=flow, relative=offset, absolute=ancestor, fixed=viewport
3Flexbox vs Grid1D content-driven vs 2D layout-driven
4JS executionParse → compile → execute context → event loop
5undefined vs not definedExists/no value vs never declared (ReferenceError)
6HoistingDeclarations registered before execution; var=undefined
7TDZlet/const hoisted but inaccessible until declaration line
8HOCFunction(Component) → EnhancedComponent; prefer hooks now
9map/filter/reduceTransform, select, accumulate — immutable
10Traffic lightuseState + useEffect + setTimeout + cleanup; 5s/3s/2s

Back to index


342. Principles of Functional Programming

Theory

Functional Programming (FP) treats computation as the evaluation of pure functions, avoiding mutable state and side effects. Core principles:

  1. Pure functions — same input always produces same output, no side effects
  2. Immutability — never mutate data; create new copies
  3. First-class functions — functions are values, can be passed/returned
  4. Higher-order functions — functions that take/return functions
  5. Function composition — combine small functions into pipelines
  6. Declarative style — describe what, not how

Pros & Cons

ProsCons
Predictable, easier to testLearning curve for OOP developers
Fewer bugs from shared mutable stateCan be verbose without language support
Enables parallelismPerformance cost of copying large data

Real-Life Example

javascript
// Imperative (mutating)
function getActiveOrderTotals(orders) {
  const totals = []
  for (let i = 0; i < orders.length; i++) {
    if (orders[i].status === 'active') {
      orders[i].total = orders[i].items.reduce((s, item) => s + item.price, 0)
      totals.push(orders[i].total)
    }
  }
  return totals
}
 
// Functional (immutable, pure)
const getActiveOrderTotals = (orders) =>
  orders
    .filter((order) => order.status === 'active')
    .map((order) => order.items.reduce((sum, item) => sum + item.price, 0))
 
// React embraces FP
const [orders, setOrders] = useState([])
setOrders((prev) => [...prev, newOrder]) // immutable update

Back to index


343. Projects deep dive

Theory

a fintech app Round 1 opens with a deep project walkthrough — not a resume summary. They probe: what you built, why you made specific technical choices, what broke, and what you'd do differently today.

Use STAR for features but add a technical decision layer: alternatives considered, trade-offs, metrics.

What interviewers probe

QuestionWhat they want
Why this architecture?Trade-off thinking, not "it was trendy"
What broke in production?Ownership, debugging skill
What would you redo?Growth mindset, hindsight learning
How did you measure success?Impact, not just code

Real-Life Example — Answer skeleton

Note

Project: Payment checkout flow for a fintech app handling UPI, cards, and wallet.

Note

What I built: Multi-step checkout with real-time payment status via WebSocket, optimistic cart updates, and retry logic for failed UPI intents.

Performance

Why: Chose TanStack Query for payment status polling with exponential backoff instead of raw setInterval — automatic dedup and cache invalidation. Used optimistic updates for cart because 95% of add-to-cart succeeds; rollback on failure.

Warning

What I'd do differently: Initially stored payment tokens in localStorage — migrated to httpOnly cookies after security review. Would also split the 800-line checkout component into feature slices from day one.

Performance

Metrics: Payment success rate improved 4% → 97.2%. Checkout abandonment dropped 18% after adding skeleton loaders and fixing CLS.

Pros & Cons framing (for "why X over Y")

ChoiceProsConsWhen a fintech app cares
React Query vs Redux for APILess boilerplate, cachingAnother dependencyServer vs client state split
WebSocket vs pollingReal-timeConnection managementPayment status UX
Monolith vs micro-frontendSimplerTeam scalingOnly if you led the decision

Back to index


344. Quick Revision Cheat Sheet

#ScenarioCore answer
1Slow multi-API pagePromise.all, Suspense streaming, cache with revalidate
2Live stock dataHybrid: dynamic SSR snapshot + WebSocket/polling on client — not SSG
3Large bundleServer Components, next/dynamic, bundle analyzer, optimizePackageImports
4Server cachingfetch cache + tags, ISR, Redis for hot paths, revalidateTag
510K blog postsgenerateStaticParams + ISR + on-demand revalidation from CMS
6Heavy chart on interactiondynamic(..., { ssr: false }), import on click
7Large file uploadPresigned URL → direct S3 upload → completion webhook
8High-volume API routesRate limit, CDN cache, edge runtime, async queues
9SPA → Next.jsIncremental routes, middleware auth, fix hydration, client islands
10A/B testingMiddleware cookie assignment, server render variant, track in analytics
Related guides in this repo
  • 10-frontend-concepts-checklist.md — CSR vs SSR vs SSG vs ISR
  • 06-react-best-practices-project-guide.md — code splitting, performance
  • 20-senior-frontend-real-world-interview.md — production engineering topics

Back to index


345. React 18 Automatic Batching

Theory

React 18 batches all state updates into one re-render — inside events, setTimeout, promises, and native handlers. Previously only batched in React event handlers.

flushSync() forces synchronous render when needed (measuring DOM).

Interview Answer

React 18 batches multiple setState calls everywhere — events, timeouts, promises — into one re-render. Use flushSync only when you need immediate DOM access.

Real Example

jsx
function handleClick() {
  setCount((c) => c + 1)
  setFlag(true)
  // React 18: ONE re-render
}
 
fetch('/api').then(() => {
  setData(result)
  setLoading(false)
  // Also batched in React 18
})

Back to index


346. Render Phase vs Commit Phase

Theory

Render PhaseCommit Phase
Interruptible?Yes (concurrent)No — synchronous
What happensCall components, run hooks, diff treeApply DOM mutations
EffectsMark placement/update/deletion flagsRun useLayoutEffect
After—Browser paints → useEffect

Interview Answer

Render phase calls components and diffs the tree — it can be interrupted. Commit phase applies DOM changes synchronously, runs layout effects, then the browser paints, then passive effects.

Real Example

plaintext
User clicks → setState
    ↓
RENDER PHASE (interruptible)
  - Call component function
  - useState returns new value
  - useMemo recalculates
  - Diff old vs new tree
    ↓
COMMIT PHASE (sync)
  - Apply DOM insert/update/delete
  - useLayoutEffect fires
    ↓
Browser paints screen
    ↓
useEffect fires (passive)

Back to index


347. Round-by-round prep strategy

Theory

a fintech app spreads rounds across different days. Use the gap to fix what went wrong.

RoundFocusIf you fail, fix before next
1Browser internals, closures, projectsWrite debounce from memory, CSS render blocking
2Vanilla JS live searchBuild autocomplete without React, event loop drills
3React form state designKYC form with PAN/Aadhaar validation, RHF
4Cultural fitReflect on feedback, prepare honest weakness story
5(Final)Usually offer/higher-level — review all weak areas

Checklist before a fintech app

  • Write cancellable debounce from scratch in 5 minutes
  • Build vanilla JS search with keyboard nav + click outside
  • Explain defer vs async with timeline diagram
  • Explain CSS render blocking + critical CSS
  • KYC form with validation — state design on whiteboard first
  • Context re-render problem + 3 fixes
  • SSR vs ISR trade-offs for a fintech app product examples
  • Closure leak example + fix ready
  • "Why a fintech app" answer — specific, not generic
  • 2 project deep-dive stories with "what I'd do differently"
TopicOne-liner
Bubbling vs delegationMechanism vs pattern; delegation uses bubbling
defer vs asyncNeither blocks download; async exec can interrupt parse; defer waits for parse end
CSS blocks renderNeeds CSSOM for render tree — prevents FOUC
Cancellable debounceclearTimeout + .cancel() + .flush()
Closure leakInner fn holds large outer ref — cleanup/remove listener
Vanilla searchDebounce + AbortController + keydown + mousedown outside
ReconciliationSame type update; diff type replace; keys for lists
50 + contextAll consumers re-render — split context or selectors
Event loopSync → all microtasks → one macrotask
SSR/CSR/ISRFresh vs fast vs SEO — a fintech app uses all three
Form stateRHF for 10+ fields; reset dependents on parent change
Cultural fitHonest weakness + action plan, no defensiveness

Back to index


348. Scope & Scope Chain

Theory

Scope determines where variables are accessible. JavaScript has:

  • Global scope — accessible everywhere (window in browsers)
  • Function scope — var is limited to the function
  • Block scope — let/const limited to {} blocks
  • Module scope — ES modules have their own scope

The scope chain is the path JavaScript follows to look up a variable: current scope → outer scope → ... → global. Inner scopes can access outer variables; outer scopes cannot access inner ones (lexical scoping).

Pros & Cons

Block scope (let/const)Function scope (var)
✅ Variables contained to blocks❌ Variables leak from if/for blocks
✅ Fewer naming collisions❌ Harder to reason about
✅ TDZ catches early access—

Real-Life Example

javascript
const API_URL = 'https://api.example.com' // global/module scope
 
function fetchOrders(userId) {
  const endpoint = `/users/${userId}/orders` // function scope
 
  if (userId) {
    let page = 1 // block scope
    const limit = 20
 
    while (page <= 5) {
      let cursor = page * limit // block scope per iteration (let)
      fetch(`${API_URL}${endpoint}?page=${page}&limit=${limit}`)
      page++
    }
    // console.log(cursor); // ReferenceError — block scoped
  }
  // console.log(page); // ReferenceError
  return endpoint
}
 
// Scope chain lookup
const tax = 0.05 // outer
function calculate(price) {
  const discount = 0.1 // middle
  function applyDiscount() {
    return price - price * discount + price * tax
    // applyDiscount looks up: own scope → calculate → global
  }
  return applyDiscount()
}
javascript
// Module scope — variables don't pollute global
// orderService.js
const BASE_URL = import.meta.env.VITE_API_URL // module-private
export function getOrders() {
  return fetch(`${BASE_URL}/orders`)
}

Back to index


349. Server-Side Rendering techniques to improve SEO

Theory

Search engine crawlers need meaningful HTML content to index pages. SSR techniques ensure crawlers see full content without executing JavaScript.

Key techniques: SSR, SSG (Static Site Generation), ISR (Incremental Static Regeneration), dynamic rendering, structured data, meta tags.

Pros & Cons

TechniqueProsCons
SSRAlways fresh, SEO-friendlyServer load on every request
SSGFastest, cheapest hostingStale until rebuild
ISRFresh + fastComplexity, cache invalidation
Dynamic renderingServes bots HTML, users JSCloaking risk if misused

Real-Life Example

jsx
// Next.js — SSR with meta tags for SEO
export async function generateMetadata({ params }) {
  const restaurant = await fetchRestaurant(params.id)
  return {
    title: `${restaurant.name} — Order Online | a food-delivery platform`,
    description: restaurant.cuisine + ' restaurant in ' + restaurant.area,
    openGraph: {
      images: [restaurant.image],
    },
  }
}
 
// Structured data for Google rich results
function RestaurantPage({ restaurant }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Restaurant',
    name: restaurant.name,
    address: restaurant.address,
    aggregateRating: {
      '@type': 'AggregateRating',
      ratingValue: restaurant.rating,
      reviewCount: restaurant.reviewCount,
    },
  }
 
  return (
    <>
      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
      <h1>{restaurant.name}</h1>
      {/* Full content in HTML — visible to crawlers */}
    </>
  )
}

Back to index


350. Session Storage vs Local Storage

Theory

Both are Web Storage APIs storing key-value pairs in the browser. Neither is sent with HTTP requests (unlike cookies).

localStoragesessionStorage
LifetimeUntil explicitly clearedUntil tab/window closes
ScopeSame origin, all tabsSame origin, single tab
Capacity~5–10 MB~5–10 MB
Shared across tabsYesNo
Survives refreshYesYes (same tab)
Use forPreferences, draftsForm data, single-session state

Pros & Cons

localStoragesessionStorage
✅ Persists across sessions✅ Auto-clears on tab close — safer for sensitive temp data
❌ Stays until cleared — stale data risk❌ Lost when tab closes
❌ XSS can read it❌ XSS can read it

Never store auth tokens in either — use httpOnly cookies.

Interview Answer

localStorage persists until cleared and is shared across tabs. sessionStorage lasts only for the tab session. I use localStorage for user preferences and sessionStorage for temporary form data — never for auth tokens.

Real Example

javascript
// localStorage — user preferences (persist forever)
localStorage.setItem('theme', 'dark')
localStorage.setItem('language', 'en')
localStorage.setItem('recentSearches', JSON.stringify(['react', 'redux']))
 
// sessionStorage — multi-step form (cleared when tab closes)
sessionStorage.setItem('checkoutStep', '2')
sessionStorage.setItem('draftOrder', JSON.stringify(cartItems))
 
// Tab behavior demo
// Tab A: localStorage.setItem("x", "1") → Tab B can read "1"
// Tab A: sessionStorage.setItem("y", "2") → Tab B CANNOT read "y"
tsx
// Custom hook
function useSessionStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = sessionStorage.getItem(key)
    return stored ? JSON.parse(stored) : initial
  })
  useEffect(() => {
    sessionStorage.setItem(key, JSON.stringify(value))
  }, [key, value])
  return [value, setValue] as const
}

Back to index


351. Stopwatch Implementation

Theory

Build a stopwatch with Start, Stop, Reset and a live timer display. Use setInterval for ticking, store elapsed time, handle pause/resume by tracking offset.

Key: cleanup interval on stop/unmount; don't drift by recalculating from Date.now().

Interview Answer

I track start timestamp and accumulated elapsed time. Start begins an interval updating display; Stop clears interval and saves elapsed; Reset zeroes everything.

Vanilla JS Implementation

html
<div class="stopwatch">
  <span id="display">00:00:00</span>
  <button id="start">Start</button>
  <button id="stop" disabled>Stop</button>
  <button id="reset" disabled>Reset</button>
</div>
javascript
class Stopwatch {
  constructor(displayEl) {
    this.display = displayEl
    this.elapsed = 0
    this.startTime = 0
    this.timerId = null
    this.running = false
  }
 
  format(ms) {
    const totalSec = Math.floor(ms / 1000)
    const h = String(Math.floor(totalSec / 3600)).padStart(2, '0')
    const m = String(Math.floor((totalSec % 3600) / 60)).padStart(2, '0')
    const s = String(totalSec % 60).padStart(2, '0')
    return `${h}:${m}:${s}`
  }
 
  tick() {
    const now = Date.now()
    this.display.textContent = this.format(this.elapsed + (now - this.startTime))
  }
 
  start() {
    if (this.running) return
    this.running = true
    this.startTime = Date.now()
    this.timerId = setInterval(() => this.tick(), 10) // update every 10ms
  }
 
  stop() {
    if (!this.running) return
    this.running = false
    this.elapsed += Date.now() - this.startTime
    clearInterval(this.timerId)
    this.timerId = null
    this.tick() // final display
  }
 
  reset() {
    this.stop()
    this.elapsed = 0
    this.display.textContent = '00:00:00'
  }
}
 
const sw = new Stopwatch(document.getElementById('display'))
document.getElementById('start').onclick = () => sw.start()
document.getElementById('stop').onclick = () => sw.stop()
document.getElementById('reset').onclick = () => sw.reset()

React Implementation

tsx
function Stopwatch() {
  const [elapsed, setElapsed] = useState(0)
  const [running, setRunning] = useState(false)
  const startTimeRef = useRef(0)
  const accumulatedRef = useRef(0)
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
 
  const format = (ms: number) => {
    const sec = Math.floor(ms / 1000)
    const h = String(Math.floor(sec / 3600)).padStart(2, '0')
    const m = String(Math.floor((sec % 3600) / 60)).padStart(2, '0')
    const s = String(sec % 60).padStart(2, '0')
    return `${h}:${m}:${s}`
  }
 
  const start = () => {
    if (running) return
    setRunning(true)
    startTimeRef.current = Date.now()
    intervalRef.current = setInterval(() => {
      setElapsed(accumulatedRef.current + (Date.now() - startTimeRef.current))
    }, 10)
  }
 
  const stop = () => {
    if (!running) return
    accumulatedRef.current += Date.now() - startTimeRef.current
    clearInterval(intervalRef.current!)
    intervalRef.current = null
    setRunning(false)
  }
 
  const reset = () => {
    stop()
    accumulatedRef.current = 0
    setElapsed(0)
  }
 
  useEffect(() => () => clearInterval(intervalRef.current!), [])
 
  return (
    <div>
      <span aria-live="polite">{format(elapsed)}</span>
      <button onClick={start} disabled={running}>
        Start
      </button>
      <button onClick={stop} disabled={!running}>
        Stop
      </button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

Back to index


352. Strict Mode

Theory

<StrictMode> is a development-only wrapper that double-invokes certain functions (render, effects, state initializers) to expose side effects and impure code. It does not run in production.

Helps catch: missing cleanup, unsafe lifecycles, deprecated APIs.

Pros & Cons

ProsCons
Catches bugs earlyDouble effects confuse beginners
Prepares for Concurrent ReactNot a bug — intentional dev behavior

Interview Answer

Strict Mode is a dev-only tool that double-runs renders and effects to expose impure code and missing cleanups. It has no effect in production builds.

Real Example

jsx
// main.jsx
ReactDOM.createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
 
// useEffect runs twice in dev — if you see duplicate API calls,
// your cleanup is missing or effect has no abort logic

Back to index


353. Sum of Numbers without a for Loop

Theory

Sum an array without for/while using:

  • reduce() — most idiomatic
  • Recursion — classic interview approach
  • eval + join — trick (avoid in production)

Interview Answer

I use reduce — it accumulates a sum without an explicit loop. Recursion works too by adding first element to sum of rest.

Implementations

javascript
const nums = [1, 2, 3, 4, 5]
 
// Approach 1 — reduce (preferred)
const sumReduce = (arr) => arr.reduce((acc, n) => acc + n, 0)
console.log(sumReduce(nums)) // 15
 
// Approach 2 — recursion
function sumRecursive(arr) {
  if (arr.length === 0) return 0
  return arr[0] + sumRecursive(arr.slice(1))
}
console.log(sumRecursive(nums)) // 15
 
// Approach 3 — reduce without initial (only for non-empty)
const sumReduceNoInit = (arr) => arr.reduce((a, b) => a + b)
console.log(sumReduceNoInit(nums)) // 15
 
// Sum of arguments without loop
function sumArgs(...args) {
  return args.reduce((a, b) => a + b, 0)
}
console.log(sumArgs(10, 20, 30)) // 60

Back to index


354. Team Collaboration & Conflict Resolution

Common questions

  • Disagreement with a senior engineer on architecture?
  • Missed deadline — what did you do?
  • Code review feedback that felt unfair?
  • Working with backend/design on conflicting priorities?

STAR example — Technical disagreement

text
Situation: Team split on Redux vs React Query for server state.
Task:      Unblock sprint planning without endless debate.
Action:    Proposed a 1-week spike — same feature both ways; measured bundle size,
           lines of code, and cache behavior. Documented in a short ADR.
           Team voted with data; chose React Query + Zustand for UI state.
Result:    Decision in 3 days; team bought in; pattern documented for other squads.

Conflict principles the organization cares about

  • Professional services mindset — client delivery, clarity, documentation
  • Escalate early with facts, not emotion
  • Assume positive intent in code review
  • Write it down — ADR, RFC, meeting notes

Interview Answer

I resolve technical conflicts with data and time-boxed experiments, document decisions in ADRs, and escalate early when deadlines or quality are at risk — always separating people from problems.

Back to index


355. Technical weaknesses

Theory

a fintech app's Tech Lead opens with feedback from all 3 previous rounds and asks where you're weak. They watch for honesty, self-awareness, and action — not perfection.

Pros & Cons of answer approaches

DoDon't
Name a real gap with a learning plan"I don't have weaknesses"
Reference something Round 1-3 exposedBlame interviewers or questions
Show progress already madeBe defensive about feedback
Connect weakness to growthList fake weaknesses ("I work too hard")

Real-Life Example — Answer skeleton

Note

Round 2 feedback showed my vanilla JS DOM skills were rusty — I default to React too quickly. Since then I've rebuilt two components in plain JS: an autocomplete and an infinite scroll list. I also spent time on the WHATWG event model and can now explain delegation vs bubbling with concrete examples.

Note

My ongoing gap is system design for frontend at scale — I've worked on features but not owned full architecture. I'm reading through "Frontend Architecture" patterns and built a small monorepo side project with Module Federation to practice.

Back to index


356. Tree Shaking

Theory

Tree shaking is dead code elimination — the bundler removes exported modules that are never imported. Requires ES modules (import/export) and side-effect-free packages.

Works at build time. Mark packages as side-effect-free in package.json:

json
{ "sideEffects": false }

Pros & Cons

ProsCons
Automatic size reductionOnly works with ESM
No runtime costCommonJS modules can't be tree-shaken
Encourages modular librariesSide effects prevent shaking

Real-Life Example

javascript
// utils/math.js
export function add(a, b) {
  return a + b
}
export function multiply(a, b) {
  return a * b
}
export function divide(a, b) {
  return a / b
}
 
// app.js — only imports add
import { add } from './utils/math'
console.log(add(2, 3))
 
// After tree shaking: multiply and divide removed from bundle
javascript
// ❌ CommonJS — no tree shaking
const _ = require("lodash");
 
// ✅ ESM — tree shakeable
import debounce from "lodash-es/debounce";
 
// package.json of your library
{
  "sideEffects": ["*.css"], // only CSS has side effects
  "module": "dist/index.esm.js"
}

Back to index


357. Two Sum Problem

Theory

Given an array and a target, return indices of two numbers that add up to target.

Approach 1 — Brute force O(n²)

javascript
function twoSumBrute(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) return [i, j]
    }
  }
  return []
}

Approach 2 — Hash map O(n) ✅

javascript
function twoSum(nums, target) {
  const seen = new Map() // value → index
 
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i]
 
    if (seen.has(complement)) {
      return [seen.get(complement), i]
    }
 
    seen.set(nums[i], i)
  }
 
  return []
}
 
console.log(twoSum([2, 7, 11, 15], 9)) // [0, 1]
console.log(twoSum([3, 2, 4], 6)) // [1, 2]

C++ version

cpp
#include <vector>
#include <unordered_map>
 
std::vector<int> twoSum(std::vector<int>& nums, int target) {
  std::unordered_map<int, int> seen;
 
  for (int i = 0; i < nums.size(); i++) {
    int complement = target - nums[i];
    if (seen.count(complement)) {
      return {seen[complement], i};
    }
    seen[nums[i]] = i;
  }
  return {};
}

Back to index


358. Type Coercion

Theory

Type coercion is JavaScript automatically converting one data type to another during operations. It happens with ==, +, comparison operators, and logical operators.

  • Implicit coercion — JS converts automatically ("5" + 1 → "51")
  • Explicit coercion — you convert intentionally (Number("5"), String(42))

=== does not coerce — always prefer it over ==.

Pros & Cons

Implicit coercionStrict equality (===)
✅ Flexible, forgiving✅ Predictable, no surprises
❌ "0" == false is true❌ Must convert types yourself
❌ Source of countless bugs✅ Industry standard

Real-Life Example

javascript
// == coercion rules (avoid ==)
0 == false // true
'' == false // true
null == undefined // true
'5' == 5 // true
;[] == false // true
![] == false // false (quirky)
 
// === — no coercion
0 === false // false
'5' === 5 // false
 
// + operator coercion
'5' + 1 // "51" (string concat)
'5' - 1 // 4 (string → number)
true + true // 2
;[] + [] // "" (both toString to "")
 
// Explicit conversion — preferred
Number('42') // 42
Number('') // 0
Number('abc') // NaN
parseInt('42px') // 42
String(42) // "42"
Boolean('') // false
Boolean('hello') // true
!!value // common boolean cast
 
// Falsy values (all coerce to false)
// false, 0, -0, 0n, "", null, undefined, NaN
 
// Nullish coalescing — only null/undefined
const port = config.port ?? 3000 // 0 is valid, unlike ||
const name = user.name ?? 'Guest'
javascript
// Real-life: Safe number from form input
function getQuantity(input) {
  const qty = Number(input)
  return Number.isFinite(qty) && qty > 0 ? qty : 1
}

Back to index


359. Types of Objects & How to Define Them

Theory

TypeHow to createExample
Object literal{}{ name: "Amit" }
Constructornew Object()Rarely used
Constructor functionnew Person()Pre-ES6 classes
Classclass Person {}ES6 syntactic sugar
Object.createObject.create(proto)Prototypal inheritance
Factory functionFunction returning objectClosure-based
Mapnew Map()Any key type
Setnew Set()Unique values
Array[]typeof "object"
Functionfunction(){}Callable object
Date, RegExp, ErrorBuilt-in constructorsSpecialised objects

Interview Answer

I define objects with literals for simple data, classes for reusable types, Object.create for prototypal inheritance, and Map/Set when keys aren't strings or I need uniqueness.

Real Example

javascript
// Literal
const config = { apiUrl: '/api', timeout: 5000 }
 
// Class
class Transaction {
  constructor(amount, type) {
    this.amount = amount
    this.type = type
  }
  getFormatted() {
    return `₹${this.amount}`
  }
}
 
// Factory with closure
function createUser(name) {
  let loginCount = 0
  return {
    name,
    login() {
      loginCount++
    },
    getLoginCount: () => loginCount,
  }
}
 
// Object.create — inherit prototype
const animal = {
  breathe() {
    return 'breathing'
  },
}
const dog = Object.create(animal)
dog.bark = () => 'woof'
 
// Map — non-string keys
const cache = new Map()
cache.set({ id: 1 }, { data: '...' })

Back to index


360. Unfamiliar codebase

Theory

They want your onboarding process — how you become productive without breaking things.

Real-Life Example — Answer skeleton

Warning

Week 1 — Observe: Clone, run locally, read README and ADRs. Don't change code. Trace one user flow end-to-end in debugger (login → payment). Map folder structure and ask "why" in 1:1s.

Note

Week 2 — Small wins: Fix a good-first-issue bug. Add a test to understand testing patterns. Pair with someone on a small PR.

Tip

Ongoing: Keep a personal doc of "gotchas" (env vars, feature flags, deploy process). Prefer reading existing code over introducing new patterns until I understand conventions.

Note

Tools: Source graph / IDE find references, git blame for context, React DevTools for component tree, Redux DevTools if applicable.

Back to index


361. Use environment variables for configuration values

Theory

Environment variables store configuration that changes between environments (development, staging, production) — API URLs, feature flags, public keys. Never hardcode URLs or commit secrets to git.

In Vite: VITE_ prefix. In Create React App: REACT_APP_ prefix. Only variables with these prefixes are exposed to the browser bundle.

Pros & Cons

Environment variablesHardcoded values
✅ Different config per environment❌ Must change code to deploy
✅ No secrets in source code❌ Risk of committing API keys
✅ CI/CD friendly❌ Same build can't serve multiple envs
❌ Public in bundled JS (VITE_ vars)—

Real-Life Example

bash
VITE_API_URL=http://localhost:3001/api
VITE_ENABLE_MOCK=true
VITE_APP_NAME=MyApp (Dev)
 
# .env.production
VITE_API_URL=https://api.production.com
VITE_ENABLE_MOCK=false
VITE_APP_NAME=MyApp
typescript
// config/env.ts — typed, centralized
export const env = {
  apiUrl: import.meta.env.VITE_API_URL as string,
  enableMock: import.meta.env.VITE_ENABLE_MOCK === 'true',
  appName: import.meta.env.VITE_APP_NAME as string,
} as const
 
// Usage
const apiClient = axios.create({ baseURL: env.apiUrl })
gitignore
# .gitignore — NEVER commit these
.env
.env.local
.env.production

Rules:

  • ✅ VITE_API_URL, VITE_GOOGLE_CLIENT_ID (public)
  • ❌ VITE_STRIPE_SECRET_KEY, VITE_DB_PASSWORD (secrets belong on server only)

Back to index


362. useContext Hook

Theory

useContext reads the nearest value from a Context Provider. It avoids passing props through every level — any descendant can access shared data directly.

Pros & Cons

ProsCons
No prop drillingAll consumers re-render on value change
Clean APINot for frequently changing data

Interview Answer

useContext lets any component read shared data from a Provider without prop drilling — I use it for theme, auth, or locale.

Real Example

jsx
const ThemeContext = createContext('light')
 
function App() {
  const [theme, setTheme] = useState('dark')
  return (
    <ThemeContext.Provider value={theme}>
      <Header />
    </ThemeContext.Provider>
  )
}
 
function Header() {
  const theme = useContext(ThemeContext)
  return <header className={`header-${theme}`}>Logo</header>
}

Back to index


363. useDeferredValue Hook

Theory

useDeferredValue(value) returns a deferred version of a value that may lag behind during urgent updates. React treats updating the deferred value as low priority — keeps the UI responsive during expensive re-renders.

Part of Concurrent React (React 18+). Pairs with slow lists, search filters, heavy charts.

Pros & Cons

ProsCons
Keeps input responsiveShows stale data briefly
No manual transition APIOnly helps render performance
Built-in, declarativeNeeds expensive child to matter

Interview Answer

useDeferredValue delays updating a value until urgent work finishes — I use it when typing in search should stay smooth while a heavy list re-filters in the background.

Real Example

jsx
import { useState, useDeferredValue, memo } from 'react'
 
const SlowList = memo(function SlowList({ query }) {
  const items = filterHugeList(allItems, query) // expensive
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
})
 
function SearchPage() {
  const [query, setQuery] = useState('')
  const deferredQuery = useDeferredValue(query)
  const isStale = query !== deferredQuery
 
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search products..." />
      <div style={{ opacity: isStale ? 0.6 : 1 }}>
        <SlowList query={deferredQuery} />
      </div>
    </>
  )
}

useDeferredValue vs useTransition

useDeferredValueuseTransition
DeferA valueA state update
APIuseDeferredValue(q)startTransition(() => setQ())
Pending flagCompare value !== deferredisPending boolean
Use whenValue passed to childYou control the setState
jsx
// useTransition alternative
const [isPending, startTransition] = useTransition()
 
const handleChange = (e) => {
  setInput(e.target.value) // urgent
  startTransition(() => setQuery(e.target.value)) // deferred
}

Back to index


364. useLayoutEffect Hook

Theory

useLayoutEffect runs synchronously after DOM mutations but before the browser paints. Same signature as useEffect but fires earlier.

Use when: you need to measure DOM or mutate layout before user sees the screen — prevents visual flicker.

Use useEffect for: everything else — data fetching, subscriptions (doesn't block paint).

Comparison

useEffectuseLayoutEffect
RunsAfter paintBefore paint
Blocks paint?NoYes
Use forAPI calls, subscriptionsDOM measurements, sync layout
SSR warning?NoYes — server has no layout

Interview Answer

useLayoutEffect runs before the browser paints — I use it when I need to measure DOM or fix layout to prevent flicker. For data fetching and subscriptions, I use useEffect.

Real Example

tsx
// ❌ useEffect — tooltip flickers at (0,0) then jumps
function Tooltip({ targetRef }) {
  const [pos, setPos] = useState({ x: 0, y: 0 })
  useEffect(() => {
    const rect = targetRef.current.getBoundingClientRect()
    setPos({ x: rect.left, y: rect.bottom })
  }, [targetRef])
  return <div style={{ left: pos.x, top: pos.y }}>Tip</div>
}
 
// ✅ useLayoutEffect — position calculated before paint
function Tooltip({ targetRef }) {
  const [pos, setPos] = useState({ x: 0, y: 0 })
  useLayoutEffect(() => {
    const rect = targetRef.current.getBoundingClientRect()
    setPos({ x: rect.left, y: rect.bottom })
  }, [targetRef])
  return <div style={{ left: pos.x, top: pos.y }}>Tip</div>
}
tsx
// Other valid uses: focus on mount, scroll to element, animate from measured size
useLayoutEffect(() => {
  inputRef.current?.focus()
}, [])

Back to index


365. useTransition & useDeferredValue

Theory

  • useTransition — mark state updates as non-urgent (low priority). Returns [isPending, startTransition].
  • useDeferredValue — defer re-rendering a value until urgent updates finish.

Both keep UI responsive during heavy updates (filtering large lists, tab switches).

Interview Answer

useTransition marks updates as low priority so input stays responsive. useDeferredValue delays rendering stale data until the UI catches up.

Real Example

jsx
function SearchResults({ query }) {
  const [isPending, startTransition] = useTransition()
  const [results, setResults] = useState(allItems)
 
  const handleChange = (q) => {
    setInput(q)
    startTransition(() => setResults(filterItems(allItems, q)))
  }
 
  return (
    <>
      <input value={input} onChange={(e) => handleChange(e.target.value)} />
      {isPending && <Spinner />}
      <List items={results} />
    </>
  )
}

Back to index


366. Web Accessibility and Semantic Elements

Theory

Web Accessibility (a11y) ensures apps are usable by everyone — screen readers, keyboard users, motor impairments. Semantic HTML uses meaningful tags (<nav>, <main>, <article>) instead of generic <div>.

WCAG principles: Perceivable, Operable, Understandable, Robust.

Semantic elements

ElementPurpose
<header>Page/section header
<nav>Navigation links
<main>Primary content (one per page)
<article>Self-contained content
<section>Thematic grouping
<aside>Sidebar, related content
<footer>Footer info
<button>Actions (not <div onClick>)

Interview Answer

Accessibility means building for all users — semantic HTML, ARIA labels, keyboard navigation, and color contrast. Semantic elements give meaning to screen readers and improve SEO.

Real Example

jsx
// ❌ Inaccessible
<div onClick={submit}>Submit</div>
<div className="nav">...</div>
 
// ✅ Accessible
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/dashboard">Dashboard</a></li>
    </ul>
  </nav>
</header>
<main id="main-content">
  <article>
    <h1>Transaction History</h1>
    <button type="submit" onClick={submit}>Submit</button>
  </article>
</main>

Back to index


367. What are microservices?

Theory

Microservices is an architectural style where an application is built as a collection of small, independently deployable services, each owning a specific business capability. Services communicate via HTTP/REST, gRPC, or message queues.

Each service has its own database, codebase, and deployment pipeline.

Pros & Cons

ProsCons
Independent scaling per serviceDistributed system complexity
Team autonomy per serviceNetwork latency between services
Technology diversityData consistency challenges
Fault isolationOperational overhead (monitoring, logging)

Real-Life Example

plaintext
Food delivery platform microservices:
 
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  Restaurant  │  │   Order     │  │  Payment    │
│  Service     │  │   Service   │  │  Service    │
│  (menus,     │  │  (cart,     │  │  (Razorpay, │
│   search)    │  │   tracking) │  │   refunds)  │
└──────┬───────┘  └──────┬──────┘  └──────┬──────┘
       │                 │                │
       └──────── API Gateway ─────────────┘
                         │
                    Frontend App
                    (calls gateway,
                     not individual services)

Frontend impact: BFF (Backend for Frontend) aggregates multiple microservice calls into one API tailored for the UI.

Back to index


368. What are Microtasks and Macrotasks?

Theory

The event loop processes two types of async callbacks in strict priority:

Microtasks (higher priority — all drained before next macrotask):

  • Promise.then / catch / finally
  • queueMicrotask()
  • MutationObserver
  • await (resumes as microtask)

Macrotasks (lower priority — one per loop iteration):

  • setTimeout / setInterval
  • setImmediate (Node.js)
  • I/O callbacks
  • UI rendering events (click, scroll)
  • requestAnimationFrame (before paint, separate queue)

Pros & Cons

MicrotasksMacrotasks
✅ Run immediately after current sync code✅ Yield to browser rendering
✅ Fast promise resolution✅ Better for deferring heavy work
❌ Can starve macrotasks if infinite chain❌ Slower than microtasks

Real-Life Example

javascript
console.log('1: sync start')
 
setTimeout(() => console.log('5: macrotask — setTimeout'), 0)
 
Promise.resolve()
  .then(() => console.log('3: microtask 1'))
  .then(() => console.log('4: microtask 2'))
 
queueMicrotask(() => console.log('3b: microtask 3'))
 
console.log('2: sync end')
 
// Output: 1 → 2 → 3 → 3b → 4 → 5
javascript
// Microtask starvation — avoid in production
function poisonLoop() {
  Promise.resolve().then(poisonLoop) // blocks setTimeout and UI updates forever
}
// poisonLoop(); // never do this
jsx
// Practical React pattern
function CheckoutButton() {
  const handleClick = async () => {
    console.log('1: click handler sync')
 
    await processPayment() // yields — microtask resumes after promise resolves
    console.log('3: after await')
 
    setTimeout(() => console.log('5: redirect'), 1000)
  }
 
  return <button onClick={handleClick}>Pay Now</button>
}
// Click output: 1 → (payment API) → 3 → (1 second) → 5

Microtask vs Macrotask — when to use which

Use microtask (Promise, queueMicrotask)Use macrotask (setTimeout, requestAnimationFrame)
Chain async operationsDefer work to next frame
Process promise resultsAllow browser to paint between chunks
DOM measurements after state updateBreak up long computations
javascript
// Break heavy work across macrotasks to keep UI responsive
function processInChunks(items, chunkSize = 100) {
  let index = 0
 
  function processChunk() {
    const end = Math.min(index + chunkSize, items.length)
    for (; index < end; index++) processItem(items[index])
 
    if (index < items.length) {
      setTimeout(processChunk, 0) // macrotask — browser can paint between chunks
    }
  }
 
  processChunk()
}
AreaKey Topics in This Guide
JavaScript coreEvent Loop, Hoisting, Micro/Macrotasks, map, bind, second largest
React Hooks & patternsuseEffect, useCallback, useMemo, HOC, Props Drilling
Routing & APIReact Router v6, Axios interceptors, protected routes
Frontend securityXSS, token storage, CSP, CSRF, env vars, npm audit
PerformanceDebounce, Throttle, useMemo, chunked processing
Logic questionsSecond largest element, event loop output prediction

Quick Revision Cheat Sheet

#TopicOne-liner
1Event LoopSync → all microtasks → one macrotask → repeat
2Second largestOne-pass with largest + secondLargest variables, O(n)
3Debounce vs ThrottleWait for pause vs once per interval
4map vs bindTransform array vs fix this on function
5AxiosHTTP client with interceptors, JSON, cancellation
6React RouterBrowserRouter → Routes → Route; useParams, protected routes
7Frontend securityXSS sanitize, httpOnly cookies, no secrets in env, HTTPS
8HooksuseEffect = side effects; useMemo = value; useCallback = function
9HOCWrap component for reuse; prefer custom hooks today
10Hoistingvar/function hoisted; let/const in TDZ until declared
11Props DrillingPass through unused levels; fix with Context/composition
12Event BubblingTarget → parents; enables delegation
13Micro vs MacroPromises first, then setTimeout; one macro per loop

Back to index


369. What are Smart Pointers?

Theory

Smart pointers are RAII wrappers around raw pointers that automatically manage memory — they delete the object when no longer needed.

TypeOwnershipCopyable
unique_ptrExclusiveNo
shared_ptrShared (ref count)Yes
weak_ptrNon-owning observerYes
cpp
#include <memory>
 
void demo() {
  // Raw pointer — manual delete, leak-prone
  int* raw = new int(42);
  delete raw;
 
  // Smart pointer — automatic cleanup
  auto smart = std::make_unique<int>(42);
  // deleted when `smart` goes out of scope
}

Back to index


370. What do you do for security purposes in frontend applications?

Theory

Frontend security protects users and data in the browser. While true security enforcement happens on the server, the frontend must prevent common attacks and never trust client-side code alone.

Key areas: XSS, CSRF, authentication, data exposure, dependency security, and secure communication.

Pros & Cons of frontend security layers

PracticeBenefitLimitation
Input sanitizationPrevents XSSServer must also validate
httpOnly cookiesTokens not accessible to JSRequires backend support
CSP headersBlocks inline script injectionCan break third-party scripts
HTTPS onlyEncrypts data in transitDoesn't protect against XSS

Real-Life Example — Security checklist

1. Prevent XSS (Cross-Site Scripting)

tsx
// ❌ Dangerous — renders raw HTML from user/API
<div dangerouslySetInnerHTML={{ __html: userComment }} />
 
// ✅ Safe — React escapes by default
<div>{userComment}</div>
 
// ✅ If HTML needed — sanitize first
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userComment) }} />

2. Secure token storage

javascript
// ❌ Never store tokens in localStorage (accessible to any JS/XSS)
localStorage.setItem('token', accessToken)
 
// ✅ Use httpOnly, Secure, SameSite cookies (set by backend)
// Frontend cannot read it — immune to XSS token theft
// Backend sets: Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

3. Authentication & authorization

tsx
// Frontend checks are UX only — backend MUST enforce
function AdminPanel() {
  const { user } = useAuth()
 
  // UI guard
  if (!user?.roles.includes('admin')) {
    return <Navigate to="/unauthorized" />
  }
 
  return <AdminDashboard />
}
 
// API calls always include auth — backend validates
apiClient.get('/api/admin/users') // 403 if not admin, regardless of UI

4. Environment variables

javascript
// ✅ Public keys only in frontend env
VITE_API_URL=https://api.example.com
VITE_GOOGLE_CLIENT_ID=abc123  // public OAuth client ID — OK
 
// ❌ NEVER expose secrets in frontend
VITE_STRIPE_SECRET_KEY=sk_live_xxx  // visible in bundled JS!

5. Content Security Policy (CSP)

html
<!-- Set via server headers -->
<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />

6. CSRF protection

javascript
// Include CSRF token in state-changing requests
apiClient.post('/api/orders', orderData, {
  headers: { 'X-CSRF-Token': getCsrfTokenFromCookie() },
})

7. Dependency security

bash
npm audit
npm audit fix

8. Other practices

PracticeWhy
HTTPS everywhereEncrypt data in transit
Validate/sanitize all inputsClient + server validation
Rate limit sensitive actionsPrevent brute force on login
Logout clears all session dataPrevent session fixation
Subresource Integrity (SRI)Verify CDN scripts haven't been tampered
Don't log sensitive dataConsole logs visible in production tools

Interview answer (concise)

Warning

Frontend security is defense-in-depth: sanitize inputs against XSS, store tokens in httpOnly cookies not localStorage, never put secrets in env vars shipped to browser, use HTTPS, implement CSP headers, validate on both client and server, and run npm audit in CI. Frontend guards are for UX — backend always enforces authorization.

Back to index


371. What happens during object construction and destruction?

Construction order

plaintext
1. Base class constructors (in declaration order)
2. Member variables (in declaration order, not initializer list order)
3. Constructor body executes
cpp
class Base {
public:
  Base() { std::cout << "Base ctor\n"; }
  ~Base() { std::cout << "Base dtor\n"; }
};
 
class Derived : public Base {
  int a;
  std::string b;
public:
  Derived() : a(1), b("hello") {
    std::cout << "Derived ctor body\n";
  }
  ~Derived() { std::cout << "Derived dtor\n"; }
};
 
// Output on creation:
// Base ctor
// Derived ctor body
 
// Output on destruction (reverse order):
// Derived dtor
// Base dtor

Destruction order (reverse of construction)

  1. Derived destructor body
  2. Derived member destructors (reverse declaration order)
  3. Base class destructor

Virtual destructor rule

cpp
class Base {
public:
  virtual ~Base() = default; // REQUIRED if deleting via base pointer
};
 
class Derived : public Base { /* ... */ };
 
Base* ptr = new Derived();
delete ptr; // calls Derived::~Derived then Base::~Base
// Without virtual ~Base, only Base destructor runs → leak

Back to index


372. What happens internally when a state update is triggered?

Step-by-step internals

javascript
// You write:
setCount(count + 1)
 
// Internally (simplified):
  1. Update object created — { lane: SyncLane, action: (c) => c + 1, eagerState: ... }
  2. Enqueued on Fiber — attached to fiber.updateQueue (circular linked list)
  3. Eager evaluation — React may compute new state immediately for bailout check
  4. Bailout? — If Object.is(oldState, newState), skip scheduling
  5. Schedule work — scheduleUpdateOnFiber(root, fiber, lane)
  6. Render scheduled — requestUpdateLane → ensureRootIsScheduled
  7. Work loop runs — workLoopConcurrent or workLoopSync
  8. Component re-executed — hooks read updated memoizedState
  9. Reconciliation — diff old vs new child elements
  10. Commit — DOM patched if diff has changes

Batching behavior

jsx
function handleClick() {
  setCount((c) => c + 1)
  setFlag((f) => !f)
  // React 18+: both batched → single re-render
}
 
// Also batched in timeouts and native events (React 18+)
setTimeout(() => {
  setCount(1)
  setFlag(true)
}, 1000) // single re-render

flushSync — opt out of batching

jsx
import { flushSync } from 'react-dom'
 
flushSync(() => {
  setCount(1)
})
// DOM updated synchronously here
setFlag(true) // separate render

Back to index


373. What is providesTags and invalidatesTags?

Answer

  • providesTags: Used in queries to tag cached data.
  • invalidatesTags: Used in mutations to tell RTK Query to invalidate and refetch related data.

Back to index


374. What is an event in React?

Answer

Events in React are handled using camelCase syntax and JavaScript functions. They are used to respond to user actions like clicks, typing, or form submissions.

jsx
<button onClick={handleClick}>Click Me</button>

Back to index


375. What is defaultProps?

Answer

defaultProps allows you to set default values for props in class components. It ensures props have a fallback value if not provided.

jsx
MyComponent.defaultProps = {
  name: 'Guest',
}

Back to index


376. What is Event Bubbling?

Theory

When an event fires on a DOM element, it goes through three phases:

  1. Capturing — travels from window down to the target
  2. Target — reaches the element that triggered the event
  3. Bubbling — travels from target back up to window

By default, event listeners run in the bubbling phase. This enables event delegation — attaching one listener on a parent instead of many on children.

event.stopPropagation() stops the event from bubbling further. event.stopImmediatePropagation() also prevents other listeners on the same element.

Pros & Cons

Event BubblingEvent Capturing
✅ Enables delegation — fewer listeners✅ Intercept before children handle
✅ Natural default behavior❌ Less intuitive
❌ Children can unintentionally block parentsUsed with { capture: true }

Real-Life Example

html
<div id="card">
  <button id="add-to-cart">Add to Cart</button>
  <button id="favorite">♥</button>
</div>
javascript
// Bubbling order when "Add to Cart" is clicked:
// 1. button (target)
// 2. div#card (bubbling)
// 3. body → html → window
 
document.getElementById('card').addEventListener('click', (e) => {
  const button = e.target.closest('button')
  if (!button) return
 
  if (button.id === 'add-to-cart') addToCart()
  if (button.id === 'favorite') toggleFavorite()
})
// One listener handles all buttons — works for dynamically added buttons too
jsx
// React — bubbling works the same
function ProductCard({ onAddToCart }) {
  return (
    <div className="card" onClick={() => console.log('card clicked')}>
      <button
        onClick={(e) => {
          e.stopPropagation() // prevent card click from firing
          onAddToCart()
        }}>
        Add to Cart
      </button>
    </div>
  )
}
 
// Modal — stop propagation on content to prevent overlay close
function Modal({ onClose, children }) {
  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>
  )
}

Back to index


377. What is Event Capturing and Bubbling

Theory

When an event fires on a DOM element, it travels in three phases:

  1. Capturing phase — event travels from window → target (top down)
  2. Target phase — event reaches the target element
  3. Bubbling phase — event travels from target → window (bottom up)

By default, event listeners run in the bubbling phase. Use { capture: true } or addEventListener(el, fn, true) for capturing.

event.stopPropagation() stops further propagation. event.stopImmediatePropagation() also prevents other listeners on the same element.

Pros & Cons

CapturingBubbling
ProsIntercept events before children handle themNatural default, event delegation
ConsLess intuitive, rarely neededChildren can block parent handlers
Use caseFocus management, analytics wrappersDelegated click handlers on lists

Real-Life Example

html
<div id="grandparent">
  <div id="parent">
    <button id="child">Click me</button>
  </div>
</div>
javascript
document.getElementById('grandparent').addEventListener('click', () => console.log('grandparent bubble'))
document.getElementById('parent').addEventListener('click', () => console.log('parent bubble'))
document.getElementById('child').addEventListener('click', () => console.log('child target'))
 
// Click button → Output: child target → parent bubble → grandparent bubble
 
// Event delegation (bubbling) — real-life pattern
document.getElementById('menu').addEventListener('click', (e) => {
  const item = e.target.closest('[data-action]')
  if (!item) return
  handleAction(item.dataset.action) // one listener for 100 menu items
})

Back to index


378. What is forwardRef?

Answer

forwardRef is used to pass a ref through a component to one of its children.

jsx
const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />)

Back to index


379. What is Props Drilling?

Theory

Props drilling is passing data through multiple intermediate components that don't need the data themselves, just to reach a deeply nested child. It makes components tightly coupled and hard to maintain.

plaintext
App (user)
 └── Header (passes user)
      └── Navbar (passes user)
           └── UserMenu (passes user)
                └── Avatar (finally uses user) ← 4 levels deep

Pros & Cons of solutions

SolutionProsCons
Props drillingExplicit, easy to traceUnmaintainable at depth
Context APINo intermediate passingAll consumers re-render on change
State library (Zustand/Redux)Scalable, devtoolsSetup overhead
Component compositionClean, no extra librariesOnly works for specific slots

Real-Life Example

The problem

tsx
// ❌ Props drilling — theme passed through 4 levels
function App() {
  const [theme, setTheme] = useState('dark')
  return <Layout theme={theme} setTheme={setTheme} />
}
 
function Layout({ theme, setTheme }) {
  return <Sidebar theme={theme} setTheme={setTheme} />
}
 
function Sidebar({ theme, setTheme }) {
  return <ThemeToggle theme={theme} setTheme={setTheme} />
}
 
function ThemeToggle({ theme, setTheme }) {
  return <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>{theme}</button>
}

Solution 1 — Context API

tsx
const ThemeContext = createContext()
 
function App() {
  const [theme, setTheme] = useState('dark')
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  )
}
 
function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext)
  return <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>{theme}</button>
}

Solution 2 — Component composition

tsx
function App() {
  const [theme, setTheme] = useState('dark')
  return <Layout sidebar={<ThemeToggle theme={theme} setTheme={setTheme} />} />
}
 
function Layout({ sidebar }) {
  return (
    <div>
      <aside>{sidebar}</aside> {/* no drilling — direct slot */}
      <main>...</main>
    </div>
  )
}

Solution 3 — Zustand (scalable)

tsx
import { create } from 'zustand'
 
const useThemeStore = create((set) => ({
  theme: 'dark',
  toggle: () => set((s) => ({ theme: s.theme === 'dark' ? 'light' : 'dark' })),
}))
 
function ThemeToggle() {
  const { theme, toggle } = useThemeStore()
  return <button onClick={toggle}>{theme}</button>
}

Interview answer

Warning

Props drilling is passing props through components that don't use them. Fix with Context for theme/auth, composition for layout slots, or Zustand/Redux for complex global state. One-to-two levels of prop passing is fine — don't over-engineer.

Back to index


380. What is RAII?

Theory

Resource Acquisition Is Initialization — bind resource lifetime to object lifetime. Acquire in constructor, release in destructor.

cpp
class FileHandle {
  FILE* file;
 
public:
  FileHandle(const char* path) : file(fopen(path, "r")) {
    if (!file) throw std::runtime_error("Cannot open file");
  }
 
  ~FileHandle() {
    if (file) fclose(file); // always called, even on exception
  }
 
  // delete copy, allow move...
};
 
void readConfig() {
  FileHandle f("config.txt");
  // use f...
} // destructor closes file automatically — no leak

RAII in standard library

ResourceRAII wrapper
Memoryunique_ptr, shared_ptr
Filefstream
Mutexlock_guard, unique_lock
Socketcustom RAII class

Interview answer

Note

RAII ties resource management to scope. Constructors acquire, destructors release. This guarantees cleanup even when exceptions are thrown — the core idea behind smart pointers and lock_guard.

Back to index


381. What is Temporal Dead Zone (TDZ)?

Theory

The Temporal Dead Zone is the period between entering a scope and the line where a let or const variable is declared. During TDZ, the variable exists in the lexical environment but is not initialized — accessing it throws ReferenceError.

var does not have a TDZ — it's initialized as undefined when hoisted.

TDZ exists to:

  • Catch errors from using variables before declaration
  • Make let/const behave more predictably than var
  • Prevent issues with class and const before initialization

Pros & Cons

TDZ (let/const)No TDZ (var)
✅ Catches bugs early❌ Returns undefined silently
✅ Enforces declare-before-use❌ Hoisting confusion
❌ ReferenceError if accessed early✅ "Forgiving" but dangerous

Real-Life Example

javascript
// TDZ demonstration
{
  // TDZ for `value` starts here (scope entry)
  // console.log(value); // ReferenceError — in TDZ
 
  let value = 42 // TDZ ends here
  console.log(value) // 42
}
 
// typeof and TDZ
typeof undeclaredVar // "undefined" (safe — no ReferenceError)
// typeof tdzVar;         // ReferenceError — tdzVar is in TDZ
let tdzVar = 10
 
// TDZ with const
const API_KEY = import.meta.env.VITE_API_KEY
// Must be declared before use — cannot access before line
 
// Class TDZ
// const instance = new Service(); // ReferenceError — class in TDZ
class Service {
  connect() {
    return 'connected'
  }
}
const instance = new Service() // OK
javascript
// Why TDZ matters — prevents this bug with var
function init() {
  console.log(config) // undefined (var) — silent bug
  var config = { debug: true }
}
 
function initSafe() {
  // console.log(config); // ReferenceError (let) — caught immediately
  let config = { debug: true }
}

Interview answer (concise)

Note

TDZ is the time between entering a block and the let/const declaration line. The variable is hoisted but uninitialized — accessing it throws ReferenceError. var has no TDZ; it's initialized as undefined. TDZ helps catch use-before-declare bugs.

Back to index


382. What is the difference between var, let, and const?

Theory

Featurevarletconst
ScopeFunction-scopedBlock-scopedBlock-scoped
HoistingHoisted, initialized as undefinedHoisted but in TDZ until declarationHoisted but in TDZ until declaration
ReassignmentAllowedAllowedNot allowed (binding is constant)
RedeclarationAllowed in same scopeNot allowedNot allowed
Global object propertywindow.x in browsersNoNo

TDZ (Temporal Dead Zone): The period between entering a scope and the line where let/const is declared. Accessing the variable in TDZ throws ReferenceError.

Important nuance for const: const prevents rebinding, not mutation. Objects and arrays can still be mutated.

Practical Example

javascript
// --- Scope ---
function scopeDemo() {
  if (true) {
    var a = 1 // function-scoped
    let b = 2 // block-scoped
    const c = 3 // block-scoped
  }
  console.log(a) // 1
  // console.log(b); // ReferenceError
  // console.log(c); // ReferenceError
}
 
// --- Hoisting ---
console.log(x) // undefined (var is hoisted)
var x = 10
 
// console.log(y); // ReferenceError (TDZ)
let y = 20
 
// --- const mutation vs reassignment ---
const user = { name: 'Rahul' }
user.name = 'Amit' // ✅ OK — mutating object
// user = {};       // ❌ TypeError — rebinding
 
// --- Classic var loop bug ---
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100) // 3, 3, 3
}
 
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100) // 0, 1, 2
}

Interview Answer

Use const by default, let when reassignment is needed, and avoid var in modern code. let/const give block scope and prevent hoisting bugs. const ensures the reference doesn't change, which makes code easier to reason about in large codebases.

Back to index


383. What is the difference between children and props?

Answer

  • props are custom attributes passed to a component.
  • children is a special prop that includes any nested elements or components passed between the component's opening and closing tags.
jsx
<MyComponent>Hello</MyComponent> // "Hello" is passed as children

Top 15 Redux Toolkit + RTK Query Interview Questions (With Answers)

These are essential interview questions and answers focused on Redux Toolkit and RTK Query, ideal for beginner to intermediate developers.

Back to index


384. What is the most challenging task you handled in your project?

Theory

This is a behavioral question. Interviewers assess problem-solving, ownership, technical depth, and communication. Use the STAR method:

  • Situation — project context and constraints
  • Task — your specific responsibility
  • Action — technical decisions and steps you took
  • Result — measurable outcome

the company values practical experience, so anchor your answer in real technical work — performance, legacy migration, production bugs, or cross-team coordination.

Pros & Cons of answer strategies

DoAvoid
Quantify impact (load time, error rate, users affected)Vague answers without technical detail
Explain trade-offs you consideredBlaming teammates
Show what you learnedPicking a trivial task
Mention tools (React Profiler, Redux DevTools)Rambling over 3 minutes

Real-Life Example — Sample answer skeleton

Note

Situation: In our e-commerce project, the product listing page took 8+ seconds to load on mobile. It had 200+ product cards, each fetching ratings from a separate API, causing a waterfall of 200 requests.

Performance

Task: I owned the frontend performance fix for the listing page before a major sale event.

Note

Action:

Note

  • Profiled with React DevTools — identified unnecessary re-renders and N+1 API pattern
  • Worked with backend to create a batch ratings API (/api/ratings?ids=1,2,3...)
  • Introduced TanStack Query for caching and deduplication
  • Virtualized the product list with @tanstack/react-virtual — only 15 DOM nodes instead of 200
  • Wrapped ProductCard in React.memo and stabilized callbacks with useCallback

Performance

Result: Page load dropped from 8s to 1.4s (LCP). API calls reduced from 200+ to 2 per page view. Zero performance complaints during the sale weekend.

Tips for your own answer

Pick a story where you can naturally mention React, TypeScript, or Redux — the company's focus areas. Have 2–3 stories ready and choose based on the conversation flow.

Back to index


385. What is Web Accessibility?

Theory

Web Accessibility (a11y) means designing and building websites so that everyone can use them — including people with visual, auditory, motor, cognitive, and neurological disabilities. It also benefits users on slow networks, mobile devices, and older browsers.

Standards are defined by WCAG (Web Content Accessibility Guidelines) with levels A, AA, and AAA. Most companies target WCAG 2.1 AA.

Core principles (POUR):

  • Perceivable — content available to all senses (alt text, captions)
  • Operable — keyboard navigation, no seizure triggers
  • Understandable — clear language, predictable behavior
  • Robust — works with assistive technologies (screen readers)

Pros & Cons

Accessible appsIgnoring accessibility
✅ Larger user base (15%+ have disabilities)❌ Legal risk (ADA, EAA, RPwD Act)
✅ Better SEO (semantic HTML)❌ Lost enterprise contracts
✅ Better UX for everyone❌ Fails Lighthouse a11y audits
✅ Required by many clients❌ Keyboard users can't navigate

Real-Life Example

tsx
// ❌ Inaccessible
<div onClick={submitOrder}>Place Order</div>
<input placeholder="Email" />
<div className="error">Invalid email</div>
 
// ✅ Accessible
<button type="submit" onClick={submitOrder}>
  Place Order
</button>
 
<label htmlFor="email">Email address</label>
<input
  id="email"
  type="email"
  aria-describedby="email-hint email-error"
  aria-invalid={hasError}
  autoComplete="email"
/>
<span id="email-hint" className="sr-only">
  Enter your registered email address
</span>
{hasError && (
  <p id="email-error" role="alert">
    Please enter a valid email address
  </p>
)}
tsx
// Modal — focus trap + ARIA
function ConfirmModal({ isOpen, onClose, onConfirm, title, children }) {
  const modalRef = useRef(null)
 
  useEffect(() => {
    if (!isOpen) return
    modalRef.current?.focus()
    const handleEsc = (e) => e.key === 'Escape' && onClose()
    document.addEventListener('keydown', handleEsc)
    return () => document.removeEventListener('keydown', handleEsc)
  }, [isOpen, onClose])
 
  if (!isOpen) return null
 
  return (
    <div className="overlay" role="presentation" onClick={onClose}>
      <div
        ref={modalRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
        tabIndex={-1}
        onClick={(e) => e.stopPropagation()}>
        <h2 id="modal-title">{title}</h2>
        {children}
        <button onClick={onConfirm}>Confirm</button>
        <button onClick={onClose}>Cancel</button>
      </div>
    </div>
  )
}

Quick checklist:

PracticeExample
Semantic HTML<nav>, <main>, <button>, not <div onClick>
Alt text<img alt="Chicken Biryani from Spice Garden" />
KeyboardTab through all interactive elements
Focus visible:focus-visible outline styles
Color contrast4.5:1 for normal text (WCAG AA)
Skip link"Skip to main content"
Live regionsaria-live="polite" for status updates
bash
npm install -D eslint-plugin-jsx-a11y @axe-core/react

Interview answer (concise)

Note

Web accessibility ensures apps are usable by people with disabilities and assistive technologies. I use semantic HTML, ARIA where needed, keyboard navigation, focus management, color contrast, and screen reader labels. It's both a legal requirement and better UX for everyone.

Back to index


386. What was your role in your last project?

Theory

Use STAR method — Situation, Task, Action, Result. For senior roles, emphasize ownership, decisions, collaboration, and measurable impact.

Interview Answer

I was the senior frontend developer owning the [module name] — from architecture and implementation to code review, mentoring, and production support.

Real Example — Sample answer

Note

Situation: Enterprise SaaS platform serving 10,000+ business users with a legacy jQuery frontend being migrated to React.

Note

Task: I was the senior frontend developer responsible for the dashboard and reporting module — the highest-traffic area after login.

Note

Action:

Note

  • Designed feature-based folder structure and shared component library
  • Implemented auth with httpOnly cookies and protected routes
  • Introduced Redux Toolkit for global state and TanStack Query for API caching
  • Led code reviews, wrote ADRs for key decisions, mentored 2 junior devs
  • Reduced dashboard load time from 4.2s to 1.1s with code splitting and virtualization

Performance

Result: Module shipped on time, zero P0 bugs in first month, dashboard LCP improved 74%, junior devs promoted within a year.

Back to index


387. Why did you choose React?

Theory

Interviewers want genuine reasoning — not "because it's popular." Connect React's strengths to real problems you've solved.

Pros & Cons (why React vs alternatives)

ReactAngularVue
✅ Flexible — library not framework✅ Full framework, opinionated✅ Gentle learning curve
✅ Huge ecosystem and hiring pool❌ Steeper learning curve❌ Smaller enterprise adoption in India
✅ Component reusability
✅ Virtual DOM performance
❌ Needs choices (router, state)

Interview Answer

I chose React because of its component model, one-way data flow, and ecosystem. It lets me build reusable UIs at scale, hire and collaborate easily, and the declarative model makes complex state manageable with hooks.

Real Example — Strong answer

Tip

I picked React over Angular because I prefer its flexibility — I choose my router, state library, and tooling based on project needs rather than a fixed framework. The component model maps naturally to UI design, and hooks made state logic reusable without class boilerplate.

Note

Practically, React's ecosystem — React Query, Redux Toolkit, Next.js — solved real problems in my projects: caching API data, managing auth state, and SSR for SEO. The job market and community support also mean faster problem-solving and easier team scaling.

Back to index


388. Why does CSS block rendering?

Theory

Browsers build the render tree by combining the DOM and CSSOM (CSS Object Model). The browser cannot render until it knows how elements look — so CSS is a render-blocking resource.

Pipeline:

plaintext
HTML → DOM tree
CSS  → CSSOM tree
DOM + CSSOM → Render tree → Layout → Paint → Composite

If CSS hasn't loaded, the browser would show unstyled content (FOUC — Flash of Unstyled Content), then repaint when CSS arrives — causing layout shift and wasted work. So browsers block rendering until critical CSS is parsed.

JavaScript can also block rendering because document.write and synchronous scripts can modify DOM/CSSOM. CSS blocking is about the render tree dependency, not JS execution per se.

Pros & Cons of render-blocking CSS

Blocking (default)Non-blocking (media trick / async)
✅ No FOUC✅ Faster first paint
✅ Correct layout on first paint❌ Flash of unstyled content
❌ Delays first render❌ CLS risk

Real-Life Example

html
<!-- Render-blocking — browser waits before painting -->
<link rel="stylesheet" href="styles.css" />
 
<!-- Non-render-blocking — loads but doesn't block render -->
<link rel="stylesheet" href="print.css" media="print" />
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)" />
 
<!-- Critical CSS — inline above-the-fold styles -->
<style>
  .header {
    background: #0d0d0d;
    height: 64px;
  }
  .hero {
    min-height: 400px;
  }
</style>
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'" />

Optimization strategies a fintech app expects you to know:

  • Inline critical CSS for above-the-fold
  • media attribute for non-critical stylesheets
  • preload for important CSS
  • Avoid @import in CSS (serial loading)
  • Minimize CSS size — remove unused (PurgeCSS)

Interview answer (concise)

Performance

CSS blocks rendering because the browser needs the CSSOM to build the render tree — it won't paint until it knows how elements are styled, preventing FOUC. JS doesn't block rendering the same way, but synchronous scripts block HTML parsing which indirectly delays DOM/CSSOM. Optimize with critical inline CSS, preload, and media queries for non-critical styles.

Back to index


389. Why use React?

Answer

React offers several benefits:

  • Declarative syntax makes code more predictable and easier to debug.
  • Component-based architecture encourages reusable, maintainable code.
  • It uses a Virtual DOM for faster rendering.
  • It's backed by a strong community and ecosystem.

Back to index


390. Write unit tests for critical functionality

Theory

Test behavior, not implementation. Focus on critical paths: authentication, payments, form validation, data transformations, and user-facing interactions. Use React Testing Library — query by role/label (how users see the UI), not by CSS class or internal state.

Testing trophy: More integration tests, fewer shallow unit tests, some E2E (Playwright/Cypress).

Pros & Cons

Testing critical pathsNo tests
✅ Catch regressions before deploy❌ Production bugs cost more to fix
✅ Confidence to refactor❌ Fear of changing code
✅ Living documentation❌ Manual QA only — slow and incomplete
❌ Initial time investment—

Real-Life Example

tsx
// utils/pricing.test.ts — pure logic, fast tests
import { calculateOrderTotal } from './pricing'
 
describe('calculateOrderTotal', () => {
  it('applies free delivery above ₹500', () => {
    const items = [{ price: 300, quantity: 2 }] // subtotal = 600
    const result = calculateOrderTotal(items)
    expect(result.deliveryFee).toBe(0)
  })
 
  it('charges ₹40 delivery below ₹500', () => {
    const items = [{ price: 200, quantity: 1 }]
    const result = calculateOrderTotal(items)
    expect(result.deliveryFee).toBe(40)
  })
})
tsx
// components/LoginForm.test.tsx — user behavior
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
 
describe('LoginForm', () => {
  it('shows error on invalid credentials', async () => {
    const user = userEvent.setup()
    render(<LoginForm onSubmit={jest.fn()} />)
 
    await user.type(screen.getByLabelText(/email/i), 'wrong@test.com')
    await user.type(screen.getByLabelText(/password/i), 'wrongpass')
    await user.click(screen.getByRole('button', { name: /sign in/i }))
 
    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent(/invalid credentials/i)
    })
  })
 
  it('disables submit while loading', async () => {
    render(<LoginForm onSubmit={() => new Promise(() => {})} />)
    await userEvent.click(screen.getByRole('button', { name: /sign in/i }))
    expect(screen.getByRole('button', { name: /signing in/i })).toBeDisabled()
  })
})

What to test vs skip:

Test ✅Skip ❌
Payment/checkout flowThird-party library internals
Form validation rulesCSS styling (use visual regression)
Auth guards / protected routesEvery trivial render
Business logic (utils)Implementation details (state variable names)

Back to index


391. Your application has 10,000 blog posts. How would you generate and serve these pages efficiently?

Description

Rendering 10,000 pages at every build can be slow. You need a strategy that balances SEO, build time, and freshness — typically SSG + ISR or dynamic SSR with caching, not 10K blocking builds on every deploy.

Theory

ApproachWhen to use
SSG all 10K at buildOK if build pipeline handles it (~minutes)
ISR + generateStaticParamsPre-render popular posts; generate rest on first visit
On-demand ISRCMS webhook triggers revalidatePath
Pagination + SSG listingList pages static; detail on demand

generateStaticParams — pre-build top N; others generated at runtime.

Pros & Cons

ISR (incremental)Full SSG every deploy
✅ Fast deploys✅ All pages instantly cached at edge
✅ Scales to millions of pages❌ Build time grows linearly
❌ First visitor pays generation cost❌ Rebuild all 10K for one typo fix

Real Example — Blog with 10,000 posts

tsx
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
 
export const revalidate = 3600 // ISR — revalidate every hour
 
// Pre-build top 500 posts; rest on-demand
export async function generateStaticParams() {
  const posts = await fetch(`${process.env.API_URL}/posts?top=500`).then((r) => r.json())
 
  return posts.map((post: { slug: string }) => ({ slug: post.slug }))
}
 
async function getPost(slug: string) {
  const res = await fetch(`${process.env.API_URL}/posts/${slug}`, {
    next: { tags: [`post-${slug}`] },
  })
  if (res.status === 404) return null
  return res.json()
}
 
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  if (!post) notFound()
 
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </article>
  )
}
tsx
// app/blog/page.tsx — paginated listing (SSG per page)
export async function generateStaticParams() {
  const { totalPages } = await getPostCount() // e.g. 400 pages × 25 posts
  return Array.from({ length: totalPages }, (_, i) => ({
    page: String(i + 1),
  }))
}
tsx
// CMS webhook — revalidate single post
// app/api/revalidate/post/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
 
export async function POST(req: Request) {
  const { slug, secret } = await req.json()
  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }
 
  revalidateTag(`post-${slug}`)
  revalidatePath('/blog')
  return Response.json({ ok: true })
}

Serving: Deploy to Vercel/Cloudflare — static HTML at edge CDN; ISR regenerates in background.

Interview Answer

I'd use generateStaticParams to pre-render high-traffic posts, ISR with revalidate for the long tail of 10K pages, paginated static listing routes, and on-demand revalidation from the CMS so deploys stay fast and content updates don't require full rebuilds.

Back to index


Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime