Free lesson · JavaScript Basic to Advance
Interview Lens: Browser and DOM Questions
The Question
Browser and DOM interview questions cluster around four patterns:
"Explain event bubbling and event delegation."
"What's the difference between
localStorage,sessionStorage, and cookies?""What happens when I click a button — walk me through the event from click to handler."
"How does
fetchdiffer fromXMLHttpRequest? Why isfetchpreferred?"
These appear at all frontend interview levels but cluster especially at mid-level. They test whether you've actually built browser-based applications, not just used React or another framework that abstracts the DOM away.
I'm Olena. I covered the technical material across Topics 12.2-12.6 with a deliberately lighter voice — reference content benefits from precision over personality. For this Interview Lens, I'm back at full voice, and Arun joins for the interview-strategy framing. Browser questions sit at an interesting intersection: they're foundational enough that a weak answer is disqualifying, but most candidates only know what their framework does for them, so depth signals real frontend experience.
Why They Ask It
Browser APIs are the substrate beneath every frontend framework. React works on top of the DOM. Vue works on top of the DOM. Every reactive framework is doing DOM manipulation underneath whatever virtual abstraction it provides.
A candidate who only knows React can build apps. A candidate who knows React and the DOM underneath can debug those apps when React's abstractions leak, performance-tune them when needed, and reason about edge cases that frameworks don't fully handle.
Three signals interviewers listen for:
Signal 1: Do you understand event delegation, not just events? Adding a click handler to a button is basic. Understanding why a single listener on a parent can handle clicks on hundreds of children — that's the senior-level understanding.
Signal 2: Can you reason about performance? Browser performance questions probe whether you understand reflow vs repaint, why animating transform is fast and animating width is slow, why batched DOM updates matter. This depth separates "uses the DOM" from "understands the DOM."
Signal 3: Do you handle the dark corners? CORS errors. AbortController for fetch cancellation. The "fetch doesn't throw on 404" surprise. The localStorage-is-synchronous-and-blocking detail. These aren't trick questions — they're things that come up in real frontend work, and senior candidates have hit them.
Arun here. The interview question that taught me the most about events came up in my Atlassian rejection, my second failed interview. They asked me "if I have a list with 1000 items and want to handle clicks on each, how would you do it?" I gave the surface answer: "Add an event listener to each item." The interviewer asked, "what if items get added and removed dynamically?" I said I'd add listeners to new items as they were created.
She let me finish, then walked me through event delegation. One listener on the parent. Use event.target to identify which item. Works for current items, works for future items. No memory management of 1000 listeners. I'd never thought about it that way. I learned the pattern that night and have used it constantly ever since.
The lesson: many browser interview questions seem easy until you realize there's a more elegant pattern. Doing the obvious thing isn't wrong, but the senior signal is reaching for the right abstraction.
The Wrong Answers
Warning: The browser-API mistakes interviewers see most often.
"localStorage is asynchronous." It isn't. Both read and write are synchronous and blocking. This often surprises developers used to async-everything in modern JavaScript.
"Cookies and localStorage are the same." They're not. Cookies are sent with every HTTP request automatically. localStorage stays on the client unless your code explicitly sends it. They have different size limits, different security models, different appropriate use cases.
"event.target and event.currentTarget are the same." They're not, especially with delegation.
event.targetis the actual element that fired the event.event.currentTargetis the element the listener is attached to. With delegation, they're usually different."fetch throws if the request fails." It doesn't throw on HTTP error status codes. A 404 or 500 still resolves the Promise. Only network failures reject. Always check
response.ok."You should use innerHTML for everything." No — it's an XSS risk with untrusted content, and it's slower than property-based modification. Use
textContentandsetAttributefor most cases. ReserveinnerHTMLfor when you genuinely need to insert HTML.
Interactive animation: The four projector rows - starting with event.target against event.currentTarget, each answer given with its rule.
The Right Answer
Three layers depending on the depth being probed.
The 30-Second Answer
For "explain event delegation":
"Event delegation is the pattern of attaching a single event listener to a parent element instead of attaching listeners to each child. When a child fires the event, it bubbles up to the parent's listener, which uses event.target to identify which specific child was involved. Benefits: fewer listeners, works with dynamically added children, easier cleanup."
For "localStorage vs sessionStorage vs cookies":
"All three persist data, but they differ in scope and behavior. localStorage persists across browser sessions and tabs for the same origin — about 5-10MB capacity, synchronous API. sessionStorage is the same API but only for the current tab. Cookies are sent automatically with every HTTP request to the same origin — small (~4KB) but useful when the server needs to see the data. For sensitive data like auth tokens, HttpOnly cookies are safer because JavaScript can't access them."
The 2-Minute Answer
Build on the above:
"Event delegation works because of DOM event bubbling. When an event fires on an element, it propagates up through every ancestor — the target phase happens at the actual element, then the bubble phase walks back up. By default, addEventListener registers handlers for the bubble phase, which is what makes delegation work.
In practice, delegation is the right pattern for any list of similar items. Instead of N listeners on N items, one listener on the container handles all of them. New items added later work automatically because the bubbling still reaches the container. This is especially important in React or other frameworks where children get re-rendered — the framework can re-render without losing listeners.
For storage, the choice depends on what you're storing and who needs to see it. UI preferences and user customizations go in localStorage — the client manages them, the server doesn't need to see them. Session-specific state goes in sessionStorage when you want it to clear when the tab closes. Auth tokens that need to be sent to the server go in cookies, often HttpOnly so JavaScript can't accidentally leak them. The 4KB cookie size limit means you can't store complex client state there — that's what localStorage is for."
Interactive animation: Where each piece of data lives - preferences in localStorage, tab state in sessionStorage, auth tokens in HttpOnly cookies.
The Deep Dive
For senior or staff interviews:
"Event delegation is one of several DOM patterns that exist because of bubbling. Another is using event.stopPropagation() to prevent ancestor handlers from running — though this is usually an anti-pattern because it breaks delegation systems higher in the tree. A better pattern is to mark events as 'handled' explicitly through a state flag the upper handler checks.
The capture phase exists for handlers that want to intercept events before the target has a chance to handle them. This is rare in application code but used by libraries — for example, focus-trapping logic in modal dialogs uses capture-phase listeners on the document to detect focus moving outside the modal.
For storage, the security model matters. localStorage is accessible to any script running on the origin, which means XSS vulnerabilities can read any tokens stored there. The HttpOnly cookie flag exists specifically because of this — a cookie marked HttpOnly can't be accessed from JavaScript at all, so XSS can't steal it. The trade-off is that you can't manage HttpOnly cookies from JavaScript either; only the server can set them.
The 'fetch doesn't throw on HTTP errors' design is intentional. The reasoning: the request didn't fail at the protocol level, the response just happened to be 404 or 500. The application code is in a better position to decide what those status codes mean than the fetch function is. A REST API might treat 404 as 'not found' rather than as an error. A 401 might trigger a token refresh rather than a failure. By not throwing, fetch leaves the policy decisions to the application.
For performance, the key insight is the reflow-repaint-composite hierarchy. Modern browsers separate layout, paint, and compositing into different stages with different costs. Properties that only affect compositing — transform, opacity — can be animated by the GPU without touching the main thread. Properties that affect paint require redrawing pixels but not recalculating layout. Properties that affect layout require recomputing positions of all affected elements. For 60fps animations, transform-and-opacity-only is the rule."
This level of answer signals real depth. Use it when staff engineers are being evaluated.
A Real Example: The Delegation Question
A canonical interview question:
<ul id="todo-list">
<li>Buy milk <button>delete</button></li>
<li>Write code <button>delete</button></li>
<li>Exercise <button>delete</button></li>
</ul>
"Implement delete buttons that remove their parent list item, in a way that works even when new items are added dynamically."
Walk through it out loud:
"Two approaches. The naive approach: add a click listener to each delete button. The problem is when we add new items — they don't have listeners until we manually add them. This produces code that has to remember to add listeners on every item creation.
The better approach is event delegation. One listener on the ul element. When clicked, check if event.target is a delete button. If yes, find its parent li and remove it. This works for the existing three items and any items added later."
// Setup the structure
document.body.innerHTML = `
<ul id="todo-list">
<li>Buy milk <button class="delete">delete</button></li>
<li>Write code <button class="delete">delete</button></li>
<li>Exercise <button class="delete">delete</button></li>
</ul>
`;
const list = document.getElementById("todo-list");
// One listener handles all current and future items
list.addEventListener("click", (event) => {
if (event.target.classList.contains("delete")) {
const li = event.target.closest("li");
li.remove();
}
});
// Test: add a new item dynamically — it still works
const newItem = document.createElement("li");
newItem.innerHTML = "New item <button class='delete'>delete</button>";
list.appendChild(newItem);
console.log("Click any delete button — including the new one");
The use of closest is worth noting. event.target.closest("li") finds the nearest ancestor li, including the target itself. This is the right pattern when you might be clicking on a child of the actual target (like clicking text inside the button).
Interactive animation: The two builds side by side - delegation with closest, and a debounce built on a resettable timer.
The Follow-Ups
Interview: What the interviewer asks next.
"What if there are nested elements where you'd want delegation at different levels?" Multiple delegated listeners can coexist. The outer handles broad concerns, inner handles specific concerns. Use
event.stopPropagation()carefully if you need to prevent outer handlers from firing."What's the difference between
event.preventDefault()andevent.stopPropagation()?" preventDefault cancels the browser's default behavior (form submission, link navigation, etc.) but the event still bubbles. stopPropagation prevents bubbling but the default behavior still happens. They're orthogonal."What happens if you store more than 10MB in localStorage?" Throws a QuotaExceededError. You should catch it and have a fallback strategy — purge old data, use IndexedDB instead, or notify the user.
"Why is animating
transformsmoother than animatingwidth?" transform is composited by the GPU on its own layer. width requires layout recalculation and repainting, which happens on the main thread and competes with JavaScript execution."How would you implement an autocomplete that doesn't fire too many requests as the user types?" Debounce the input handler (Topic 5.8) and use AbortController to cancel previous in-flight requests (Topic 12.5).
The Founder Moment
Olena here. The browser interview question that taught me the most came up in my Zalando loop, the role I took before Microsoft. They asked me to build a simple drag-and-drop interface in vanilla JavaScript. No React, no library — just events and DOM manipulation.
I knew the events I needed — mousedown, mousemove, mouseup. I started attaching mousemove to the dragged element. The interviewer let me run, but it didn't work right — the drag would break when the mouse moved fast enough to leave the element.
She gave me a hint: "where should you attach the mousemove listener?" I thought, then realized — the document, not the element. The mouse can move anywhere during a drag, even outside the element being dragged. Attaching mousemove to the document captures all mouse movement during the drag. Attaching it to the element only captures movement inside the element.
I rewrote it. Worked perfectly. The interviewer noted in the feedback that getting this right under pressure showed I'd thought about event propagation, not just memorized API names.
The lesson stuck. Browser APIs aren't just lists of methods — they have semantics around where events happen, when they fire, how they propagate. Senior frontend interviews probe whether you've internalized those semantics, not whether you've memorized method names. Build the model, not the vocabulary.