RSRobince Studio

Top 50 JavaScript in the Browser interview questions and answers

The 50 questions interviewers ask most about JavaScript in the Browser, with short, clear answers and code where it helps. The first 25 are the most common; the rest go deeper. Free, no sign-up.

All 50 questions

  1. What is the DOM?
  2. What is the difference between `querySelector`, `getElementById` and `getElementsByClassName`?
  3. What is event bubbling?
  4. What is event capturing, and how does it differ from bubbling?
  5. What is event delegation and why is it useful?
  6. What is the difference between `event.target` and `event.currentTarget`?
  7. What is the difference between `preventDefault()` and `stopPropagation()`?
  8. What is the difference between `localStorage`, `sessionStorage` and cookies?
  9. What are cookie attributes like `HttpOnly`, `Secure` and `SameSite`?
  10. How does the `fetch` API work, and how is it different from `XMLHttpRequest`?
  11. What is CORS and how does it work?
  12. What is a CORS preflight request?
  13. What is the same-origin policy?
  14. What is the difference between `window`, `document` and `screen`?
  15. How do you navigate or read the URL with `location` and `history`?
  16. What is debouncing and how do you implement it?
  17. What is throttling and how is it different from debouncing?
  18. What is the difference between reflow (layout) and repaint?
  19. How does the browser render a page? Explain the critical rendering path.
  20. What is `requestAnimationFrame` and why use it instead of `setTimeout` for animations?
  21. What is XSS and how do you prevent it?
  22. What is CSRF and how do you prevent it?
  23. What is a Web Worker and when would you use one?
  24. What is a Service Worker and what is it used for?
  25. What is `IntersectionObserver` and what is it used for?
  26. What is the difference between `defer` and `async` on a script tag?
  27. What is the difference between `DOMContentLoaded` and `load`?
  28. What is the difference between `innerHTML`, `textContent` and `innerText`?
  29. How would you insert hundreds of elements into the DOM efficiently?
  30. What is the difference between `addEventListener` and an `onclick` property?
  31. What does the `passive` option do in `addEventListener`?
  32. How do you remove an event listener, and why do anonymous functions not work?
  33. What causes memory leaks in browser JavaScript, and how do you find them?
  34. What is the output of this code, and why? `for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);`
  35. What is the difference between microtasks and macrotasks in the event loop?
  36. How can a long-running task freeze the page, and how do you avoid it?
  37. What is the difference between `requestIdleCallback` and `requestAnimationFrame`?
  38. How do you cancel a `fetch` request, and why would you?
  39. How would you make several API calls in parallel and handle failures?
  40. Why does `localStorage` not work well for large or structured data, and what are the alternatives?
  41. What is the difference between `postMessage` and a `BroadcastChannel`, and what must you check when receiving messages?
  42. What is a Content Security Policy, and how does it help security?
  43. What is the difference between `MutationObserver`, `ResizeObserver` and `IntersectionObserver`?
  44. What is layout thrashing and how do you fix this snippet? `boxes.forEach(b => { b.style.width = b.offsetWidth + 10 + "px"; })`
  45. How do you lazy load images and code in a web page?
  46. What are Core Web Vitals and how can you improve them with JavaScript?
  47. How do you copy text to the clipboard, and what restrictions apply?
  48. What is the difference between a `Blob`, a `File` and an object URL?
  49. Why do you need to be careful with `JSON.parse` on data from the browser, and what can go wrong with `JSON.stringify`?
  50. How would you handle errors globally in a browser application?

1. What is the DOM?

The DOM (Document Object Model) is a tree-shaped, in-memory representation of an HTML document that the browser builds while parsing, and that JavaScript can read and change through an API. Every element, attribute and piece of text becomes a node, so scripts can add, remove or restyle parts of the page. It is not the HTML source itself: the DOM can differ from the markup after scripts run or the parser fixes invalid HTML.

2. What is the difference between `querySelector`, `getElementById` and `getElementsByClassName`?

getElementById returns one element by its id, querySelector returns the first element matching any CSS selector, and getElementsByClassName returns a live HTMLCollection of every match. querySelectorAll returns a static NodeList, which does not update when the DOM changes, while the getElementsBy* collections do. getElementById is the fastest, but querySelector is the most flexible, so it is the usual default.

const a = document.getElementById("app");
const b = document.querySelector(".card > a");
const all = document.querySelectorAll("li.active");
const live = document.getElementsByClassName("item");

3. What is event bubbling?

Event bubbling is the behavior where an event fires on the target element first and then propagates upward through its ancestors, up to document and window. So a click on a button also triggers click handlers on its parent div, body and so on. You can stop it with event.stopPropagation(). Most DOM events bubble, but a few, such as focus, blur and mouseenter, do not.

4. What is event capturing, and how does it differ from bubbling?

Capturing is the first phase of event propagation, in which the event travels from window down to the target, while bubbling goes from the target back up. The full order is capture, target, then bubble. Listeners run in the bubbling phase by default; pass { capture: true } (or true) as the third argument to addEventListener to listen during capture.

parent.addEventListener("click", () => console.log("capture"), { capture: true });
parent.addEventListener("click", () => console.log("bubble"));
child.addEventListener("click", () => console.log("target"));
// click on child: capture, target, bubble

5. What is event delegation and why is it useful?

Event delegation is attaching one listener to a common parent instead of many listeners on its children, and using bubbling plus event.target to find which child was hit. It saves memory, and it automatically works for elements added later. Use event.target.closest(selector) to handle clicks on nested content correctly.

list.addEventListener("click", (e) => {
  const li = e.target.closest("li");
  if (li && list.contains(li)) console.log(li.dataset.id);
});

6. What is the difference between `event.target` and `event.currentTarget`?

event.target is the element that actually triggered the event, while event.currentTarget is the element the running listener is attached to. They differ when the event bubbles from a child to a parent listener. Also, currentTarget is only valid during dispatch and becomes null if you read it later asynchronously.

7. What is the difference between `preventDefault()` and `stopPropagation()`?

preventDefault() cancels the browser's default action for an event, such as following a link or submitting a form, while stopPropagation() stops the event from reaching other elements in the propagation path. They are independent: one does not imply the other. stopImmediatePropagation() additionally prevents other listeners on the same element from running.

8. What is the difference between `localStorage`, `sessionStorage` and cookies?

localStorage persists until it is cleared, sessionStorage lasts only for the lifetime of a tab, and cookies have an optional expiry and are sent to the server with matching requests. Web storage offers roughly 5 MB per origin and is never sent automatically, while cookies are limited to about 4 KB each. Storage is synchronous and stores strings only, and it is readable by any script on the origin, so do not keep secrets in it.

localStorage.setItem("theme", "dark");
sessionStorage.setItem("step", "2");
const user = JSON.parse(localStorage.getItem("user") ?? "null");

9. What are cookie attributes like `HttpOnly`, `Secure` and `SameSite`?

They are flags that control how a cookie is exposed and sent. HttpOnly hides the cookie from document.cookie, which limits theft through XSS; Secure sends it only over HTTPS; and SameSite (Strict, Lax or None) limits cross-site sending, which helps against CSRF. Browsers now treat cookies without a SameSite value as Lax, and SameSite=None requires Secure.

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600

10. How does the `fetch` API work, and how is it different from `XMLHttpRequest`?

fetch is a promise-based API for HTTP requests that returns a Response object, replacing the older callback-based XMLHttpRequest. A key gotcha is that fetch only rejects on network failure, so HTTP errors like 404 or 500 still resolve and you must check response.ok. It also supports streaming and AbortController, and it does not send cookies cross-origin unless you set credentials.

