Zerodha Frontend Interview Questions
Small team. Fundamentals and simple, fast software.
Zerodha is unusual among Indian tech companies. It has said publicly that it keeps its technology team small and prefers simple, fast, dependency-light software, and its Kite web app is built with Vue, not React. Hiring is less of a fixed multi-round loop than at larger companies.
Prepare for fundamentals over framework trivia: how the browser works, plain JavaScript and CSS, performance, and being able to explain decisions in your own past work. The React questions below are included because the concepts carry over, but do not expect a React-specific interview.
- Questions
- 15, 5 with free answers
- Interview rounds
- 4
- Last reviewed
- October 2026
Zerodha frontend interview process
The usual shape of the loop. A guide to what to prepare, not a promise of what you will get.
- 1
Application review
Your past work, open-source contributions and how you write about them carry real weight.
- 2
Task or technical conversation
A practical task or a long discussion of something you have built. The format varies.
- 3
Technical deep dive
Fundamentals, performance and the trade-offs behind your choices.
- 4
Team conversation
How you work and whether the small-team style suits you.
What interviewers tend to look for
- Browser and network fundamentals: rendering, caching, script loading.
- Performance and small bundles, with a reason for every dependency.
- JavaScript that does not depend on a framework.
- Clear reasoning about work you have shipped.
Your Zerodha 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
Zerodha interviewer
What is a closure? Give a practical use for one.
Model answer
A closure is a function together with the scope it was created in. The function keeps access to the variables of that scope even after the outer function has returned.
function createCounter() { let count = 0; // private: nothing outside can touch it return { increment: () => ++count, value: () => count, }; } const counter = createCounter(); counter.increment(); counter.value(); // 1Practical uses: private state as above, function factories, and utilities such as
debounceandmemoize, which keep a timer or a cache between calls.The cost to mention: a closure keeps its captured variables alive for as long as the function is reachable, so a long-lived callback holding a large object is a memory leak.
- Question 2 of 15JavaScript
Zerodha interviewer
How does prototypal inheritance work, and what does the
classsyntax add?Model answer
Every object has a hidden link to another object, its prototype. When you read a property that the object does not have, the engine looks on the prototype, then on that object's prototype, and so on until it finds the property or reaches
null.A function's
prototypeproperty becomes the prototype of the objects it creates withnew. That is how all arrays share one copy ofmap.classis mostly syntax over the same mechanism: methods go on the prototype,extendslinks the chain andsupercalls the parent. It does add a few real things:#privatefields, static blocks, and the rule that a class cannot be called withoutnew.Object.create(proto)creates an object with a chosen prototype directly, andObject.getPrototypeOf(obj)reads it. - Question 3 of 15JavaScript
Zerodha interviewer
What is event delegation? Explain bubbling and capturing.
Model answer
A DOM event travels in three phases: down from
windowto the target (capturing), at the target, then back up towindow(bubbling). Listeners run in the bubbling phase unless you pass{ capture: true }.Delegation uses bubbling: you put one listener on a common ancestor and work out which child was clicked.
list.addEventListener('click', (event) => { const item = event.target.closest('li[data-id]'); if (!item || !list.contains(item)) return; select(item.dataset.id); });Benefits: one listener instead of hundreds, and it keeps working for items added later.
Details that come up:
stopPropagationstops the event travelling,preventDefaultcancels the browser's default action, and a few events such asfocusandblurdo not bubble (usefocusinandfocusout). - Question 4 of 15JavaScript
Zerodha interviewer
How do ES modules differ from CommonJS, and why does tree shaking depend on ES modules?
Model answer
ES module
importandexportstatements are static. They sit at the top level and are resolved before any code runs, so a bundler can see exactly which exports are used and drop the rest. That removal is tree shaking.CommonJS
requireis an ordinary function call. It can run inside anif, with a computed path, and it returns a plain object, so the bundler cannot be sure what is unused.Other differences: ES module imports are live bindings to the exported variable, ES modules load asynchronously and support top-level
await, and they always run in strict mode.Tree shaking also needs the library to be side-effect free, which packages declare with
"sideEffects": falseinpackage.json. Dynamicimport()is the tool for code splitting: it returns a promise and creates a separate chunk. - Question 5 of 15JavaScript
Zerodha interviewer
What causes memory leaks in a frontend app, and how do you find one?
Model answer
A leak is memory that is no longer needed but is still reachable, so the garbage collector cannot free it. Usual causes:
- Event listeners, timers and subscriptions that are never removed.
- DOM nodes removed from the page but still referenced from JavaScript.
- Caches, arrays and maps that only ever grow.
- Closures that keep a large object alive.
- In React, an effect with no cleanup function.
To find one, open the Memory panel in Chrome DevTools. Take a heap snapshot, repeat the suspect action several times (open and close a modal, change route and come back), take another, and compare. Objects whose count keeps growing are the leak; the retainers view shows what holds them.
Fixes: always return a cleanup from effects, cancel requests with
AbortController, and useWeakMaporWeakReffor caches keyed by objects. - Question 6 of 15JavaScript
Zerodha 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
Zerodha 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
1 question- Question 8 of 15React
Zerodha interviewer
How do you decide where state should live, and when would you add a state management library?
Model answer
Sort the state by kind first, because each kind has a natural home:
- Local UI state (is this menu open):
useStateoruseReducerin the component. - State shared by a few siblings: lift it to their nearest common parent.
- App-wide client state that changes rarely (theme, auth): Context.
- Server state, meaning data fetched from an API: a cache library such as TanStack Query, SWR or RTK Query, or the framework's own data layer. It handles caching, deduplication, retries and refetching.
- State that should survive a refresh or be shareable (filters, page number, selected tab): the URL.
Most of what teams once put in a global store was server state. With that handled by a cache, the remaining shared client state is often small.
Reach for Redux Toolkit or Zustand when many distant components read and write the same client state, when updates need to be traceable, or when Context re-renders become a measured problem.
- Local UI state (is this menu open):
CSS
3 questions- Question 9 of 15CSS
Zerodha interviewer
Explain the CSS box model and box-sizing.
Model answer
Every element is a box made of four layers: content, padding, border and margin.
box-sizingdecides whatwidthandheightmeasure. With the default,content-box, they set the content area only, so a 200px wide box with 20px padding and a 1px border takes 242px. Withborder-box, the padding and border are included in the 200px. That is easier to reason about, which is why most projects start with:*, *::before, *::after { box-sizing: border-box; }Related points that get asked:
- Vertical margins of adjacent block elements collapse into the larger of the two. This does not happen inside flex or grid containers.
- Inline elements ignore
width,heightand vertical margins.inline-blockaccepts them. outlineis drawn outside the border and does not take up space.
- Question 10 of 15CSS
Zerodha interviewer
How does the browser decide which CSS rule wins?
Model answer
When several declarations target the same property on the same element, the cascade checks, in order:
- Origin and importance:
!importantdeclarations beat normal ones. - Cascade layers: for normal declarations, a later
@layerbeats an earlier one, and styles outside any layer beat all layers. - Specificity.
- Source order: the last one written wins.
Specificity is compared as three numbers: ids, then classes, attributes and pseudo-classes, then element names and pseudo-elements.
#nav .linkis (1,1,0) and beats.menu .item .linkat (0,3,0), because ids are compared first. An inlinestyleattribute beats any selector.Useful details:
:where()always has zero specificity, while:is()and:not()take the specificity of their most specific argument.In practice, keep specificity low and flat by styling with single classes, and use
@layerto order resets, components and utilities instead of fighting with!important. - Origin and importance:
- Question 11 of 15CSS
Zerodha interviewer
Compare CSS Modules, utility classes such as Tailwind, and CSS-in-JS.
Model answer
- CSS Modules: ordinary CSS files whose class names are made unique at build time. No runtime cost and no naming collisions. You still write and name every class.
- Utility classes (Tailwind): you compose small single-purpose classes in the markup. Values come from a fixed scale, which keeps spacing and colour consistent, and the CSS file stops growing because classes are reused. The markup gets long and there is a vocabulary to learn.
- Runtime CSS-in-JS (styled-components, Emotion): styles are written in JavaScript and can depend on props. Styles sit next to the component. The cost is work in the browser on every render to generate and inject them, and they do not fit React Server Components, which cannot run that code.
- Build-time CSS-in-JS (vanilla-extract and similar): styles are written in TypeScript and extracted to static CSS files, which keeps type safety without the runtime cost.
There is no single right answer. The interviewer wants the trade-offs: runtime cost, how well it works with server rendering, how it scales to a large team, and how design tokens are enforced. Say what you have used and what went wrong with it.
Browser, performance and system design
4 questions- Question 12 of 15Browser, performance and system design
Zerodha interviewer
What happens between typing a URL and seeing the page?
Model answer
- DNS turns the domain into an IP address.
- The browser opens a connection: a TCP and TLS handshake, or QUIC for HTTP/3.
- It sends the request and the server starts streaming HTML back.
- The parser builds the DOM as HTML arrives. CSS is downloaded and parsed into the CSSOM. CSS blocks rendering, and a plain
scripttag blocks the parser until it has downloaded and run. - DOM and CSSOM are combined into the render tree, which holds only what is visible.
- Layout works out the size and position of every box, paint fills in the pixels, and compositing puts the layers on screen.
- JavaScript then runs, and in a framework app it hydrates the page so that it responds to input.
This sequence is called the critical rendering path. Most performance work is shortening it: fewer and smaller render-blocking files,
deferon scripts, inlining the CSS needed for the first screen, compression, a CDN close to the user, and caching so that repeat visits skip the network.Interviewers often pick one step and go deeper, so know at least one well.
- Question 13 of 15Browser, performance and system design
Zerodha 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.
- Question 14 of 15Browser, performance and system design
Zerodha interviewer
The JavaScript bundle is too large. How do you reduce it?
Model answer
Measure first. A bundle analyser shows which modules take the space, and the Coverage panel in DevTools shows how much of the loaded code is not used on the page.
- Split by route, so that a page loads only its own code. Frameworks do this by default.
- Lazy-load heavy pieces that are not needed at first paint, such as a chart, a rich text editor or a modal, with
React.lazyor a dynamicimport(). - Check dependencies. Replace large libraries with smaller ones or with built-in APIs (
Intlfor dates and numbers), and import single functions instead of whole packages. - Make tree shaking work. Use ES module builds and named imports.
- Ship modern code. Transpiling for very old browsers adds polyfills and longer output.
- Move work to the server. With Server Components, a library used only to render static content is never downloaded.
- Compress with Brotli and cache hashed files for a long time.
const Chart = lazy(() => import('./Chart')); <Suspense fallback={<ChartSkeleton />}> <Chart data={data} /> </Suspense>Then prevent it coming back: set a size budget in CI so that a pull request that adds 200 KB is noticed.
- Question 15 of 15Browser, performance and system design
Zerodha interviewer
What is the difference between async and defer on a script tag? What are preload and prefetch?
Model answer
- A plain
scriptstops the HTML parser while the file downloads and runs. asyncdownloads in parallel and runs as soon as the file arrives, pausing the parser at that moment. Order is not guaranteed. Use it for independent scripts such as analytics.deferdownloads in parallel and runs after the document has been parsed, in the order the scripts appear, just beforeDOMContentLoaded. Use it for application code.type="module"scripts are deferred by default.
Resource hints tell the browser about things it has not discovered yet:
preload: this page needs the file soon, so fetch it now at high priority. Used for a font, or a hero image referenced from CSS.prefetch: a later navigation will probably need this, so fetch it when idle.preconnect: open the connection to another origin early.fetchpriority="high"on an image raises its priority, which helps LCP.
Preloading too much is a common mistake. Everything marked urgent competes with what really is.
- A plain
Practise for Zerodha
Reading answers is the easy half. These problems run in the browser against real tests.
