Skip to content

PhonePe React JS Interview Questions & Answers

Curated interview practice questions with answers. These are practice scenarios, not a verified list of past interview questions.

About PhonePe hiring for React JS roles

PhonePe is an Indian digital payments and financial services company, majority-owned by Walmart, known for UPI payments, insurance and investment distribution, and the Pincode commerce app. Its engineering teams build high-scale payments systems. It hires for software engineers, SDETs, data analysts, data engineers and data scientists.

For React JS roles, this PhonePe practice set focuses most on Props and State (6), Performance Optimization (5), Testing and Debugging (4), React Fundamentals (4). It has 2 easy, 21 medium, 7 hard questions, so you can start easy and work up to the harder ones.

Company type
Fintech
Headquarters
Bengaluru, India
Main India offices
Bengaluru, Pune

PhonePe React JS interview process

  1. Online coding assessment
  2. Machine coding round
  3. DSA or problem solving round
  4. Design or hiring manager round
  5. HR round

Typical pattern based on public candidate experiences. Rounds vary by role, team and year.

Solved for PhonePeStart 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

How do you test a custom hook?

Save to ▾
Show answer
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.
02

You are building a 4-step onboarding wizard. How do you keep data when users move back and forth between steps?

Save to ▾
Show answer
Store the wizard data in a parent component, a reducer, or a small store, not inside each step, because step components unmount when hidden. Each step receives its slice and an update callback. Validate each step before moving forward, and optionally save drafts to sessionStorage or the server so a refresh does not lose progress. The current step can also live in the URL for back-button support.
03

A user types into an input inside a list row, then sorts the list, and the typed text appears in a different row. How would you fix this?

Save to ▾
Show answer
The rows are probably keyed by index or by a value that changes after sorting, so React keeps the DOM input in the same position while the data moves. Give each row a stable key from the data, such as the record id. Better still, make the input controlled and store its value in state keyed by id, so the text always follows the correct record.
04

How does a child component send data back to its parent?

Save to ▾
Show answer
The parent passes a callback function as a prop, and the child calls it with the data. function Parent() { const [city, setCity] = useState(''); return <CityPicker onSelect={setCity} />; } function CityPicker({ onSelect }) { return ( <select onChange={e => onSelect(e.target.value)}> <option>Delhi</option> <option>Noida</option> </select> ); }
05

How do you find what is making a React page slow?

Save to ▾
Show answer
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.
06

An orders table renders 10,000 rows and the page freezes on load and scroll. What would you do?

Save to ▾
Show answer
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.
07

What is useId and why not use Math.random for ids?

Save to ▾
Show answer
useId generates a unique, stable id for a component instance that matches between server and client rendering. It is used for accessibility attributes, like linking a label's htmlFor to an input's id. Math.random or a global counter would produce different values on the server and the browser, causing hydration mismatches, and would change on every render. useId should not be used as list keys.
08

How do you write a generic, type-safe List component in TypeScript?

Save to ▾
Show answer
Use a type parameter so the item type flows to the render callback. type ListProps<T> = { items: T[]; getKey: (item: T) => string | number; renderItem: (item: T) => React.ReactNode; }; function List<T>({ items, getKey, renderItem }: ListProps<T>) { return <ul>{items.map(i => <li key={getKey(i)}>{renderItem(i)}</li>)}</ul>; } <List items={users} getKey={u => u.id} renderItem={u => u.name} />
09

What is tree shaking?

Save to ▾
Show answer
Tree shaking is dead code elimination done by bundlers like Rollup (used by Vite), esbuild and webpack. They analyse ES module import and export statements and drop exports that are never used. It works only with static ESM syntax, not CommonJS require, and can be blocked by modules with side effects. Importing import { debounce } from 'lodash-es' instead of the whole lodash library keeps the bundle small.
10

A teammate writes setCount(count + 1) three times in a click handler and is surprised that the count increases by only one. Explain why.

Save to ▾
Show answer
Inside one render, count is a constant snapshot, say 0. Each call schedules setCount(0 + 1), so all three set the same value 1, and React batches them into one render. State does not change immediately after calling the setter. To increment three times, use the updater form setCount(c => c + 1), which queues functions that each receive the latest pending value.
11

How would you update deeply nested state cleanly? Show with Immer.