const res = await fetch("/api/users", { headers: { Accept: "application/json" } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const users = await res.json();

11. What is CORS and how does it work?

CORS (Cross-Origin Resource Sharing) is a browser mechanism that lets a server allow cross-origin requests by sending headers such as Access-Control-Allow-Origin. The browser enforces the same-origin policy and blocks scripts from reading a cross-origin response unless the server opts in. The block happens in the browser, not on the server, so tools like curl are unaffected. Fixing CORS is done on the server, not in client code.

12. What is a CORS preflight request?

A preflight is an automatic OPTIONS request the browser sends before certain cross-origin requests to ask the server whether the real request is allowed. It happens for non-simple requests, such as methods other than GET, HEAD or POST, custom headers, or a Content-Type like application/json. The server must answer with Access-Control-Allow-Methods and Access-Control-Allow-Headers, and can cache the result with Access-Control-Max-Age.

13. What is the same-origin policy?

The same-origin policy is a browser security rule that stops a page from reading data from a different origin, where an origin is the combination of scheme, host and port. For example, https://a.com and http://a.com are different origins. It prevents a malicious site from reading your data on another site, and CORS is the controlled way to relax it.

14. What is the difference between `window`, `document` and `screen`?

window is the global object representing the browser tab and holds the BOM, document is the property of window that gives access to the page's DOM, and screen describes the user's display, such as its width and height. Global variables declared with var and global functions become properties of window, while let and const do not.

15. How do you navigate or read the URL with `location` and `history`?

location reads and changes the current URL, and history lets you move through the session history without a full reload. location.href = url navigates, location.replace(url) navigates without adding a history entry, and history.pushState() changes the URL without loading the page, which single-page apps use for routing. Listen for the popstate event to react to back and forward.

history.pushState({ id: 7 }, "", "/items/7");
window.addEventListener("popstate", (e) => render(e.state));
const q = new URLSearchParams(location.search).get("q");

16. What is debouncing and how do you implement it?

Debouncing delays running a function until a set time has passed with no further calls, so only the last call in a burst executes. It suits search-as-you-type or resize handlers. You implement it by clearing and resetting a timer on each call.

function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
input.addEventListener("input", debounce(search, 300));

17. What is throttling and how is it different from debouncing?

Throttling guarantees a function runs at most once per fixed interval, no matter how often it is triggered, while debouncing waits until activity stops. Use throttling for continuous events where regular updates matter, such as scrolling or dragging, and debouncing when only the final value matters, such as a search box.

function throttle(fn, wait = 200) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= wait) { last = now; fn(...args); }
  };
}

18. What is the difference between reflow (layout) and repaint?

A reflow recalculates the size and position of elements, while a repaint redraws their pixels without changing geometry. Reflow is more expensive and can cascade to children and ancestors, so changing width or adding nodes triggers it, while changing color or background usually only triggers repaint. Animating transform and opacity can avoid both by running on the compositor. Reading layout values like offsetHeight right after a write forces a synchronous reflow, which is called layout thrashing.

19. How does the browser render a page? Explain the critical rendering path.

The browser parses HTML into the DOM and CSS into the CSSOM, combines them into a render tree, computes layout, paints the pixels, and composites layers onto the screen. Scripts without async or defer block HTML parsing, and CSS blocks rendering, so placing critical CSS early and deferring scripts speeds up the first paint.

20. What is `requestAnimationFrame` and why use it instead of `setTimeout` for animations?

requestAnimationFrame schedules a callback to run just before the browser's next repaint, typically 60 times per second or matching the display refresh rate. Unlike setTimeout, it syncs with rendering, gives smoother animation, and pauses in background tabs to save battery. The callback receives a high-resolution timestamp, which you should use to compute movement rather than assuming a fixed frame time.

function step(time) {
  box.style.transform = `translateX(${(time / 10) % 300}px)`;
  requestAnimationFrame(step);
}
requestAnimationFrame(step);

21. What is XSS and how do you prevent it?

XSS (cross-site scripting) is an attack where an attacker gets malicious script to run in another user's page, usually through unescaped user input. To prevent it, treat user data as text by using textContent rather than innerHTML, escape output for its context, sanitize any HTML you must render with a library such as DOMPurify, and add a strict Content Security Policy. Marking session cookies HttpOnly limits the damage.

el.textContent = userInput;      // safe
// el.innerHTML = userInput;     // dangerous

22. What is CSRF and how do you prevent it?

CSRF (cross-site request forgery) tricks a logged-in user's browser into sending an unwanted request to a site where they are authenticated, because the browser attaches cookies automatically. Prevent it with anti-CSRF tokens, SameSite cookies, and checking the Origin header, and never use GET requests for state-changing actions. APIs that use an Authorization header instead of cookies are not exposed to the same attack.

23. What is a Web Worker and when would you use one?

A Web Worker runs JavaScript on a background thread so heavy computation does not block the main thread and freeze the UI. Workers have no access to the DOM and communicate with the page through postMessage and the message event, with data copied by structured clone or transferred. Use them for tasks like parsing large files, image processing or complex calculations.

const worker = new Worker("worker.js");
worker.postMessage({ n: 40 });
worker.onmessage = (e) => console.log(e.data);

24. What is a Service Worker and what is it used for?

