Swiggy Frontend Interview Questions
Consumer app at scale. Async JavaScript and a build round.
Swiggy's products, food delivery, Instamart and Dineout, are used mostly on phones, often on slow networks, and include live states such as order tracking. Frontend interviews reflect that: async JavaScript, performance and real-time updates come up often.
Candidates commonly describe a JavaScript round, a machine coding round and a problem-solving round, with a design round for senior roles.
- Questions
- 15, 5 with free answers
- Interview rounds
- 5
- Last reviewed
- October 2026
Swiggy frontend interview process
The usual shape of the loop. A guide to what to prepare, not a promise of what you will get.
- 1
JavaScript fundamentals
Closures, promises, the event loop and writing small utilities.
- 2
Machine coding
Build a small feature such as a cart, a search box or a timer in 60 to 90 minutes, usually in React.
- 3
Problem solving
Data structure and algorithm questions at an easy to moderate level.
- 4
Frontend system design
For senior roles: design a screen such as live order tracking or a restaurant listing.
- 5
Hiring manager
Project deep dive and how you handle trade-offs.
What interviewers tend to look for
- Async code that survives slow and failing networks: retries, cancellation and stale responses.
- Mobile web performance: bundle size, images and the first load.
- Real-time updates and when to use polling, SSE or a WebSocket.
- Modelling state such as a cart so that totals cannot go out of sync.
Your Swiggy 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
5 questions- Question 1 of 15JavaScript
Swiggy 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
Swiggy interviewer
Implement Promise.all.
Model answer
function promiseAll(iterable) { return new Promise((resolve, reject) => { const items = Array.from(iterable); const results = new Array(items.length); let pending = items.length; if (pending === 0) return resolve(results); items.forEach((item, index) => { Promise.resolve(item).then((value) => { results[index] = value; // by index, not by finish order pending -= 1; if (pending === 0) resolve(results); }, reject); }); }); }What the interviewer checks:
- Results keep the input order, whichever promise finishes first.
- Plain values are accepted, which is what
Promise.resolve(item)handles. - The first rejection rejects the whole thing.
- An empty input resolves straight away with an empty array.
Expect a follow-up asking for
Promise.allSettled,Promise.anyorPromise.race. - Question 3 of 15JavaScript
Swiggy interviewer
Write a function that retries a failing async call with exponential backoff.
Model answer
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function retry(fn, { retries = 3, delay = 300 } = {}) { for (let attempt = 0; ; attempt++) { try { return await fn(); } catch (error) { if (attempt >= retries) throw error; await wait(delay * 2 ** attempt); // 300, 600, 1200 ms } } }What a strong answer adds without being asked:
- Retry only what can succeed later: network errors, 5xx and 429. A 400 or a 401 will fail the same way again.
- Retry only idempotent requests, or use an idempotency key, so a payment is not made twice.
- Add random jitter to the delay so many clients do not retry at the same instant.
- Cap the delay and accept an
AbortSignalso the caller can cancel.
- Question 4 of 15JavaScript
Swiggy interviewer
Compare Promise.all, allSettled, race and any. How do you run async calls in parallel with async/await?
Model answer
Promise.all: fulfils with every value, rejects on the first rejection.Promise.allSettled: never rejects. It reports the outcome of each promise.Promise.race: settles with whichever promise settles first, fulfilled or rejected.Promise.any: fulfils with the first success, and rejects with anAggregateErroronly if all fail.
A common mistake is awaiting inside a loop, which runs the calls one after another. Start them first, then await them together:
// Sequential: the second call waits for the first const user = await getUser(id); const orders = await getOrders(id); // Parallel: both start immediately const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);Wrap awaited calls in
try/catch. UseallSettledwhen one failure should not throw away the other results. - Question 5 of 15JavaScript
Swiggy interviewer
What is the difference between a shallow and a deep copy? How would you deep clone an object?
Model answer
A shallow copy (
{ ...obj },Object.assign,arr.slice()) copies one level. Nested objects are still shared with the original.structuredClone(value)is the built-in deep clone. It handles dates, maps, sets, typed arrays and circular references. It throws on functions and DOM nodes and does not keep the prototype chain.JSON.parse(JSON.stringify(value))is weaker: it dropsundefinedand functions, turns dates into strings and fails on cycles.Interviewers usually want it written by hand:
function deepClone(value, seen = new WeakMap()) { if (value === null || typeof value !== 'object') return value; if (seen.has(value)) return seen.get(value); // circular reference const copy = Array.isArray(value) ? [] : {}; seen.set(value, copy); for (const key of Reflect.ownKeys(value)) { copy[key] = deepClone(value[key], seen); } return copy; }Say what it does not cover (Date, Map, Set, class instances) and how you would add each one.
React
3 questions- Question 6 of 15React
Swiggy interviewer
How does useEffect work? Explain the dependency array, cleanup and the common mistakes.
Model answer
An effect runs after React has committed a render. Its job is to keep something outside React in step with your state: a subscription, a timer, a DOM API or a network request.
- No dependency array: runs after every render.
- An empty array: runs once after the first render.
- With dependencies: runs again when any of them changes, compared with
Object.is. - The function you return is the cleanup. It runs before the next run of the effect and when the component unmounts.
useEffect(() => { const controller = new AbortController(); fetch('/api/users/' + id, { signal: controller.signal }) .then((response) => response.json()) .then(setUser) .catch((error) => { if (error.name !== 'AbortError') setError(error); }); return () => controller.abort(); }, [id]);Mistakes interviewers look for: leaving a dependency out and reading a stale value, depending on an object or function that is recreated every render (an endless loop), not handling a response that arrives after
idhas changed, and using an effect to compute something from props that could be computed during render.In development, Strict Mode mounts each component, unmounts it and mounts it again, to expose effects that do not clean up.
- Question 7 of 15React
Swiggy interviewer
Why does a setInterval inside useEffect keep seeing the old state? How do you fix it?
Model answer
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); // count is always 0 here }, 1000); return () => clearInterval(id); }, []); return <p>{count}</p>; // goes to 1 and stays there }Every render has its own
count. The effect runs once, so its callback closes over thecountfrom the first render, which is 0, and keeps setting the state to 1. This is a stale closure.Fixes:
- Use the updater form,
setCount((c) => c + 1). It does not readcountfrom the closure at all. This is the right fix here. - Add
countto the dependency array. That is correct, but it tears down and recreates the interval every second. - Keep the latest value in a ref when the callback needs to read state without restarting.
The
react-hooks/exhaustive-depslint rule catches this. Explain why it warns instead of silencing it. - Use the updater form,
- Question 8 of 15React
Swiggy interviewer
What is hydration, and what causes a hydration mismatch?
Model answer
With server rendering, the browser receives finished HTML. Hydration is React running your components in the browser and attaching event handlers and state to that existing HTML instead of creating it again. Until it finishes, the page is visible but not interactive.
A mismatch happens when the first render in the browser produces something different from the server's HTML. Causes:
Date.now(),Math.random()or a locale-dependent format used during render.- Reading
window,localStorageor the screen size during render. - Invalid nesting, such as a
divinside ap, which the browser repairs before React sees it. - Browser extensions that edit the page.
Fixes: render the same output in both places, move browser-only values into
useEffectso that they appear after hydration, or render that part on the client only.suppressHydrationWarningis for a single value that cannot match, such as a timestamp.Hydration cost grows with the amount of JavaScript. Streaming with Suspense lets React hydrate parts of the page independently, and Server Components reduce how much there is to hydrate.
Next.js
2 questions- Question 9 of 15Next.js
Swiggy interviewer
Compare SSG, ISR, SSR and CSR. When would you choose each?
Model answer
- SSG (static generation): HTML is built once at build time and served from a CDN. Fastest option. Use it for content that is the same for everyone and changes rarely: marketing pages, docs, blog posts.
- ISR (incremental static regeneration): a static page that is rebuilt in the background after a set time or on demand. Use it for content that changes but can be a few minutes old: product pages, listings.
- SSR (server rendering per request): HTML is built for each request. Use it when the page depends on the request, such as cookies or a search query, or must always be fresh. The cost is a slower first byte and server load.
- CSR (client rendering): the browser builds the page after JavaScript loads. Fine for screens behind a login where search engines do not matter.
The questions that decide it: does the content differ per user, how stale can it be, and does it need to rank in search.
In the App Router you rarely pick a mode by name. A route is static by default and becomes dynamic when it reads something request-specific, such as cookies, headers or search params. One page can mix both, with a static shell and dynamic parts streamed in.
- Question 10 of 15Next.js
Swiggy interviewer
What do next/image and next/font do for performance?
Model answer
next/imagewraps theimgelement and handles the things people forget:- It serves a correctly sized file for each screen through
srcset, in a modern format such as WebP or AVIF. - It lazy-loads images that are off screen.
- It requires
widthandheight(orfillwith a sized parent), so the browser reserves the space and the layout does not jump. - The image that is the largest element above the fold should be marked for eager, high-priority loading so that it does not hurt LCP.
Remote image hosts have to be allowed in
next.config, andsizesshould describe how wide the image is at each breakpoint, otherwise the browser may download a file larger than it needs.next/fontdownloads font files at build time and serves them from your own domain, so there is no extra connection to a font host. It also generates a fallback font with matching metrics, which reduces the shift when the real font loads. - It serves a correctly sized file for each screen through
CSS
1 question- Question 11 of 15CSS
Swiggy interviewer
How do you build a responsive layout?
Model answer
Start mobile-first: the base styles are for the smallest screen and
min-widthmedia queries add to them as space allows..title { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); /* fluid, with limits */ } .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); } .card { container-type: inline-size; } @container (min-width: 28rem) { .card-body { display: flex; /* reacts to the card's width, not the screen's */ } }- Prefer layouts that adapt by themselves, such as wrapping flex rows and
auto-fitgrids, over a breakpoint for every device. - Container queries style a component by the width of its container, so the same card works in a sidebar and in a wide page.
- Serve images with
srcsetandsizes, and give them an aspect ratio. - Include the viewport meta tag. Use
dvhinstead ofvhfor full-height sections on phones. - Keep touch targets large, and respect
prefers-reduced-motionandprefers-color-scheme.
- Prefer layouts that adapt by themselves, such as wrapping flex rows and
Browser, performance and system design
2 questions- Question 12 of 15Browser, performance and system design
Swiggy interviewer
Compare polling, Server-Sent Events and WebSockets for real-time updates.
Model answer
- Short polling: ask the server every few seconds. Simple and works everywhere. It wastes requests when nothing has changed, and updates are late by up to one interval.
- Long polling: the server holds each request open until it has something to send, then the client asks again. Closer to real time, more connection handling.
- Server-Sent Events: one long-lived HTTP response over which the server pushes text events. One direction only, server to client. The browser's
EventSourcereconnects by itself. Good for notifications, live scores, order status and streamed AI output. - WebSocket: a two-way connection with very little overhead per message. Use it when the client also sends often: chat, collaborative editing, multiplayer, trading screens.
Choose by direction and frequency. If only the server pushes, SSE is simpler to run. If both sides talk frequently, use a WebSocket. If updates are rare, polling is enough.
Operational points that show experience: reconnect with backoff, send heartbeats to detect dead connections, fetch missed events after a reconnect, pause updates when the tab is hidden, and remember that many open connections change how the backend has to scale.
- Question 13 of 15Browser, performance and system design
Swiggy interviewer
How does HTTP caching work? What caching strategy would you use for a web app?
Model answer
The server controls caching with the
Cache-Controlresponse header:max-age=N: reuse the response for N seconds without asking.no-cache: keep a copy, but check with the server before each use.no-store: do not keep it at all.immutable: this will never change, so do not recheck even on reload.s-maxage: a separate lifetime for CDNs.stale-while-revalidate: serve the old copy now and refresh it in the background.
Checking uses validators. The server sends an
ETag; the browser sends it back asIf-None-Match; if nothing changed the server answers304 Not Modifiedwith no body.# JS, CSS and images with a content hash in the file name Cache-Control: public, max-age=31536000, immutable # HTML Cache-Control: no-cacheThat pair is the standard strategy. Hashed assets are cached for a year because any change produces a new file name. The HTML is always revalidated, so a new deployment is picked up at once and points at the new assets.
Other layers to mention: the CDN, a service worker for offline use, and the in-memory data cache of a library such as TanStack Query.
Machine coding
2 questions- Question 14 of 15Machine coding
Swiggy interviewer
Build a typeahead search box that fetches suggestions as the user types.
Model answer
State:
query,results,status(idle, loading, error) andactiveIndexfor the highlighted row.useEffect(() => { if (query.trim().length < 2) { setResults([]); return; } const controller = new AbortController(); const timer = setTimeout(async () => { try { setStatus('loading'); const res = await fetch('/api/search?q=' + encodeURIComponent(query), { signal: controller.signal, }); setResults(await res.json()); setStatus('idle'); } catch (error) { if (error.name !== 'AbortError') setStatus('error'); } }, 300); return () => { clearTimeout(timer); // debounce controller.abort(); // drop the stale request }; }, [query]);What gets marked:
- Debouncing, and not showing results for an older query after a newer one.
- Arrow keys move the highlight, Enter selects, Escape closes, and a click outside closes.
- Loading, no results and error states.
- A small cache so that repeated queries do not refetch.
- Combobox ARIA attributes on the input and list.
- Question 15 of 15Machine coding
Swiggy interviewer
Build a countdown timer or a stopwatch with start, pause and reset.
Model answer
The trap is counting ticks.
setIntervalis not exact, and browsers slow timers down in background tabs, so a counter that subtracts one every second drifts. Store timestamps and compute the time from the clock.function useCountdown(durationMs) { const [remaining, setRemaining] = useState(durationMs); const [running, setRunning] = useState(false); const endTime = useRef(0); useEffect(() => { if (!running) return; const id = setInterval(() => { const left = Math.max(0, endTime.current - Date.now()); setRemaining(left); if (left === 0) setRunning(false); }, 250); return () => clearInterval(id); }, [running]); const start = () => { endTime.current = Date.now() + remaining; // resume from where it paused setRunning(true); }; const pause = () => setRunning(false); const reset = () => { setRunning(false); setRemaining(durationMs); }; return { remaining, running, start, pause, reset }; }- The interval only refreshes the display. The remaining time always comes from
Date.now(). - Pause keeps the remaining time. Start computes a new end time from it.
- The interval is cleared on pause, on finish and on unmount.
- Format as
mm:sswith leading zeros, and disable Start while running.
Follow-ups: several timers on one page, laps for a stopwatch, and surviving a page refresh by saving the end time.
- The interval only refreshes the display. The remaining time always comes from
Practise for Swiggy
Reading answers is the easy half. These problems run in the browser against real tests.
