Curated interview practice questions with answers. These are practice scenarios, not a verified list of past interview questions.
About Zepto hiring for React JS roles
Zepto is an Indian quick commerce company delivering groceries and daily essentials from dark stores within minutes. Its technology and analytics teams work on demand forecasting, inventory, dark store operations, delivery routing and app experience. It hires for software engineers, SDETs, business analysts, data analysts and data scientists.
For React JS roles, this Zepto practice set focuses most on Props and State (5), React Hooks (5), Performance Optimization (5), State Management (4). It has 2 easy, 18 medium, 10 hard questions, so you can start easy and work up to the harder ones.
Company type
E-commerce & consumer tech
Headquarters
Mumbai, India
Main India offices
Mumbai, Bengaluru
Zepto React JS interview process
Online assessment or take-home assignment
Technical or case round
Hiring manager round
Founder or leadership round
Typical pattern based on public candidate experiences. Rounds vary by role, team and year.
Solved for ZeptoStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
A cart page keeps the selected item object in state and also the items array. After editing the item's quantity, the details panel shows the old quantity. Why?
The selected item is a copy duplicated from the array, so updating the array does not update the copy. This breaks the single source of truth. Store only selectedId in state and derive the item during render: const selected = items.find(i => i.id === selectedId). Now there is one place where each item lives, and every view stays in sync automatically.
02
A junior notices that every child component re-renders when the parent's state changes, even when their props did not change. How do you explain this?
By default, when a component renders, React re-renders all of its children, because it cannot know whether they depend on something else. Re-rendering is usually cheap since only real changes reach the DOM. If a child is expensive, wrap it in React.memo and keep its props stable with useCallback or useMemo, or let the React Compiler memoise automatically. Moving state down also helps.
An error boundary catches errors thrown while rendering its children and shows a fallback UI. It must be a class component (or use the react-error-boundary package).
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info.componentStack);
}
render() {
return this.state.hasError ? <p>Something went wrong</p> : this.props.children;
}
}
It does not catch errors in event handlers or async code.
05
What do useActionState and useFormStatus do in React 19?
useActionState wraps an action function and returns [state, formAction, isPending]; you pass formAction to a form's action prop and the returned state holds the result, like a validation error. useFormStatus, from react-dom, lets a child component such as a submit button read the pending status of the parent form without prop drilling. Together they remove a lot of manual loading and error state code.
A pure reducer returns new state based only on its inputs and has no side effects: no API calls, no random values, no mutation of arguments. This makes state changes predictable, testable and replayable, which is what enables time-travel debugging in Redux DevTools. Side effects belong in thunks, listeners or RTK Query. Redux Toolkit uses Immer, so draft mutations inside createSlice are still safe.
07
A teammate wraps every function in useCallback and every value in useMemo for performance. When should you not memoise, and what feedback would you give?
Memoisation has costs: extra memory, comparisons on every render and more complex code. Skip it for cheap calculations, components that rarely re-render, and props that change every render anyway. Memoising everything by default also hides design problems like state placed too high. Use the profiler to find real bottlenecks first. With the React Compiler, manual memoisation is mostly needed only as an escape hatch.
08
Show a login form using React Hook Form with Zod validation.
Save it to localStorage and read it on startup. With Zustand, the persist middleware does this.
import { persist } from 'zustand/middleware';
const useCart = create(persist(
set => ({ items: [], add: i => set(s => ({ items: [...s.items, i] })) }),
{ name: 'cart-storage' }
));
With Redux, use redux-persist or a store.subscribe that writes a throttled snapshot. Never store secrets like tokens in localStorage.
The React Compiler, released as stable version 1.0 in late 2025, is a build-time tool (Babel plugin, also integrated in Vite and Next.js setups) that automatically memoises components and values. It analyses your code and inserts caching similar to useMemo, useCallback and React.memo, so developers write plain code. It requires following the rules of React, such as pure rendering, and can be adopted gradually per directory.
Use renderHook from React Testing Library and wrap state changes in act.
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('increments', () => {
const { result } = renderHook(() => useCounter(5));
act(() => result.current.increment());
expect(result.current.count).toBe(6);
});
If the hook needs context, pass a wrapper option with the provider.
13
Users lose their typed data when they accidentally click another menu link. How would you warn them about unsaved changes?
Track a dirty flag, for example formState.isDirty from React Hook Form. For in-app navigation, use React Router's useBlocker to block the transition and show a confirm dialog. For tab close or refresh, add a beforeunload listener while the form is dirty, and remove it in the effect cleanup. Also consider autosaving drafts, which is often a better user experience than blocking.
14
A search box fetches results on every keystroke and sometimes shows results for an older query. How do you fix this race condition?
Responses can arrive out of order, so a slow old request overwrites newer results. Cancel or ignore stale requests in the effect cleanup.
useEffect(() => {
const ctrl = new AbortController();
fetch(`/api/search?q=${q}`, { signal: ctrl.signal })
.then(r => r.json())
.then(setResults)
.catch(() => {});
return () => ctrl.abort();
}, [q]);
Also debounce the input, or use TanStack Query which handles this for you.
15
After adding an AuthContext at the root, profiling shows the whole app re-rendering every few seconds. What would you check?
Check what the provider value is and what updates it. Likely the value is a new object each render, or it includes fast-changing data such as a token refresh timer or a clock. Memoise the value with useMemo, move frequently changing data out of the context, and split it into separate contexts, so components reading only the user do not re-render when unrelated fields change.
16
On a profile edit page, when you switch from one user to another the form still shows the old user's typed values. How can you reset it cleanly?
React preserves state when the same component stays at the same position in the tree. Give the form a key tied to the user, like <ProfileForm key={user.id} user={user} />. When the key changes, React unmounts the old form and mounts a fresh one with new initial state. This is cleaner than writing an effect that manually resets every field when the user prop changes.
17
All unit tests passed, but users found that the delete button did nothing. The tests were checking that a handleDelete function was called. What went wrong?
The tests checked implementation details instead of user-visible behaviour. A mocked handler being called says nothing about whether the item actually disappears or the API is hit. Rewrite the test to click the button by role, mock the API with MSW and assert the item is removed from the screen. Behaviour-focused tests survive refactoring and catch real bugs like a broken wiring.
18
How would you update deeply nested state cleanly? Show with Immer.
Manual spreading of nested objects is error prone. Immer lets you write mutating code on a draft and produces an immutable copy. Redux Toolkit uses it internally.
import { useImmer } from 'use-immer';
const [form, updateForm] = useImmer({ address: { city: '', pin: '' } });
updateForm(draft => {
draft.address.city = 'Noida';
});
Also consider flattening state to avoid deep nesting.
19
What are the common styling options in React, and how do Server Components affect the choice?
Options include plain CSS or CSS Modules (scoped class names), Tailwind CSS utility classes, Sass, and CSS-in-JS libraries like styled-components or Emotion. Runtime CSS-in-JS needs client-side JavaScript and context, so it fits poorly with Server Components. That is why Tailwind and CSS Modules, or zero-runtime libraries, are the most common choice in modern Next.js projects. Component libraries like shadcn/ui build on Tailwind.
20
Your tests pass but print 'An update to Component inside a test was not wrapped in act(...)'. What does it mean and how do you fix it?
It means state changed after the test finished checking, usually because an async operation like a fetch or timer resolved later. The test may not be checking the final UI. Fix it by awaiting the result: use findBy queries or waitFor for the expected content, await user-event calls, and use fake timers with act when testing timeouts. Do not just wrap random code in act to hide the warning.
Use the React DevTools Profiler: record an interaction, then check the flame chart for components with long render times and the reason each one re-rendered (enable 'record why each component rendered'). Pair it with the Chrome Performance panel for long tasks, layout and network. Enable highlight updates to spot unnecessary renders. Measure first, fix the biggest cost, then measure again in a production build.
useSyncExternalStore subscribes a component to an external, non-React data source, like a browser API or a custom store, in a way that is safe with concurrent rendering. You give it a subscribe function and a getSnapshot function, and optionally getServerSnapshot. It prevents tearing, where parts of the UI show different values of the same store. Libraries like Redux and Zustand use it internally.
23
Your React app works locally, but after deployment refreshing /dashboard gives a 404 from the server. Why, and how do you fix it?
The server looks for a real file at /dashboard, which does not exist because routing happens in the browser. Configure the server to return index.html for all unknown paths: a rewrite rule in Nginx (try_files $uri /index.html), a _redirects or rewrites setting on static hosts, or a fallback in Express. Alternatively, use a framework with server routing. HashRouter also avoids it but gives ugly URLs.
Server Components run only on the server or at build time. They can be async, read databases or files directly and send rendered output to the client without shipping their JavaScript, which reduces bundle size. They cannot use state, effects or browser APIs. Client Components, marked with 'use client', handle interactivity. Server Components can render Client Components and pass them serialisable props.
25
What is state colocation and why does it matter for performance and maintainability?
State colocation means keeping state as close as possible to the components that use it. Global or high-level state causes large parts of the tree to re-render on every change and makes ownership unclear. If only a dropdown needs isOpen, keep it in the dropdown. Lift state only when siblings must share it. Good colocation often removes the need for memoisation entirely.
26
Typing in a search box feels laggy because a big product list is filtered on every keystroke. How do you fix it?
Keep the input state urgent and make the expensive part deferred. Use const deferredQuery = useDeferredValue(query) and filter with deferredQuery inside useMemo, and wrap the list in React.memo. The input updates instantly while React renders the list in the background and can interrupt it. If filtering is still heavy, debounce it, move it to a Web Worker or filter on the server.
27
Why use Link instead of a normal anchor tag in a React app?
A normal <a href> causes a full page reload, which throws away all React state and re-downloads the app. <Link to="/cart"> intercepts the click, updates the URL with the History API and lets the router render the new route instantly. It still renders a real anchor, so accessibility, middle-click and right-click open in new tab work. NavLink adds active styling for menus.
28
Your Next.js build fails with 'ReferenceError: window is not defined'. Why, and how do you fix it?
Code that touches browser globals runs during server rendering, where window and document do not exist. This often comes from a library that accesses window at import time or from reading localStorage in render. Fix it by moving browser access into useEffect or event handlers inside a Client Component, checking typeof window !== 'undefined', or loading the library with next/dynamic and ssr: false from a client component.
29
A junior developer is confused about when to use useEffect. How would you explain it and help them?
Situation: a new joiner was putting almost all logic into effects, causing bugs. Task: help them build the right mental model. Action: I explained that effects sync with outside systems, while calculations go in render and user actions go in handlers. We refactored one of their components together and I shared the react.dev article on avoiding effects. Result: their next PR had no unnecessary effects.
30
An orders table renders 10,000 rows and the page freezes on load and scroll. What would you do?
First question whether users need all rows at once: server-side pagination or infinite scroll with cursor-based loading may solve it. If a long scrollable list is required, virtualise it with TanStack Virtual so only around 30 rows exist in the DOM. Also memoise row components, avoid heavy formatting in render by precomputing, and move sorting or filtering off the main thread if it is expensive.
Tips to crack the Zepto React JS interview
Prepare cases on dark store operations, such as reducing stock-outs, improving picking time or choosing new store locations.
Practise SQL and Python for forecasting and inventory questions, including handling daily demand data.
Show comfort with a fast-paced startup environment and give examples of taking ownership in tight timelines.
Keep a one-minute story ready for every project on your CV: problem, your part, tools, result.
Practise the hard questions on this page aloud; Zepto interviewers usually ask follow-ups on your first answer.
Frequently asked questions
How many rounds are there in the Zepto React JS interview?
Candidates usually go through 4 stages: Online assessment or take-home assignment; Technical or case round; Hiring manager round; Founder or leadership round. The exact rounds change with the role, team, location and your experience, so confirm the process with the recruiter.
Which topics should I prepare for the Zepto React JS interview?
This practice set leans on Props and State (5), React Hooks (5), Performance Optimization (5), State Management (4). Revise those first, then practise explaining one or two of your own projects end to end, because most Zepto interviewers ask you to walk through real work.
Are these the exact questions asked at Zepto?
No. These are curated practice questions that match the skills and difficulty usually tested in e-commerce & consumer tech interviews like Zepto's. They are not a leaked or verified list of past questions.
Where does Zepto hire React JS professionals in India?
Zepto has large teams in Mumbai, Bengaluru. Openings and locations change often, so check the Zepto careers page and filter by city and experience.