A Service Worker is a script that runs in the background as a programmable network proxy between the page, the browser and the network, enabling offline support, caching and push notifications. It must be served over HTTPS (localhost is allowed), is event-driven, and has no DOM access. Its lifecycle has register, install, activate and fetch stages, and it uses the Cache API to serve responses offline.

navigator.serviceWorker.register("/sw.js");
// sw.js
self.addEventListener("fetch", (e) => {
  e.respondWith(caches.match(e.request).then((r) => r || fetch(e.request)));
});

25. What is `IntersectionObserver` and what is it used for?

IntersectionObserver asynchronously reports when a target element enters or leaves the viewport, or another ancestor, without needing scroll listeners. It is commonly used for lazy loading images, infinite scroll and triggering animations or analytics when something becomes visible. Because it does not run on every scroll event, it performs much better than manual getBoundingClientRect checks.

const io = new IntersectionObserver((entries) => {
  entries.forEach((e) => {
    if (e.isIntersecting) { e.target.src = e.target.dataset.src; io.unobserve(e.target); }
  });
}, { rootMargin: "200px" });
document.querySelectorAll("img[data-src]").forEach((img) => io.observe(img));

26. What is the difference between `defer` and `async` on a script tag?

Both download a script in parallel with HTML parsing, but defer runs it after parsing finishes, in document order, while async runs it as soon as it is downloaded, in any order. Use defer for scripts that depend on the DOM or on each other, and async for independent scripts such as analytics. Classic scripts without either attribute block parsing. Module scripts (type="module") are deferred by default.

<script src="app.js" defer></script>
<script src="analytics.js" async></script>

27. What is the difference between `DOMContentLoaded` and `load`?

DOMContentLoaded fires when the HTML has been fully parsed and deferred scripts have run, without waiting for stylesheets, images or subframes, while load fires on window after all those resources have finished loading. Use DOMContentLoaded to start initializing the UI early. Scripts at the end of the body or with defer often make the DOMContentLoaded listener unnecessary.

document.addEventListener("DOMContentLoaded", init);
window.addEventListener("load", () => console.log("all resources loaded"));

28. What is the difference between `innerHTML`, `textContent` and `innerText`?

innerHTML reads or writes markup and parses it as HTML, textContent reads or writes the raw text of a node and all its descendants, and innerText returns only the rendered, visible text. innerText is aware of CSS, so hidden elements are skipped, and reading it triggers a reflow. For plain text, prefer textContent because it is faster and safe from HTML injection.

29. How would you insert hundreds of elements into the DOM efficiently?

Build them off-DOM and insert them in one operation so the browser reflows once instead of hundreds of times. Use a DocumentFragment (or build an HTML string and assign it once) and append the fragment. For very long lists, also consider virtualization so only the visible rows exist in the DOM.

const frag = document.createDocumentFragment();
for (const item of items) {
  const li = document.createElement("li");
  li.textContent = item.name;
  frag.append(li);
}
list.append(frag);

30. What is the difference between `addEventListener` and an `onclick` property?

addEventListener can attach many listeners to the same event and offers options like capture, once and passive, whereas assigning onclick allows only one handler and a second assignment overwrites the first. Listeners added with addEventListener can be removed with removeEventListener if you keep a reference to the same function. Inline onclick attributes are also blocked by a strict Content Security Policy.

31. What does the `passive` option do in `addEventListener`?

Setting { passive: true } promises the browser that the listener will never call preventDefault(), so it can start scrolling immediately without waiting for your handler to finish. This avoids scroll jank on touchstart, touchmove and wheel listeners. If you call preventDefault() in a passive listener, the browser ignores it and logs a console warning. Listeners on those events at the window or document level are passive by default.

window.addEventListener("touchmove", onMove, { passive: true });

32. How do you remove an event listener, and why do anonymous functions not work?

removeEventListener only removes a listener if you pass the same event type, the same function reference and the same capture flag used when adding it. An anonymous arrow function creates a new function each time, so there is nothing matching to remove. Keep the handler in a variable, or use { once: true } or an AbortSignal to clean up automatically.

const ac = new AbortController();
btn.addEventListener("click", onClick, { signal: ac.signal });
ac.abort(); // removes the listener

33. What causes memory leaks in browser JavaScript, and how do you find them?

Memory leaks come from references that keep objects alive after they are no longer needed, such as forgotten event listeners, uncleared setInterval timers, detached DOM nodes held in variables, accidental globals and growing caches. Find them with the Chrome DevTools Memory tab by comparing heap snapshots, or with the Performance monitor, and look for detached nodes that keep growing. Fix them by removing listeners, clearing timers and using WeakMap or WeakRef for caches.

34. What is the output of this code, and why? `for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);`

It prints 3, 3, 3, because var is function scoped, so all three callbacks share one i, and the callbacks run after the loop has finished and i is already 3. Using let creates a new binding per iteration, so it prints 0, 1, 2. The older fix was to wrap the body in an IIFE.

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 0 1 2
}

35. What is the difference between microtasks and macrotasks in the event loop?

Macrotasks, such as setTimeout callbacks, UI events and I/O, are run one per event-loop turn, while microtasks, such as promise callbacks and queueMicrotask, run to completion right after the current task and before the browser renders or starts the next macrotask. So a promise callback always runs before a setTimeout(fn, 0) scheduled at the same time. Endless microtasks can starve rendering.

setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("sync");
// sync, promise, timeout

36. How can a long-running task freeze the page, and how do you avoid it?

JavaScript on the main thread shares one thread with rendering and input handling, so a long synchronous task blocks painting and clicks until it finishes. Avoid it by moving heavy work to a Web Worker, splitting the work into small chunks scheduled with setTimeout, requestIdleCallback or scheduler.yield(), and reducing work per event. Tasks over 50 ms are flagged as long tasks and hurt responsiveness metrics such as INP.

37. What is the difference between `requestIdleCallback` and `requestAnimationFrame`?

requestAnimationFrame runs right before the next paint and is meant for visual updates, while requestIdleCallback runs when the browser has spare time and is meant for low-priority work like analytics or prefetching. An idle callback receives a deadline object with timeRemaining() so it can stop in time. Never touch layout-critical work in idle callbacks, and note that Safari support has lagged, so provide a fallback.

requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && queue.length) process(queue.shift());
});

38. How do you cancel a `fetch` request, and why would you?

Create an AbortController, pass its signal to fetch, and call abort() to cancel; the promise rejects with an AbortError. This is useful for search-as-you-type, so a stale response cannot overwrite a newer one, and for timeouts or leaving a page. Handle the error by checking err.name. AbortSignal.timeout(ms) gives a ready-made timeout signal.

const ac = new AbortController();
fetch(url, { signal: ac.signal }).catch((e) => {
  if (e.name !== "AbortError") throw e;
});
ac.abort();

39. How would you make several API calls in parallel and handle failures?

Start the requests together and await them with Promise.all when you need every result, which rejects as soon as one fails, or Promise.allSettled when you want each outcome regardless of failures. Awaiting calls one after another in a loop makes them sequential and slower. Remember that fetch does not reject on HTTP errors, so check response.ok inside each call.

const results = await Promise.allSettled([
  fetch("/api/a").then((r) => r.json()),
  fetch("/api/b").then((r) => r.json()),
]);
const ok = results.filter((r) => r.status === "fulfilled");

40. Why does `localStorage` not work well for large or structured data, and what are the alternatives?

localStorage is synchronous, so large reads and writes block the main thread, it holds only strings, and it is limited to about 5 MB. For large or structured data use IndexedDB, which is asynchronous, transactional, indexed and stores objects and blobs. The Cache API suits request and response pairs. Wrappers like idb make IndexedDB far easier to use. It is also unavailable in workers, unlike IndexedDB.

41. What is the difference between `postMessage` and a `BroadcastChannel`, and what must you check when receiving messages?

postMessage sends a message to one specific window, iframe or worker, while a BroadcastChannel broadcasts to every same-origin context listening on a named channel, such as other tabs. When receiving a window message, always verify event.origin (and ideally event.source) before trusting the data, and send with a specific target origin rather than "*". Otherwise any site can spoof or intercept messages.

window.addEventListener("message", (e) => {
  if (e.origin !== "https://trusted.example.com") return;
  handle(e.data);
});

42. What is a Content Security Policy, and how does it help security?

A Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of scripts, styles, images and other resources are allowed, so injected scripts from XSS are blocked from running. A strict policy avoids unsafe-inline and uses nonces or hashes for allowed inline scripts. You can try a policy first with Content-Security-Policy-Report-Only. CSP is a second line of defense, not a replacement for escaping output.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'

43. What is the difference between `MutationObserver`, `ResizeObserver` and `IntersectionObserver`?

MutationObserver reports changes to the DOM tree such as added nodes or changed attributes, ResizeObserver reports changes to an element's content box size, and IntersectionObserver reports visibility relative to the viewport or an ancestor. All three batch notifications asynchronously, so they are cheaper than polling or listening to scroll and resize. Disconnect them with disconnect() when they are no longer needed to avoid leaks.

const ro = new ResizeObserver(([entry]) => {
  console.log(entry.contentRect.width);
});
ro.observe(panel);

44. What is layout thrashing and how do you fix this snippet? `boxes.forEach(b => { b.style.width = b.offsetWidth + 10 + "px"; })`

Layout thrashing is alternating DOM reads and writes so the browser must recompute layout synchronously on every iteration. In that snippet, reading offsetWidth after the previous write forces a reflow each time. Fix it by batching: do all the reads first, then all the writes, or do the writes inside requestAnimationFrame.

const widths = boxes.map((b) => b.offsetWidth);
boxes.forEach((b, i) => {
  b.style.width = widths[i] + 10 + "px";
});

45. How do you lazy load images and code in a web page?

For images, add loading="lazy" to img and iframe elements, which lets the browser defer offscreen loading natively, or use IntersectionObserver for custom control. For code, use dynamic import() to split a bundle and load a module only when it is needed, for example on a click or a route change. Never lazy load the main above-the-fold image, since that delays Largest Contentful Paint.

button.addEventListener("click", async () => {
  const { openChart } = await import("./chart.js");
  openChart();
});

46. What are Core Web Vitals and how can you improve them with JavaScript?

Core Web Vitals are Google's user-experience metrics: Largest Contentful Paint (LCP) for loading, Interaction to Next Paint (INP) for responsiveness, and Cumulative Layout Shift (CLS) for visual stability. In JavaScript, reduce LCP by shipping less blocking script, improve INP by breaking up long tasks and keeping event handlers light, and reduce CLS by reserving space for images and late-injected content. Measure them with the web-vitals library or PerformanceObserver.

47. How do you copy text to the clipboard, and what restrictions apply?

Use the asynchronous Clipboard API, navigator.clipboard.writeText(), which returns a promise. It requires a secure context (HTTPS) and generally must be triggered by a user gesture such as a click, and reading the clipboard additionally asks for permission. The old document.execCommand("copy") is deprecated, so keep it only as a legacy fallback.

btn.addEventListener("click", async () => {
  await navigator.clipboard.writeText(code.textContent);
});

48. What is the difference between a `Blob`, a `File` and an object URL?

A Blob is an immutable chunk of raw binary data with a type, a File is a Blob with a name and last-modified date, usually coming from an <input type="file">, and an object URL is a temporary blob: URL created with URL.createObjectURL() that points to one of them. Object URLs are handy for previewing an image or triggering a download without a server. Free them with URL.revokeObjectURL() to avoid leaking memory.

const url = URL.createObjectURL(file);
img.src = url;
img.onload = () => URL.revokeObjectURL(url);

49. Why do you need to be careful with `JSON.parse` on data from the browser, and what can go wrong with `JSON.stringify`?

JSON.parse throws a SyntaxError on invalid input, so data from storage, the network or users should be wrapped in try/catch, and its result should be validated before use. JSON.stringify silently drops undefined, functions and symbols, turns Date objects into strings, converts NaN to null, and throws on circular references or BigInt. For deep copies, prefer structuredClone().

function safeParse(text, fallback = null) {
  try { return JSON.parse(text); } catch { return fallback; }
}

50. How would you handle errors globally in a browser application?

Register a window.onerror or error listener for uncaught runtime errors and an unhandledrejection listener for promises that rejected without a catch, then log them to a monitoring service and show a friendly fallback. These handlers are a safety net, not a replacement for local try/catch. Cross-origin script errors appear only as "Script error." unless the script is served with CORS headers and crossorigin set. In frameworks, also use their error boundaries.

window.addEventListener("error", (e) => report(e.error));
window.addEventListener("unhandledrejection", (e) => report(e.reason));

More Frontend interview questions

HTML · CSS · JavaScript · TypeScript · React · Angular · All interview questions