Flipkart Frontend Interview Questions
UI Engineer loop built around a machine coding round.
Flipkart hires frontend engineers under the UI Engineer title. Its web and app surfaces serve very large traffic, with sharp peaks during sale events, on a wide range of phones and networks. It was also an early adopter of progressive web apps. Performance on low-end devices is a recurring theme.
The loop is well known for its machine coding round. Candidates commonly describe that round, a problem-solving round and a UI technology round as the core.
- Questions
- 15, 5 with free answers
- Interview rounds
- 5
- Last reviewed
- October 2026
Flipkart 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
Around 90 minutes to build a working app such as a cart, a carousel or a feed, followed by a review of your code.
- 2
Problem solving and data structures
Algorithm questions at a moderate level.
- 3
UI technology
JavaScript internals, polyfills, browser rendering, React and performance.
- 4
Frontend design
For SDE 2 and above: design a page or a widget, covering data flow, caching and performance.
- 5
Hiring manager
Past work, ownership and team fit.
What interviewers tend to look for
- A working, modular solution in the build round. Unfinished clever code scores below finished simple code.
- Polyfills and JavaScript internals written by hand.
- Rendering performance: long lists, images, and what is slow on a cheap phone.
- Clear separation between data, state and view.
Your Flipkart 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
Flipkart interviewer
Explain the event loop. In what order do a setTimeout, a promise callback and synchronous logs run?
Model answer
JavaScript runs on one call stack. When the stack is empty, the event loop first runs every queued microtask (promise callbacks,
queueMicrotask,MutationObserver), then takes one macrotask (a timer, an I/O callback, a UI event), and the browser may render in between.console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D'); // A, D, C, BSynchronous code finishes first (A, D). The promise callback is a microtask, so it runs before the timer, which is a macrotask, even with a 0 ms delay.
With
asyncfunctions, everything after anawaitis scheduled as a microtask. A common follow-up is a longer snippet mixing both; work through it queue by queue instead of guessing. - Question 2 of 15JavaScript
Flipkart 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
Flipkart interviewer
Write a polyfill for Array.prototype.reduce.
Model answer
Array.prototype.myReduce = function (callback, initialValue) { let index = 0; let accumulator = initialValue; if (arguments.length < 2) { // No initial value: use the first element that exists. while (index < this.length && !(index in this)) index++; if (index >= this.length) { throw new TypeError('Reduce of empty array with no initial value'); } accumulator = this[index++]; } for (; index < this.length; index++) { if (index in this) { accumulator = callback(accumulator, this[index], index, this); } } return accumulator; };The details that separate answers: checking
arguments.lengthinstead ofinitialValue === undefined(passingundefinedon purpose is valid), throwing on an empty array with no initial value, and skipping holes in sparse arrays.mapandfilterare the usual warm-ups. Both take the same(value, index, array)callback and an optionalthisArg. - Question 4 of 15JavaScript
Flipkart 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 5 of 15JavaScript
Flipkart 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
Flipkart interviewer
Implement an EventEmitter with on, off, once and emit.
Model answer
class EventEmitter { #listeners = new Map(); on(event, listener) { if (!this.#listeners.has(event)) this.#listeners.set(event, new Set()); this.#listeners.get(event).add(listener); return () => this.off(event, listener); } off(event, listener) { this.#listeners.get(event)?.delete(listener); } once(event, listener) { const unsubscribe = this.on(event, (...args) => { unsubscribe(); listener(...args); }); return unsubscribe; } emit(event, ...args) { // Copy first: a listener may remove itself while we iterate. for (const listener of [...(this.#listeners.get(event) ?? [])]) { listener(...args); } } }Points to raise:
onreturns an unsubscribe function,emititerates over a copy, and aSetprevents the same listener being added twice. Ask whether one listener throwing should stop the others; wrapping each call intry/catchis the usual answer.
TypeScript
1 question- Question 7 of 15TypeScript
Flipkart interviewer
Which utility types do you use, and can you implement Pick and Omit yourself?
Model answer
The everyday ones:
Partial,Required,Readonly,Pick,Omit,Record,ReturnType,Parameters,AwaitedandNonNullable.They are built from mapped types, which loop over keys with
in:type MyPartial<T> = { [K in keyof T]?: T[K] }; type MyPick<T, K extends keyof T> = { [P in K]: T[P] }; type MyOmit<T, K extends PropertyKey> = MyPick<T, Exclude<keyof T, K>>; type MyRecord<K extends PropertyKey, V> = { [P in K]: V };A typical use is deriving one type from another so they cannot drift apart:
interface User { id: string; name: string; email: string; } type NewUser = Omit<User, 'id'>; type UserPatch = Partial<NewUser>;A common harder follow-up is
DeepPartialorDeepReadonly, which apply the same mapping recursively.
React
3 questions- Question 8 of 15React
Flipkart interviewer
How does React decide what to update in the DOM? Why do list items need keys?
Model answer
Rendering calls your components and produces a tree of elements. React compares that tree with the previous one (reconciliation) and then applies only the differences to the DOM, in a separate commit step.
Two rules keep the comparison fast:
- A different element type at the same position (a
divbecoming asection, orLoginbecomingDashboard) means React throws that subtree away and builds a new one. Its state is lost. - The same type at the same position means React keeps the instance and its state and updates the props.
In a list, position is not enough, because items move. A
keytells React which item is which between renders. With the array index as the key, inserting at the top makes every item look changed, and state such as a typed input value ends up attached to the wrong row. Use a stable id from the data.A useful trick follows from the same rule: changing a component's
keyon purpose resets its state. - A different element type at the same position (a
- Question 9 of 15React
Flipkart interviewer
How would you render a list of 10,000 rows without the page slowing down?
Model answer
Render only the rows that are on screen. This is called windowing or virtualisation.
The container gets the full scroll height (row count times row height). From the scroll position you work out which rows are visible, render those plus a few extra above and below, and position them with a transform. Scrolling changes which twenty or so rows exist.
In practice use a library such as TanStack Virtual or react-window. Writing a fixed-height version by hand is a common interview task.
Things to raise:
- Rows of unknown height have to be measured after they render.
- Fetch the data in pages too. Virtualising 10,000 rows you downloaded in one request only solves half the problem.
- Use stable keys and memoised rows.
- The browser's find-in-page cannot see rows that are not rendered, and screen readers need
aria-rowcountandaria-rowindex. content-visibility: autoin CSS is a cheaper option for moderately long pages.
- Question 10 of 15React
Flipkart interviewer
What changed in React 19?
Model answer
The main additions deal with forms and async updates:
- Actions: pass an async function to a form's
actionprop or tostartTransition. React tracks the pending state and errors for you. useActionStatereturns the result of the last submission and a pending flag.useFormStatusreads the pending state of the parent form from inside a child such as a submit button.useOptimisticshows the expected result immediately and goes back to the real state when the action finishes.usereads a promise or a context during render. Unlike other hooks it can be called inside conditions and loops.refis a normal prop on function components, soforwardRefis no longer needed.<title>,<meta>and<link>rendered anywhere in the tree are moved into the document head.- A context can be rendered directly as a provider:
<ThemeContext value={theme}>.
function Signup() { const [error, submit, isPending] = useActionState(async (previous, formData) => { const result = await createUser(formData.get('email')); return result.ok ? null : result.message; }, null); return ( <form action={submit}> <input name="email" type="email" required /> <button disabled={isPending}>Sign up</button> {error && <p role="alert">{error}</p>} </form> ); } - Actions: pass an async function to a form's
CSS
1 question- Question 11 of 15CSS
Flipkart interviewer
What is the difference between reflow and repaint? How do you keep animations smooth?
Model answer
The browser renders in steps: style, layout, paint, composite.
- Changing geometry (
width,top,margin, font size, adding nodes) forces layout, also called reflow, for the affected part of the tree, and then a repaint. - Changing appearance only (
color,background,box-shadow) skips layout but still repaints. - Changing
transformoropacitycan be done by the compositor alone, without layout or paint. These are the properties to animate.
/* Janky: recalculates layout every frame */ .panel { transition: left 200ms; } /* Smooth: handled by the compositor */ .panel { transition: transform 200ms; } .panel.open { transform: translateX(240px); }Layout thrashing is the other common cause of jank. Reading a layout value such as
offsetHeightorgetBoundingClientRect()straight after writing a style forces the browser to run layout immediately. In a loop that is one forced layout per iteration. Do all the reads first, then all the writes, or schedule writes inrequestAnimationFrame.will-changehints that an element will animate. Use it on a few elements, since each one takes memory. - Changing geometry (
Browser, performance and system design
1 question- Question 12 of 15Browser, performance and system design
Flipkart interviewer
What are the Core Web Vitals, and how do you improve each one?
Model answer
- LCP (Largest Contentful Paint): when the largest image or text block in the first screen is drawn. Good is 2.5 seconds or less. Improve it with a faster server response, a correctly sized and prioritised hero image, fewer render-blocking files, and server-rendered content.
- INP (Interaction to Next Paint): how long the page takes to visibly respond to a click, tap or key press. It replaced FID in 2024. Good is 200 ms or less. Improve it by breaking up long tasks, shipping less JavaScript, avoiding large re-renders on input, and moving heavy work off the main thread.
- CLS (Cumulative Layout Shift): how much content moves unexpectedly. Good is 0.1 or less. Improve it by giving images, ads and embeds a reserved size, by not inserting content above what the user is reading, and by loading fonts with matching fallbacks.
Each is judged at the 75th percentile of real visits, not the average.
Know the two kinds of data. Lab data from Lighthouse is one run on one simulated device, useful for debugging. Field data from real users, collected with the
web-vitalslibrary or from the Chrome UX Report, is what counts. INP in particular can only be measured properly in the field.
Machine coding
3 questions- Question 13 of 15Machine coding
Flipkart 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 14 of 15Machine coding
Flipkart interviewer
Build a product list with a shopping cart.
Model answer
Store the cart as a map from product id to quantity. Totals, item counts and line prices are derived from that and the product list; they are not stored.
function cartReducer(cart, action) { switch (action.type) { case 'add': return { ...cart, [action.id]: (cart[action.id] ?? 0) + 1 }; case 'setQuantity': { if (action.quantity <= 0) { const { [action.id]: removed, ...rest } = cart; return rest; } return { ...cart, [action.id]: action.quantity }; } case 'clear': return {}; default: return cart; } } const totalPaise = Object.entries(cart).reduce( (sum, [id, quantity]) => sum + productsById[id].pricePaise * quantity, 0 );- Adding a product that is already in the cart raises its quantity. It does not add a second line.
- A quantity stepper with a minimum of 1, or removal at 0, and a maximum from stock.
- Keep money as whole paise (or cents) and format only for display, so that floating point errors never reach a price.
- An empty cart state.
- Save the cart to
localStorage.
Follow-ups: coupon codes and tax, sharing the cart across pages with context or a store, and syncing with a server optimistically.
- Question 15 of 15Machine coding
Flipkart interviewer
Build an infinite scrolling list.
Model answer
Put an empty sentinel element after the list and watch it with an
IntersectionObserver. When it comes into view, load the next page.const sentinelRef = useRef(null); useEffect(() => { const node = sentinelRef.current; if (!node || !hasMore) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !loading) loadNextPage(); }, { rootMargin: '200px' } // start a little before the end ); observer.observe(node); return () => observer.disconnect(); }, [hasMore, loading, loadNextPage]);- Track
items,cursor(or page),loading,hasMoreanderror. - Never send the same page request twice. The
loadingcheck above does that. - Append to the list; do not replace it. Use stable ids as keys.
- Show a loading row, an end-of-list message, and a retry button when a page fails.
- Stop observing when there is nothing more to load.
Follow-ups: why not a scroll listener (it fires constantly and needs throttling and manual maths), how to handle a list that grows to thousands of rows (virtualise), and how to restore the scroll position when the user comes back.
- Track
Practise for Flipkart
Reading answers is the easy half. These problems run in the browser against real tests.