Save to ▾
Show answer
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.
12

What is a Higher-Order Component and is it still used?

Save to ▾
Show answer
A Higher-Order Component is a function that takes a component and returns a new component with extra behaviour, for example withAuth(Profile) that redirects unauthenticated users. HOCs were common for sharing logic like routing or Redux connect. Today custom hooks are preferred because they avoid wrapper nesting, prop name collisions and confusing component trees. You will still see HOCs in older codebases.
13

How do you handle a form with multiple controlled inputs using one change handler?

Save to ▾
Show answer
Use the input's name attribute to update the right field. function SignupForm() { const [form, setForm] = useState({ name: '', email: '', city: '' }); const handleChange = e => { const { name, value } = e.target; setForm(f => ({ ...f, [name]: value })); }; const handleSubmit = e => { e.preventDefault(); console.log(form); }; return ( <form onSubmit={handleSubmit}> <input name="name" value={form.name} onChange={handleChange} /> <input name="email" value={form.email} onChange={handleChange} /> <button>Submit</button> </form> ); }
14

In a Next.js app you see a hydration mismatch error. How do you debug it?

Save to ▾
Show answer
It means the HTML rendered on the server differs from the first client render. Common causes: using Date.now(), Math.random() or locale formatting in render, reading window or localStorage during render, browser extensions changing the DOM, and invalid HTML nesting like a div inside a p. Read the diff in the error, render client-only values inside useEffect, use useId for ids and fix invalid markup.
15

What is list virtualisation?

Save to ▾
Show answer
Virtualisation, or windowing, renders only the rows visible in the viewport plus a small buffer, instead of thousands of DOM nodes. As the user scrolls, rows are recycled with new data. Libraries such as TanStack Virtual and react-window implement it. It dramatically cuts render time and memory for long lists, chat histories and data grids, but needs care with variable row heights and browser find-in-page.
16

A developer stores fullName in state and updates it in a useEffect whenever firstName or lastName changes. What would you suggest?

Save to ▾
Show answer
This is redundant derived state. It causes an extra render and risks the values going out of sync. Compute it during render instead: const fullName = firstName + ' ' + lastName. If the calculation is expensive, wrap it in useMemo. As a rule, if a value can be calculated from existing props or state, do not store it in state and do not sync it with an effect.
17

What is React Fiber?

Save to ▾
Show answer
Fiber is the reconciliation engine introduced in React 16. It represents each component as a unit of work (a fiber node) so rendering can be split into chunks, paused, prioritised and resumed. This makes concurrent features possible: urgent updates like typing can interrupt less urgent renders. Rendering happens in an interruptible render phase, then changes are applied in a synchronous commit phase.
18

Write a useDebounce custom hook.

Save to ▾
Show answer
Debouncing delays a value until the user stops typing, which reduces API calls. function useDebounce(value, delay = 400) { const [debounced, setDebounced] = useState(value); useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]); return debounced; } const q = useDebounce(searchText); // fetch when q changes
19

A timer using setInterval inside useEffect always shows 1 and never goes higher. What is the bug?

Save to ▾
Show answer
The effect probably runs once with an empty dependency array and calls setCount(count + 1). The interval callback closes over the count from the first render, which is 0 forever, a stale closure. Fix it with the updater form setCount(c => c + 1), so the callback never needs the current value, and return a cleanup that calls clearInterval to avoid leaks.
20

How do you include accessibility checks in your React tests?

Save to ▾
Show answer
Use jest-axe or vitest-axe to run axe-core on rendered components. import { axe } from 'vitest-axe'; test('form has no a11y violations', async () => { const { container } = render(<SignupForm />); expect(await axe(container)).toHaveNoViolations(); }); Also query by role and label in tests, run Lighthouse or axe DevTools in the browser, and test keyboard navigation manually, since automated tools catch only part of the issues.
21

What is atomic state management, as in Jotai or Recoil-style libraries?

Save to ▾
Show answer
Atomic state splits state into small independent units called atoms. Components subscribe to only the atoms they use, and derived atoms compute values from others, similar to a spreadsheet. This gives fine-grained re-renders without selectors or a big central store. Jotai is the popular option today; Recoil is no longer maintained. It suits apps with many independent pieces of interactive state.
22

How do you mock a module in Vitest?

