JavaScript · Browser APIs

Browser APIs Interview Questions
With Answers & Code Examples

6 carefully curated Browser APIs interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
6 questions0 beginner2 core4 advanced
Q1Core

What is the Fetch API and how do you handle errors correctly?

💡 Hint: fetch() only rejects on network failure — HTTP 4xx/5xx must be checked via response.ok

The key gotcha: fetch() only rejects on network failure (no connection, DNS fail). HTTP errors like 404 or 500 are "successful" responses!

// ❌ Wrong — HTTP errors silently "succeed"
const data = await fetch('/api').then(r => r.json());
// 404 or 500 still "resolves" — you get error HTML as data

// ✅ Correct — check response.ok
async function apiFetch(url, options = {}) {
  const res = await fetch(url, options);
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  }
  return res.json();
}

// POST with JSON
await apiFetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice' }),
});

// With timeout (AbortController)
async function fetchWithTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), ms);
  try {
    return await fetch(url, { signal: controller.signal });
  } catch (err) {
    if (err.name === 'AbortError') throw new Error('Request timed out');
    throw err;
  } finally {
    clearTimeout(id);
  }
}
💡 Always check response.ok. Create a reusable fetch wrapper that throws on non-2xx responses so every call site doesn't have to remember.
Practice this question →
Q2Core

What is the difference between localStorage, sessionStorage, and cookies?

💡 Hint: Differ in persistence, scope, size, and whether auto-sent to server
FeaturelocalStoragesessionStorageCookie
LifetimePersistent (until cleared)Tab session onlyExpiry date / session
ScopeSame origin, all tabsSame tab onlyDomain + path
Size limit~5-10 MB~5-10 MB~4 KB
Sent to serverNoNoYes (every request)
JS accessYesYesYes (unless HttpOnly)
// localStorage / sessionStorage — same API
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme');    // 'dark'
localStorage.removeItem('theme');
localStorage.clear();

// Must stringify objects
localStorage.setItem('user', JSON.stringify({ name: 'Alice', id: 1 }));
const user = JSON.parse(localStorage.getItem('user'));
💡 Never store auth tokens or sensitive data in localStorage/sessionStorage — XSS attacks can steal it. Use HttpOnly cookies for auth tokens — they're inaccessible to JavaScript.
Practice this question →
Q3Advanced

What are Web Workers and when should you use them?

💡 Hint: Run JS in a background thread — no DOM access; use postMessage to communicate

Web Workers run JavaScript in a separate background thread — preventing heavy computation from blocking the main thread (UI freezing).

// main.js
const worker = new Worker('worker.js');

// Send data to worker (structured clone — copies data)
worker.postMessage({ array: bigArray, threshold: 50 });

// Receive results
worker.onmessage = (e) => {
  console.log('Result:', e.data.result);
  worker.terminate(); // clean up
};

worker.onerror = (e) => console.error('Worker error:', e.message);

// worker.js — completely separate context
self.onmessage = (e) => {
  const { array, threshold } = e.data;
  // Heavy computation — won't block UI
  const result = array.filter(x => x > threshold).reduce((a,b) => a+b, 0);
  self.postMessage({ result });
};

Limitations: No access to DOM, window, document. Communication only via postMessage. Data is copied (structured clone), not shared (except SharedArrayBuffer).

Use cases: Image/video processing, data parsing, crypto, ML inference, large sort/filter operations.

💡 Use the Comlink library to make Worker APIs feel like regular async function calls — wraps postMessage/onmessage into an async function interface.
Practice this question →
Q4Advanced

What are Service Workers and what problems do they solve?

💡 Hint: Background JS proxy between app and network — enables offline support, push notifications, caching

A Service Worker is a script that runs in the background, separate from the page — acting as a network proxy. Enables progressive web app features.

// Registration (main thread)
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered:', reg.scope))
    .catch(err => console.error('SW failed:', err));
}

// sw.js — the service worker itself
const CACHE = 'v1';
const ASSETS = ['/index.html', '/styles.css', '/app.js'];

// Install — pre-cache core assets
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE).then(cache => cache.addAll(ASSETS))
  );
});

// Activate — clean old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
    )
  );
});

// Fetch — intercept ALL network requests
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request); // cache-first strategy
    })
  );
});

Capabilities: Offline caching, background sync, push notifications, intercepting requests, URL routing.

💡 Service Workers only run on HTTPS (or localhost). They have no DOM access. They're the foundation of Progressive Web Apps (PWAs). Use Workbox library to simplify service worker code.
Practice this question →
Q5Advanced

What is the Same-Origin Policy and how does CORS work?

💡 Hint: Browser blocks cross-origin requests by default; server opts in via CORS headers

The Same-Origin Policy (SOP) blocks web pages from reading resources from a different origin (protocol + domain + port combination).

// Same origin — all identical: protocol, domain, port
// https://app.com/page can read from https://app.com/api ✅
// https://app.com/page CANNOT read from https://api.other.com ❌

// CORS (Cross-Origin Resource Sharing):
// Server opts in by sending response headers

// Server response headers to allow access:
Access-Control-Allow-Origin: https://app.com  // or * for all
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true // if sending cookies

// Simple requests (GET, POST with basic headers) — no preflight
fetch('https://api.other.com/data');

// Preflighted requests — browser sends OPTIONS first
fetch('https://api.other.com/data', {
  method: 'DELETE',           // non-simple method
  headers: { 'X-Custom': '1' } // non-simple header
});
// Browser sends: OPTIONS https://api.other.com/data
// → checks server allows it before actual request

// CORS does NOT apply to:
// - Server-to-server (Node.js fetch, cURL)
// - <img>, <script>, <link> tags (but limited access)
// - Same origin
💡 CORS is enforced by the BROWSER only — it's not a server-side security measure. Server-to-server calls bypass it entirely. The browser is protecting users from malicious scripts, not the server from requests.
Practice this question →
Q6Advanced

What is Shadow DOM and when do you use it?

💡 Hint: Encapsulated DOM subtree — styles and JS don't leak in or out; foundation of Web Components
// Attach a shadow root to any element
const host = document.getElementById('my-widget');
const shadow = host.attachShadow({ mode: 'open' }); // or 'closed'

// Add content — fully encapsulated
shadow.innerHTML = `
  <style>
    /* This CSS is SCOPED to shadow DOM only */
    p { color: red; font-size: 1.5rem; }
    :host { display: block; border: 1px solid blue; }
  </style>
  <p>I'm in shadow DOM</p>
  <slot></slot>  <!-- slot: renders host element's children -->
`;

// 'open' mode: accessible via element.shadowRoot
host.shadowRoot.querySelector('p'); // works
// 'closed' mode: host.shadowRoot = null (truly private)

// <slot> — project host children into shadow DOM
// <my-card><h2>Title</h2></my-card>
// The <h2> is rendered where <slot> is placed

// Web Component using Shadow DOM
class MyButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>button { background: purple; color: white; }</style>
      <button><slot>Click me</slot></button>
    `;
  }
}
customElements.define('my-button', MyButton);
💡 Shadow DOM = CSS encapsulation + DOM encapsulation. Styles from the outside can't penetrate in (except CSS custom properties / variables), and internal styles can't leak out. This is how browser built-ins like <video> and <input type="date"> hide their internal structure.
Practice this question →

Other JavaScript Interview Topics

Core JSFunctionsAsync JSObjectsArrays'this' KeywordError HandlingModern JSPerformanceDOM & Events

Ready to practice Browser APIs?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →