PhonePe Frontend Interview Questions
Payments at scale. Machine coding, problem solving, UI tech.
PhonePe runs payments and financial services for a very large user base, and frontend work spans consumer flows, merchant tools and internal platforms. As at other payments companies, correctness and security matter more than visual flourish.
Candidates commonly describe a loop similar to other large Indian product companies: a machine coding round, a problem-solving round and a UI technology round.
- Questions
- 15, 5 with free answers
- Interview rounds
- 4
- Last reviewed
- October 2026
PhonePe frontend interview process
The usual shape of the loop. A guide to what to prepare, not a promise of what you will get.
- 1
Machine coding
60 to 90 minutes to build a small application, judged on working code and structure.
- 2
Problem solving and data structures
Algorithm questions at a moderate level.
- 3
UI technology
JavaScript internals, polyfills, React and browser security.
- 4
Hiring manager
Past projects, ownership and team fit.
What interviewers tend to look for
- JavaScript internals:
this, closures, polyfills written by hand. - Clean state modelling in the build round.
- Security: token storage, XSS, CSRF and CORS.
- Enough data structures to implement a cache or a scheduler.
Your PhonePe mock interview
15 questions. Type an answer under each one and an AI reviewer scores it and tells you what you missed. Stuck on one? Ask for a hint. A free account gets 3 AI replies a day.
Your mock interview
0 of 15 answered
Answer a question and your score shows up here.
JavaScript
6 questions- Question 1 of 15JavaScript
PhonePe interviewer
How is the value of
thisdecided? How do call, apply and bind differ?Model answer
thisdepends on how a function is called, not where it is written:- Called as a method (
user.greet()): the object before the dot. - Called plainly (
greet()):undefinedin strict mode and in modules, the global object otherwise. - Called with
new: the newly created object. - Called with
call,applyorbind: whatever you pass. - Arrow functions have no
thisof their own. They use the one from the surrounding scope, and it cannot be rebound.
callinvokes the function with arguments listed one by one,applytakes them as an array, andbinddoes not invoke anything: it returns a new function withthis(and optionally leading arguments) fixed.The classic bug is passing
user.greetas a callback. The function is then called plainly and loses itsthis. - Called as a method (
- Question 2 of 15JavaScript
PhonePe interviewer
Write a polyfill for Function.prototype.bind.
Model answer
Function.prototype.myBind = function (context, ...preset) { const fn = this; return function bound(...args) { // Called with `new`: the bound `this` is ignored. if (new.target) return new fn(...preset, ...args); return fn.apply(context, [...preset, ...args]); }; }; function greet(greeting, name) { return greeting + ', ' + name + ' from ' + this.city; } const hello = greet.myBind({ city: 'Pune' }, 'Hello'); hello('Asha'); // "Hello, Asha from Pune"Three things to cover: it returns a new function, it supports partial application (the preset arguments come first), and a bound function used with
newignores the boundthis. Most candidates miss the third. - Question 3 of 15JavaScript
PhonePe interviewer
Implement curry, so that add(1)(2)(3) and add(1, 2)(3) both work.
Model answer
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } return (...more) => curried.apply(this, [...args, ...more]); }; } const add = curry((a, b, c) => a + b + c); add(1)(2)(3); // 6 add(1, 2)(3); // 6It collects arguments until there are as many as the original function declares, then calls it.
The limitation to name:
fn.lengthdoes not count rest parameters or parameters with defaults, so this cannot curry a variadic function. The follow-up for that case is an infinitesum(1)(2)(3)()that stops on an empty call. - Question 4 of 15JavaScript
PhonePe interviewer
What does a setTimeout inside a
for (var i ...)loop print, and how do you fix it?Model answer
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 3, 3, 3varis function-scoped, so there is oneishared by all three callbacks. They run after the loop has finished, wheniis already 3.Fixes, in the order you would reach for them:
- Use
let. It creates a new binding for each iteration, so the output is 0, 1, 2. - Pass the value in:
setTimeout((n) => console.log(n), 0, i). - Wrap the body in an immediately invoked function that takes
ias a parameter. This is the pre-ES6 answer.
- Use
- Question 5 of 15JavaScript
PhonePe interviewer
Run a list of async tasks with at most K running at the same time.
Model answer
async function runWithLimit(tasks, limit) { const results = new Array(tasks.length); let next = 0; async function worker() { while (next < tasks.length) { const index = next++; results[index] = await tasks[index](); } } const workers = Array.from( { length: Math.min(limit, tasks.length) }, worker ); await Promise.all(workers); return results; }tasksmust be functions that return promises. A promise has already started by the time you hold it, so an array of promises cannot be limited.Each worker pulls the next index until none are left.
next++is safe because JavaScript is single-threaded: nothing else runs between reading and incrementing.Follow-ups: keep going when one task fails (catch inside the worker and store the error), add a per-task timeout, and support adding tasks while it runs.
- Question 6 of 15JavaScript
PhonePe interviewer
Implement an LRU cache with O(1) get and put.
Model answer
A JavaScript
Mapremembers insertion order, so the first key is always the oldest. Deleting and re-inserting a key on every read keeps the least recently used entry at the front.class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); } get(key) { if (!this.map.has(key)) return undefined; const value = this.map.get(key); this.map.delete(key); this.map.set(key, value); // now the most recent return value; } put(key, value) { this.map.delete(key); this.map.set(key, value); if (this.map.size > this.capacity) { const oldest = this.map.keys().next().value; this.map.delete(oldest); } } }Many interviewers then ask for the language-independent version: a hash map for lookup plus a doubly linked list for order, with the map pointing at list nodes. Be ready to write that too.
TypeScript
2 questions- Question 7 of 15TypeScript
PhonePe interviewer
Explain any, unknown and never.
Model answer
anyswitches type checking off for a value, and it spreads: anything derived from it is alsoany.unknownalso accepts any value, but you cannot use it until you narrow it. It is the right type for parsed JSON, a caught error and anything from outside your program.neveris the type with no values. A function that always throws returnsnever, and a union that has been fully narrowed isnever. That gives you an exhaustiveness check:type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number }; function area(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.r ** 2; case 'square': return shape.side ** 2; default: { // Add a new Shape and forget a case: this line stops compiling. const unreachable: never = shape; throw new Error('Unhandled shape: ' + JSON.stringify(unreachable)); } } } - Question 8 of 15TypeScript
PhonePe interviewer
What does it mean that TypeScript is structurally typed?
Model answer
TypeScript compares types by their shape, not by their name. A value is assignable to a type if it has at least the required properties with compatible types.
interface Point { x: number; y: number; } const marker = { x: 1, y: 2, label: 'home' }; const point: Point = marker; // fine: it has x and y const direct: Point = { x: 1, y: 2, label: 'home' }; // errorThe second assignment fails because of excess property checking, a special rule for object literals written directly where a type is expected. It exists to catch typos.
One consequence: two types with the same shape are interchangeable, so a
UserIdand anOrderIdthat are bothstringcan be mixed up. Branded types fix that:type UserId = string & { readonly __brand: 'UserId' }; type OrderId = string & { readonly __brand: 'OrderId' };
React
3 questions- Question 9 of 15React
PhonePe interviewer
Is setState asynchronous? What is batching?
Model answer
Calling a state setter does not change the variable in the code that is running. It asks React to render again with the new value. Each render sees its own fixed snapshot of state.
const [count, setCount] = useState(0); function addThreeWrong() { setCount(count + 1); setCount(count + 1); setCount(count + 1); // count is 0 in all three: result is 1 } function addThree() { setCount((c) => c + 1); setCount((c) => c + 1); setCount((c) => c + 1); // each gets the pending value: result is 3 }Batching means React collects all the updates made in one tick and renders once. Since React 18 this happens everywhere, including inside timeouts, promises and native event handlers. Before 18 it only happened in React event handlers.
flushSyncforces an immediate render when you need the DOM updated straight away, for example to measure it. It is rarely needed. - Question 10 of 15React
PhonePe interviewer
What is an error boundary, and what does it not catch?
Model answer
An error boundary is a component that catches an error thrown while rendering anything below it and shows a fallback instead of unmounting the whole app.
It is still written as a class, with
static getDerivedStateFromErrorto switch to the fallback andcomponentDidCatchto log. Most teams use thereact-error-boundarypackage instead of writing their own.<ErrorBoundary fallback={<p>Could not load orders.</p>} onError={report}> <Orders /> </ErrorBoundary>It does not catch:
- Errors in event handlers. Use
try/catchthere. - Errors in asynchronous code such as timers and promise callbacks.
- Errors thrown in the boundary itself.
Place one around each route, and a smaller one around any widget that can fail on its own, so one broken chart does not blank the page. Give the user a way to retry, usually by resetting the boundary.
- Errors in event handlers. Use
- Question 11 of 15React
PhonePe interviewer
When would you use Context, and what is its performance problem?
Model answer
Context passes a value to any component below a provider without threading it through props. It suits values that many components read and that change rarely: theme, locale, the signed-in user.
The problem: when the provider's value changes, every component that reads that context re-renders, even if the part it uses is the same. Context has no selectors.
Ways to limit that:
- Memoise the value object, so the provider's own re-render does not create a new one each time.
- Split one large context into several, and put state and its setter functions in separate contexts.
- Keep fast-changing values, such as a text input or a mouse position, out of context.
- When many components need different slices of shared state, use a store with selectors (Zustand, Redux Toolkit, or
useSyncExternalStoredirectly).
Context is a way to deliver a value, not a state manager. The state still lives in a
useStateoruseReducersomewhere.
Browser, performance and system design
2 questions- Question 12 of 15Browser, performance and system design
PhonePe interviewer
What are XSS and CSRF, and how do you prevent them?
Model answer
XSS (cross-site scripting): an attacker gets their script to run in your page, with your user's access. It can be stored (saved in a comment and shown to others), reflected (bounced back from a URL parameter) or DOM-based (your own JavaScript writes untrusted text into the page).
- Escape everything you render. React does this by default;
dangerouslySetInnerHTMLandinnerHTMLare the holes. - When you have to render user-supplied HTML, sanitise it with a library such as DOMPurify.
- Add a Content Security Policy that blocks inline scripts and unknown sources.
- Keep session cookies
HttpOnlyto limit the damage. - Do not put user input into
hreforsrcwithout checking the scheme, becausejavascript:URLs execute.
CSRF (cross-site request forgery): another site makes the user's browser send a request to yours, and the browser attaches their cookies.
- Set
SameSiteon cookies. - Require a CSRF token, or check the
Originheader, on requests that change data. - Never change data in response to a GET.
Also worth a sentence:
frame-ancestorsin the CSP stops clickjacking, and anything in the frontend bundle is public, so API secrets cannot live there. - Escape everything you render. React does this by default;
- Question 13 of 15Browser, performance and system design
PhonePe interviewer
Compare cookies, localStorage and sessionStorage. Where should an auth token be stored?
Model answer
- Cookies: about 4 KB each, sent to the server with every request to their domain. They can have an expiry and security flags.
- localStorage: around 5 MB per origin, kept until cleared, shared by all tabs. Never sent to the server. The API is synchronous.
- sessionStorage: the same API, limited to one tab and cleared when it closes.
- IndexedDB: a larger, asynchronous database for structured data, used for offline apps.
For a session or auth token, the safest common choice is a cookie with three flags:
HttpOnly: JavaScript cannot read it, so a script injected through XSS cannot steal it.Secure: sent over HTTPS only.SameSite=LaxorStrict: not sent on most cross-site requests, which blocks most CSRF.
Anything in
localStoragecan be read by any script running on the page, including a compromised dependency. That is why long-lived tokens do not belong there. If a design does keep a token in JavaScript, keep it in memory and make it short-lived, with the refresh token in an HttpOnly cookie.
Machine coding
2 questions- Question 14 of 15Machine coding
PhonePe interviewer
How should you approach a machine coding round?
Model answer
You get 60 to 90 minutes to build a small working app or component, often while sharing your screen. Interviewers generally mark, in this order: does it work, is the state and component design sensible, is the code readable, are edge cases handled, and only then the extras.
- Spend the first five minutes on requirements. Ask what is required and what is a bonus, and confirm whether a framework and libraries are allowed. Some companies ask for plain JavaScript.
- Sketch the component tree and the shape of the state before writing code. Say it out loud.
- Build the main flow end to end first, however plain it looks. A working core beats three half-built features.
- Then add loading, empty and error states, and keyboard access.
- Style last, and keep it simple and tidy.
- Leave five minutes to run through it and remove dead code.
Common ways to fail: starting with CSS, building abstractions for requirements nobody gave, keeping derived values in state so that they go out of sync, and never running the code until the end.
- Question 15 of 15Machine coding
PhonePe interviewer
Build a file explorer with nested folders.
Model answer
The data is a tree, so the component is recursive: a folder renders its children with the same component.
function Node({ node, depth = 0 }) { const [open, setOpen] = useState(false); if (!node.children) { return <div style={{ paddingLeft: depth * 16 }}>{node.name}</div>; } return ( <div> <button style={{ paddingLeft: depth * 16 }} aria-expanded={open} onClick={() => setOpen((value) => !value)} > {open ? '▾' : '▸'} {node.name} </button> {open && node.children.map((child) => ( <Node key={child.id} node={child} depth={depth + 1} /> ))} </div> ); }Expanding and collapsing is the easy part. The marks are in adding, renaming and deleting, which need the tree updated without mutating it:
- Write a recursive helper that returns a new tree with one node changed, or
- Store nodes in a flat map by id, each with a
parentIdand a list of child ids. Updates then touch one or two records. Say why you chose one. - Sort folders before files, and validate names (not empty, not a duplicate within the folder).
Follow-ups: loading a folder's children only when it is opened, keyboard navigation with the tree role and arrow keys, and remembering which folders were open.
Practise for PhonePe
Reading answers is the easy half. These problems run in the browser against real tests.