Save to ▾
Show answer
Use vi.mock with a factory, then control return values per test. import { vi } from 'vitest'; import { getProfile } from './api'; vi.mock('./api', () => ({ getProfile: vi.fn() })); test('shows name', async () => { getProfile.mockResolvedValue({ name: 'Ravi' }); render(<Profile />); expect(await screen.findByText('Ravi')).toBeInTheDocument(); }); In Jest, the same pattern uses jest.mock and jest.fn.
23

How do you create an error boundary in React?

Save to ▾
Show answer
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.
24

You inherit an old Redux codebase with connect, hand-written action types and switch reducers. How would you modernise it?

Save to ▾
Show answer
I would migrate gradually, not in one big rewrite. First replace createStore with configureStore, which keeps old reducers working. Then convert one feature at a time to createSlice, switch components from connect to useSelector and useDispatch, and move data fetching to RTK Query. Add tests around each feature before migrating, and remove dead actions. Each step ships independently and reduces risk.
25

What do useActionState and useFormStatus do in React 19?

Save to ▾
Show answer
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.
26

Create a Redux Toolkit slice for a counter.

Save to ▾
Show answer
createSlice generates action creators and the reducer. Immer lets you write mutating syntax safely. import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: state => { state.value += 1; }, addBy: (state, action) => { state.value += action.payload; } } }); export const { increment, addBy } = counterSlice.actions; export default counterSlice.reducer;
27

You wrapped a row component in React.memo, but the profiler shows it still re-renders every time. The parent passes onClick={() => remove(id)}. Why?

Save to ▾
Show answer
An inline function like onClick={() => remove(id)} creates a new function object on every render. React.memo compares props by reference, sees a different function and re-renders the child every time, so memo has no effect. Wrap the handler with useCallback and correct dependencies, or let the React Compiler handle it. If the child is not memoised, inline functions are fine and cheap.
28

What are the risks of spreading all props like <Input {...props} />?

Save to ▾
Show answer
Spreading is convenient for wrapper components, but it can pass unknown attributes to DOM elements, causing console warnings, and it hides what the component actually accepts. It can also override props you set explicitly, depending on order. Prefer destructuring the props you use and spreading only the rest onto the underlying element, for example const { label, ...inputProps } = props.
29

How would you build and maintain a shared component library for several React apps?

Save to ▾
Show answer
Start with design tokens for colours, spacing and typography, and build accessible primitives, often on top of headless libraries like Radix UI or React Aria. Document components in Storybook with usage examples, add visual regression and accessibility tests, and publish versioned packages from a monorepo with semantic versioning and changelogs. Keep APIs small and composable, and collect feedback from consuming teams before breaking changes.
30

Tell me about a time a requirement changed late in a sprint. How did you handle it?

Save to ▾
Show answer
Situation: two days before release, the product owner asked to add a GST number field with validation to our checkout form. Task: deliver it without risking the release. Action: I estimated the impact, proposed shipping the field with basic format validation first and the full verification API in the next sprint, and added tests. Result: we released on time and the follow-up shipped a week later.

Tips to crack the PhonePe React JS interview

  • Practise machine coding problems and focus on clean, extensible object-oriented design within the time limit.
  • Understand how UPI transactions work at scale, including idempotency, retries and reconciliation.
  • For analyst roles, prepare cases on transaction success rates, merchant adoption and fraud detection metrics.
  • 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; PhonePe interviewers usually ask follow-ups on your first answer.

Frequently asked questions

How many rounds are there in the PhonePe React JS interview?

Candidates usually go through 5 stages: Online coding assessment; Machine coding round; DSA or problem solving round; Design or hiring manager round; HR 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 PhonePe React JS interview?

This practice set leans on Props and State (6), Performance Optimization (5), Testing and Debugging (4), React Fundamentals (4). Revise those first, then practise explaining one or two of your own projects end to end, because most PhonePe interviewers ask you to walk through real work.

Are these the exact questions asked at PhonePe?

No. These are curated practice questions that match the skills and difficulty usually tested in fintech interviews like PhonePe's. They are not a leaked or verified list of past questions.

Where does PhonePe hire React JS professionals in India?

PhonePe has large teams in Bengaluru, Pune. Openings and locations change often, so check the PhonePe careers page and filter by city and experience.

React JS questions for similar companies