Advanced JavaScript Interview Questions and Answers

๐Ÿ“‹ Table of Contents โ–พ
  1. Questions & Answers
  2. 📝 Knowledge Check

📰 Advanced JavaScript Interview Questions

This lesson targets mid-to-senior JavaScript roles. Topics include generators, iterators, Proxy, Reflect, the Prototype chain in depth, functional programming patterns, memoisation, currying, memory management, WeakRef, error handling patterns, performance APIs, and modern ES2022-2025 features. These questions separate JavaScript users from JavaScript engineers.

Questions & Answers

01 What are generators and iterators in JavaScript?

ES6 A generator function (function*) returns a Generator object โ€” which implements both the Iterator protocol (next()) and the Iterable protocol (Symbol.iterator). Values are produced lazily โ€” one at a time on demand.

// Generator function
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;               // pause here, return a
    [a, b] = [b, a + b];  // resume here on next next() call
  }
}

const fib = fibonacci();
fib.next(); // { value: 0, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 1, done: false }

// Take first N values
function take(iterable, n) {
  const result = [];
  for (const val of iterable) {
    result.push(val);
    if (result.length === n) break;
  }
  return result;
}
take(fibonacci(), 8); // [0,1,1,2,3,5,8,13]

// Two-way communication with yield
function* accumulator() {
  let total = 0;
  while (true) {
    const value = yield total;  // yield sends total out, receives value in
    if (value === null) break;
    total += value;
  }
  return total;
}
const acc = accumulator();
acc.next();        // prime: { value: 0 }
acc.next(10);      // { value: 10 }
acc.next(20);      // { value: 30 }
acc.next(null);    // { value: 30, done: true }

// Custom iterable
const range = {
  from: 1, to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last  = this.to;
    return {
      next: () => current <= last
        ? { value: current++, done: false }
        : { done: true }
    };
  }
};
[...range]; // [1,2,3,4,5]

02 What are Proxy and Reflect in JavaScript?

Meta Proxy wraps an object and intercepts operations on it โ€” gets, sets, function calls, property deletion, and more. Reflect provides the default behaviour for those operations.

// Proxy -- intercept object operations
const handler = {
  get(target, prop, receiver) {
    console.log(`Getting ${prop}`);
    return prop in target ? target[prop] : `Property '${prop}' not found`;
  },

  set(target, prop, value) {
    if (typeof value !== "number") throw new TypeError("Only numbers!");
    target[prop] = value;
    return true;   // must return true for success in strict mode
  },

  deleteProperty(target, prop) {
    console.log(`Deleting ${prop}`);
    return Reflect.deleteProperty(target, prop);
  },

  has(target, prop) {
    return prop in target;
  }
};

const obj = new Proxy({}, handler);
obj.score = 42;       // calls set -- OK
obj.score = "hello";  // TypeError
obj.score;            // "Getting score" โ†’ 42
obj.missing;          // "Property 'missing' not found"

// Function proxy
function target(x) { return x * 2; }
const fn = new Proxy(target, {
  apply(fn, thisArg, args) {
    console.log("Called with", args);
    return Reflect.apply(fn, thisArg, args);
  }
});
fn(5); // "Called with [5]" โ†’ 10

// Reflect -- default implementations of all proxy traps
// Always use Reflect inside proxy handlers to avoid breaking prototype chains
Reflect.get(obj, "score");          // same as obj.score
Reflect.set(obj, "score", 99);      // same as obj.score = 99
Reflect.has(obj, "score");          // same as "score" in obj
Reflect.ownKeys(obj);               // all own property keys inc. symbols

// Use cases: validation, reactive state (Vue 3 uses Proxy), logging, ORM field mapping

03 What is the prototype chain in depth? How does property lookup work?

OOP

// Property lookup algorithm:
// 1. Check own properties of the object
// 2. If not found, follow [[Prototype]] link
// 3. Repeat until null (end of chain)
// 4. Return undefined

const base = { greet() { return "Hello!"; } };
const obj  = Object.create(base);     // obj.[[Prototype]] = base
obj.name   = "Alice";

obj.name;    // own property -- found immediately
obj.greet(); // not own -- found on base via prototype chain
obj.missing; // not found anywhere -- undefined

// Full prototype chain for a class instance
class Animal { breathe() { return "breathing"; } }
class Dog extends Animal { bark() { return "woof"; } }
const rex = new Dog();

// rex.[[Prototype]] = Dog.prototype (has: bark)
// Dog.prototype.[[Prototype]] = Animal.prototype (has: breathe)
// Animal.prototype.[[Prototype]] = Object.prototype (has: toString, hasOwnProperty, etc.)
// Object.prototype.[[Prototype]] = null (end of chain)

Object.getPrototypeOf(rex) === Dog.prototype      // true
Object.getPrototypeOf(Dog.prototype) === Animal.prototype  // true

// Prototype manipulation
Object.create(proto)                    // create object with specific prototype
Object.getPrototypeOf(obj)              // get prototype
Object.setPrototypeOf(obj, proto)       // set prototype (avoid -- slow!)

// hasOwnProperty vs in
rex.hasOwnProperty("bark")              // false (bark is on Dog.prototype)
"bark" in rex                           // true (found in chain)

// Property descriptor
Object.defineProperty(obj, "id", {
  value: 42, writable: false, enumerable: false, configurable: false
});
Object.getOwnPropertyDescriptor(obj, "id"); // { value:42, writable:false, ... }

04 What is currying and partial application in JavaScript?

Functional

// Currying -- transform f(a, b, c) into f(a)(b)(c)
const curry = (fn) => {
  const arity = fn.length;
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
};

const add = curry((a, b, c) => a + b + c);
add(1)(2)(3);    // 6
add(1, 2)(3);    // 6
add(1)(2, 3);    // 6
add(1, 2, 3);    // 6

// Partial application -- pre-fill some arguments
const multiply = (a, b) => a * b;
const double   = multiply.bind(null, 2);
const triple   = multiply.bind(null, 3);
double(5);   // 10
triple(5);   // 15

// More flexible partial with closures
const partial = (fn, ...presetArgs) =>
  (...laterArgs) => fn(...presetArgs, ...laterArgs);

const greet = (greeting, name) => `${greeting}, ${name}!`;
const hello = partial(greet, "Hello");
const hi    = partial(greet, "Hi");
hello("Alice"); // "Hello, Alice!"
hi("Bob");      // "Hi, Bob!"

// Real-world: point-free programming
const users = [
  { name: "Alice", role: "admin" },
  { name: "Bob",   role: "user" }
];

const hasRole = curry((role, user) => user.role === role);
const isAdmin = hasRole("admin");

users.filter(isAdmin);    // [{ name:"Alice", role:"admin" }]
users.some(isAdmin);      // true

05 What is memoisation in JavaScript?

Performance Memoisation caches the results of a function so that repeated calls with the same arguments return the cached result instead of re-executing.

// Simple memoisation decorator
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Expensive function
const fibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

fibonacci(40);  // fast (each sub-problem computed once)

// Cache with TTL (time-to-live)
function memoizeWithTTL(fn, ttlMs = 60_000) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    const entry = cache.get(key);
    if (entry && Date.now() - entry.timestamp < ttlMs)
      return entry.value;
    const value = fn.apply(this, args);
    cache.set(key, { value, timestamp: Date.now() });
    return value;
  };
}

const fetchUserCached = memoizeWithTTL(fetchUser, 5 * 60 * 1000); // 5 min

// Cache with WeakMap for object arguments (GC-friendly)
function memoizeObject(fn) {
  const cache = new WeakMap();  // keys are objects (GC'd when objects are gone)
  return (obj) => {
    if (!cache.has(obj)) cache.set(obj, fn(obj));
    return cache.get(obj);
  };
}

// React.memo, useMemo, useCallback are all forms of memoisation
// Libraries: memoizee, fast-memoize, lodash.memoize

06 What is function composition in JavaScript?

Functional Function composition creates a new function by combining multiple functions so the output of one becomes the input of the next. Core to functional programming.

// Compose: right-to-left (mathematical convention)
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

// Pipe: left-to-right (more readable for data pipelines)
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const trim  = s => s.trim();
const lower = s => s.toLowerCase();
const split = s => s.split(" ");

const normalise = pipe(trim, lower, split);
normalise("  Hello World  ");  // ["hello", "world"]

// Real-world data transformation pipeline
const processUser = pipe(
  user => ({ ...user, name: user.name.trim() }),
  user => ({ ...user, email: user.email.toLowerCase() }),
  user => ({ ...user, createdAt: new Date(user.createdAt) }),
  user => ({ ...user, isActive: user.status === "active" })
);

processUser(rawUser);

// Transducers -- composable, performant array transformations
// (Single pass instead of chaining map/filter each creating intermediate arrays)
const xform = compose(
  map(x => x * 2),        // these are "transducer" versions
  filter(x => x > 5)
);
// Process a million items: one pass, no intermediate arrays created

// Point-free style
const double    = x => x * 2;
const isPositive = x => x > 0;
const doublePositives = arr => arr.filter(isPositive).map(double);
// vs point-free equivalent using compose and curried versions

07 How does JavaScript manage memory? What causes memory leaks?

Memory JavaScript uses automatic garbage collection โ€” the engine reclaims memory for objects that are no longer reachable from a root (the global object, call stack, or active closures).

// Mark-and-sweep algorithm:
// 1. Start from "roots" (global, stack, closure variables)
// 2. Mark all reachable objects
// 3. Sweep (collect) unmarked objects

// Common memory leak causes:

// 1. Forgotten event listeners
const btn = document.getElementById("btn");
const handler = () => console.log("click");
btn.addEventListener("click", handler);
// Removing the element doesn't remove the listener
// Fix:
btn.removeEventListener("click", handler);
btn.remove();

// 2. Growing global variables / accidentally global
function leak() {
  forgottenVar = "oops";  // no var/let/const โ†’ becomes global
}

// 3. Closures retaining references to large objects
function createLeak() {
  const largeData = new Array(1_000_000).fill("data");
  return () => largeData.length; // closes over largeData
}
let fn = createLeak();
// fn holds largeData alive even if we don't need it
fn = null; // release to allow GC

// 4. Detached DOM nodes
let div = document.getElementById("content");
document.body.removeChild(div);
// div variable still holds reference โ†’ div and all children stay in memory
div = null; // release

// 5. setInterval not cleared
const intervalId = setInterval(() => heavyTask(), 1000);
clearInterval(intervalId); // clear when no longer needed

// WeakRef -- hold a reference that doesn't prevent GC
const cache = new Map();
function getCached(key, obj) {
  const ref = cache.get(key);
  const value = ref?.deref();    // deref() returns undefined if GC'd
  if (value !== undefined) return value;
  cache.set(key, new WeakRef(obj));
  return obj;
}

// FinalizationRegistry -- callback when an object is GC'd
const registry = new FinalizationRegistry((key) => cache.delete(key));

08 What are advanced async patterns in JavaScript?

Async

// Async generator -- lazily produce async values
async function* paginate(url) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (!data.items.length) return;
    yield data.items;
    page++;
  }
}

for await (const items of paginate("/api/products")) {
  await processItems(items);
}

// AbortController -- cancel fetch
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // timeout after 5s

try {
  const res = await fetch("/api/data", { signal: controller.signal });
} catch (err) {
  if (err.name === "AbortError") console.log("Request cancelled");
}

// Semaphore pattern -- limit concurrent requests
class Semaphore {
  constructor(max) { this.max = max; this.queue = []; this.count = 0; }
  async acquire() {
    if (this.count < this.max) { this.count++; return; }
    await new Promise(resolve => this.queue.push(resolve));
    this.count++;
  }
  release() {
    this.count--;
    this.queue.shift()?.();
  }
}

const sem = new Semaphore(5);  // max 5 concurrent
await Promise.all(urls.map(async (url) => {
  await sem.acquire();
  try { return await fetch(url); }
  finally { sem.release(); }
}));

// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    try { return await fetch(url); }
    catch (err) {
      if (attempt === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 2 ** attempt * 100));
    }
  }
}

09 What are JavaScript design patterns?

Patterns

// Module pattern -- encapsulate state, expose public API
const counter = (() => {
  let count = 0;                       // private
  return {
    increment: () => ++count,
    decrement: () => --count,
    value:     () => count
  };
})();  // IIFE -- runs immediately

counter.increment(); // 1
counter.count;       // undefined (private)

// Observer pattern -- publish/subscribe
class EventEmitter {
  #listeners = new Map();

  on(event, cb)  { (this.#listeners.get(event) ?? this.#listeners.set(event, new Set()).get(event)).add(cb); }
  off(event, cb) { this.#listeners.get(event)?.delete(cb); }
  emit(event, ...args) { this.#listeners.get(event)?.forEach(cb => cb(...args)); }
  once(event, cb) {
    const wrapper = (...args) => { cb(...args); this.off(event, wrapper); };
    this.on(event, wrapper);
  }
}

const emitter = new EventEmitter();
emitter.on("data", data => console.log(data));
emitter.emit("data", { id: 1 });

// Singleton pattern
class Config {
  static #instance = null;
  static getInstance() { return (Config.#instance ??= new Config()); }
  constructor() { if (Config.#instance) throw new Error("Use Config.getInstance()"); }
}

// Factory pattern
function createUser(type) {
  const templates = {
    admin:  { role: "admin",  permissions: ["read","write","delete"] },
    editor: { role: "editor", permissions: ["read","write"] },
    viewer: { role: "viewer", permissions: ["read"] }
  };
  return { ...templates[type], id: crypto.randomUUID(), createdAt: new Date() };
}

// Command pattern -- encapsulate actions as objects (undo/redo)
const history = [];
function execute(command) { command.execute(); history.push(command); }
function undo() { history.pop()?.undo(); }

10 What are private class fields and modern class features in JavaScript?

ES2022

class BankAccount {
  // Private fields (# prefix) -- enforced by the runtime
  #balance = 0;
  #owner;
  #transactions = [];

  // Private static field
  static #interestRate = 0.05;

  // Public class field
  currency = "GBP";

  constructor(owner, initialBalance = 0) {
    this.#owner   = owner;
    this.#balance = initialBalance;
  }

  // Private method
  #log(action, amount) {
    this.#transactions.push({ action, amount, date: new Date() });
  }

  deposit(amount) {
    if (amount <= 0) throw new Error("Amount must be positive");
    this.#balance += amount;
    this.#log("deposit", amount);
    return this;  // enable chaining
  }

  withdraw(amount) {
    if (amount > this.#balance) throw new Error("Insufficient funds");
    this.#balance -= amount;
    this.#log("withdraw", amount);
    return this;
  }

  // Getter (computed property access)
  get balance() { return this.#balance; }
  get owner()   { return this.#owner; }

  // Static method
  static getInterestRate() { return BankAccount.#interestRate; }
}

const acc = new BankAccount("Alice", 1000);
acc.deposit(500).withdraw(200);  // chained
acc.balance;     // 1300
// acc.#balance; // SyntaxError (private from outside)

// Private field access check
"#balance" in acc;  // true (in check works with private fields)

11 What are modern JavaScript features from ES2022โ€“2025?

Modern JS

// ES2022
// 1. Object.hasOwn() -- safer than hasOwnProperty
Object.hasOwn({ a: 1 }, "a");   // true (works even for null-prototype objects)

// 2. at() -- indexed access from end
[1,2,3,4,5].at(-1);  // 5 (last element)
"hello".at(-1);       // "o"

// 3. Array.prototype.findLast / findLastIndex
[1,2,3,4,5].findLast(x => x % 2 === 0);      // 4
[1,2,3,4,5].findLastIndex(x => x % 2 === 0); // 3

// ES2023
// 4. Array methods that don't mutate (return new array)
const arr = [3, 1, 4, 1, 5];
arr.toSorted((a,b) => a - b);  // [1,1,3,4,5] (arr unchanged)
arr.toReversed();               // [5,1,4,1,3] (arr unchanged)
arr.toSpliced(2, 1, 99);        // [3,1,99,1,5] (arr unchanged)
arr.with(2, 99);                // [3,1,99,1,5] (arr unchanged)

// 5. Object.groupBy() -- ES2024
const people = [{name:"Alice",dept:"eng"},{name:"Bob",dept:"eng"},{name:"Carol",dept:"hr"}];
Object.groupBy(people, p => p.dept);
// { eng: [...], hr: [...] }

// 6. Promise.withResolvers() -- ES2024
const { promise, resolve, reject } = Promise.withResolvers();
// Instead of: let resolve; const p = new Promise(r => resolve = r);

// 7. Iterator helpers -- ES2025 (Stage 4)
function* naturals() { let n = 1; while (true) yield n++; }
naturals()
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(5)
  .toArray();  // [4, 16, 36, 64, 100]

// 8. Set methods -- ES2025 (Stage 4)
const a = new Set([1,2,3,4]);
const b = new Set([3,4,5,6]);
a.union(b)        // Set {1,2,3,4,5,6}
a.intersection(b) // Set {3,4}
a.difference(b)   // Set {1,2}
a.isSubsetOf(b)   // false

12 What is the difference between shallow and deep copying objects?

Core

const original = {
  name: "Alice",
  address: { city: "London", postcode: "SW1A" },
  hobbies: ["chess", "coding"]
};

// Shallow copy -- top-level properties are copied, nested are still shared
const shallow1 = Object.assign({}, original);
const shallow2 = { ...original };                 // spread (same as assign)
const shallow3 = Object.create(Object.getPrototypeOf(original),
                   Object.getOwnPropertyDescriptors(original));

// Mutating nested object affects BOTH copies
shallow1.address.city = "Manchester";
console.log(original.address.city); // "Manchester" -- shared reference!

// Mutating top-level is isolated
shallow1.name = "Bob";
console.log(original.name); // "Alice" -- top-level was copied

// Deep copy options
// 1. structuredClone (ES2022, modern) -- handles Date, Map, Set, RegExp, circular refs
const deep1 = structuredClone(original);
deep1.address.city = "Edinburgh";
console.log(original.address.city); // "London" -- independent!

// 2. JSON round-trip (simple, limited)
const deep2 = JSON.parse(JSON.stringify(original));
// โš  Loses: undefined, functions, Date (โ†’ string), Map, Set, circular refs

// 3. Lodash cloneDeep (handles edge cases)
import cloneDeep from "lodash/cloneDeep";
const deep3 = cloneDeep(original);

// structuredClone limitations:
// Cannot clone: functions, DOM nodes, WeakMap, WeakSet
// Can clone: Date, RegExp, Map, Set, ArrayBuffer, Error, circular references

// structuredClone for transferring (zero-copy)
const transferred = structuredClone(buffer, { transfer: [buffer] }); // moves memory

13 What is debouncing and throttling in JavaScript?

Performance

// Debounce -- delay execution until after N ms of silence
// Use for: search input, resize handler, form auto-save
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const searchInput = document.getElementById("search");
searchInput.addEventListener("input", debounce((e) => {
  fetchResults(e.target.value);  // only fires 300ms after user stops typing
}, 300));

// Throttle -- execute at most once every N ms
// Use for: scroll handler, mousemove, rate-limited API calls
function throttle(fn, interval) {
  let lastCall = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      return fn.apply(this, args);
    }
  };
}

window.addEventListener("scroll", throttle(() => {
  updateScrollIndicator();   // fires at most every 100ms
}, 100));

// Leading vs trailing edge variants
function debounceLeading(fn, delay) {
  let timer;
  return function(...args) {
    if (!timer) fn.apply(this, args);  // fire immediately on first call
    clearTimeout(timer);
    timer = setTimeout(() => timer = null, delay);
  };
}

// Comparison:
// Debounce: "wait until quiet" -- batch rapid events, fire ONCE after silence
// Throttle: "fire at most every N ms" -- regular pacing, never misses start

// Libraries: lodash.debounce, lodash.throttle provide trailing/leading/cancel options

14 What are tagged template literals and their practical uses?

ES6

// Tag function signature: (strings: TemplateStringsArray, ...values: any[]) => any
function tag(strings, ...values) {
  console.log(strings);    // ["Hello, ", "! You are ", " years old."] (static parts)
  console.log(values);     // ["Alice", 30] (interpolated values)
  return strings.reduce((result, str, i) =>
    `${result}${str}${values[i] ?? ""}`, "");
}

const name = "Alice", age = 30;
tag`Hello, ${name}! You are ${age} years old.`;

// 1. SQL safe parameterised queries
function sql(strings, ...values) {
  const query  = strings.reduce((q, str, i) => `${q}${str}${i < values.length ? "?" : ""}`, "");
  const params = values;
  return { query, params };
}
const userId = 42;
sql`SELECT * FROM users WHERE id = ${userId}`;
// { query: "SELECT * FROM users WHERE id = ?", params: [42] }
// NEVER interpolated directly into the query string

// 2. HTML sanitisation (styled-components approach)
function safeHtml(strings, ...values) {
  return strings.reduce((html, str, i) => {
    const val = values[i] !== undefined
      ? String(values[i]).replace(/&/g,"&amp;").replace(/</g,"<").replace(/>/g,">")
      : "";
    return html + str + val;
  }, "");
}
const userInput = "<script>alert('xss')</script>";
safeHtml`<p>Hello, ${userInput}</p>`;
// "<p>Hello, <script>alert('xss')</script></p>" (escaped!)

// 3. CSS in JS (styled-components)
const Button = styled.button`
  background: ${props => props.primary ? "blue" : "white"};
  color: ${props => props.primary ? "white" : "blue"};
`;

// 4. Internationalisation (i18n)
function i18n(strings, ...values) {
  const key = strings.raw.join("{%}");
  const translated = translations[key] ?? strings.join("");
  return values.reduce((s, v, i) => s.replace(`{${i}}`, v), translated);
}

15 What are Observables and how do they compare to Promises?

Async

// Promise -- single future value, eager, not cancellable
const promise = fetch("/api/user");  // starts immediately
promise.then(res => res.json()).then(user => console.log(user));

// Observable -- stream of future values, lazy, cancellable
// (RxJS implementation -- Observables in TC39 Stage 2 proposal)
import { Observable } from "rxjs";

const clicks$ = new Observable(subscriber => {
  const handler = (e) => subscriber.next(e);
  document.addEventListener("click", handler);
  // Teardown logic -- runs on unsubscribe
  return () => document.removeEventListener("click", handler);
});

// Subscribe
const sub = clicks$.subscribe({
  next: (event) => console.log("Click:", event),
  error: (err) =>  console.error(err),
  complete: () =>  console.log("Done")
});

sub.unsubscribe();  // stop and run teardown

// RxJS operators -- composable async transformations
import { fromEvent, interval } from "rxjs";
import { debounceTime, distinctUntilChanged, switchMap, map } from "rxjs/operators";

const search$ = fromEvent(document.getElementById("search"), "input").pipe(
  map(e => e.target.value),
  debounceTime(300),             // wait 300ms of silence
  distinctUntilChanged(),        // skip if value same as last
  switchMap(query =>             // cancel previous request on new keystroke
    ajax.getJSON(`/api/search?q=${query}`)
  )
);

search$.subscribe(results => renderResults(results));

// Promise vs Observable:
// Promise: one value, eager, not cancellable, not reusable
// Observable: 0..N values, lazy, cancellable, reusable, composable with operators

16 What are JavaScript error handling patterns?

Errors

// Custom error classes
class AppError extends Error {
  constructor(message, code, context = {}) {
    super(message);
    this.name    = this.constructor.name;
    this.code    = code;
    this.context = context;
    // Maintain proper stack trace in V8
    if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
  }
}

class ValidationError extends AppError {
  constructor(field, message) {
    super(message, "VALIDATION_ERROR", { field });
    this.field = field;
  }
}

class NetworkError extends AppError {
  constructor(status, url) {
    super(`HTTP ${status} at ${url}`, "NETWORK_ERROR", { status, url });
  }
}

// Result type pattern (functional error handling -- no throw)
const ok    = value  => ({ ok: true,  value });
const err   = error  => ({ ok: false, error });
const isOk  = result => result.ok === true;

async function safeParseJSON(text) {
  try { return ok(JSON.parse(text)); }
  catch (e) { return err(new ValidationError("body", e.message)); }
}

const result = await safeParseJSON(rawBody);
if (!isOk(result)) {
  return sendError(result.error);
}
const data = result.value;

// Global error handlers
window.addEventListener("unhandledrejection", event => {
  console.error("Unhandled promise rejection:", event.reason);
  event.preventDefault(); // suppress browser console error
  reportToSentry(event.reason);
});

window.addEventListener("error", event => {
  console.error("Uncaught error:", event.error);
  reportToSentry(event.error);
});

17 What are Web Workers and when do you use them?

Performance Web Workers run JavaScript on background threads, allowing CPU-intensive work without blocking the UI thread.

// Main thread -- create a worker
const worker = new Worker("worker.js");

// Send data to worker
worker.postMessage({ type: "PROCESS", data: bigArray });

// Receive results from worker
worker.onmessage = (event) => {
  console.log("Result:", event.data);
};

// Handle errors
worker.onerror = (err) => console.error(err);

// Terminate when done
worker.terminate();

// worker.js -- runs on a separate thread
self.onmessage = (event) => {
  const { type, data } = event.data;
  if (type === "PROCESS") {
    const result = expensiveComputation(data);
    self.postMessage(result);
  }
};

// Transferable objects -- zero-copy transfer (moves memory, faster)
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage({ buffer }, [buffer]);  // transfer ownership
// buffer is now neutered in main thread (cannot access it)

// SharedArrayBuffer -- shared memory between threads
const shared = new SharedArrayBuffer(1024);
const view   = new Int32Array(shared);
// Both main thread and workers can read/write to shared

// Atomics -- synchronise shared memory access
Atomics.add(view, 0, 1);     // atomic increment
Atomics.compareExchange(view, 0, expected, desired); // CAS
Atomics.wait(view, 0, expected); // sleep until value changes

// Use cases: image processing, data compression, WebAssembly execution,
// large JSON parsing, cryptography, machine learning inference

18 What are performance measurement APIs in the browser?

Performance

// Performance.now() -- high-resolution timer (sub-millisecond)
const start = performance.now();
heavyComputation();
const end  = performance.now();
console.log(`Took ${end - start}ms`);

// User Timing API -- named marks and measures
performance.mark("start-render");
renderComponent();
performance.mark("end-render");
performance.measure("render", "start-render", "end-render");

const measures = performance.getEntriesByType("measure");
measures.forEach(m => console.log(`${m.name}: ${m.duration}ms`));
performance.clearMarks();
performance.clearMeasures();

// Navigation Timing -- page load breakdown
const nav = performance.getEntriesByType("navigation")[0];
console.log("DNS lookup:", nav.domainLookupEnd - nav.domainLookupStart);
console.log("TCP connect:", nav.connectEnd - nav.connectStart);
console.log("TTFB:", nav.responseStart - nav.requestStart);
console.log("DOM Content Loaded:", nav.domContentLoadedEventEnd);
console.log("Total load:", nav.loadEventEnd);

// Resource Timing -- per-resource timing
performance.getEntriesByType("resource")
  .filter(r => r.initiatorType === "fetch")
  .forEach(r => console.log(r.name, r.duration));

// Long Task observer -- detect tasks blocking the main thread
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    if (entry.duration > 50) console.warn("Long task:", entry.duration);
  });
});
observer.observe({ type: "longtask", buffered: true });

// Core Web Vitals measurement
new PerformanceObserver(list => {
  list.getEntries().forEach(entry => {
    if (entry.entryType === "largest-contentful-paint")
      console.log("LCP:", entry.startTime);
  });
}).observe({ type: "largest-contentful-paint", buffered: true });

19 What is the JavaScript module system? CommonJS vs ESM?

Modules

// CommonJS (CJS) -- Node.js original module system
// Runtime evaluation (synchronous, dynamic)
const path = require("path");           // any time, any place, dynamic
const { join } = require("path");

module.exports = { add, subtract };     // export an object
module.exports = function() {};         // export a single value
exports.greet = () => "Hello";         // add to exports object

// ES Modules (ESM) -- modern standard (Node.js 12+, all browsers)
// Static analysis (parse-time, tree-shakeable)
import path from "path";               // static, top-level only
import { join } from "path";           // named import
import * as pathModule from "path";    // namespace

export function add(a, b) { return a + b; }
export default class MyClass {}

// Key differences:
// CJS: synchronous (require blocks), dynamic (can require() in if/else)
//      module.exports is mutable, no tree-shaking
// ESM: asynchronous (import() returns Promise), static, live bindings
//      enables tree-shaking (dead code elimination), top-level await

// Top-level await (ESM only)
// database.js
export const db = await connectToDatabase();
// Can only be done in ESM context!

// Dynamic import (works in both)
const module = await import("./heavy.js"); // code splitting
const { default: lib } = await import("./lib.js");

// Node.js: specify type
// package.json: "type": "module"  โ†’ .js files are ESM
//               "type": "commonjs" โ†’ .js files are CJS (default)
// Or: use .mjs for ESM, .cjs for CJS regardless of package type

// Interop caveats:
// CJS can require() ESM? -- NO (CJS cannot import async ESM modules)
// ESM can import CJS? -- YES (CJS default exports become ESM default imports)

20 What is the JavaScript internationalisation API (Intl)?

Standard Library

// Intl.NumberFormat -- localised number formatting
const gbp = new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP" });
gbp.format(1234567.89);            // "ยฃ1,234,567.89"

const pct = new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1 });
pct.format(0.1567);               // "15.7%"

const compact = new Intl.NumberFormat("en", { notation: "compact" });
compact.format(1_500_000);        // "1.5M"

// Intl.DateTimeFormat -- localised date/time
const fmt = new Intl.DateTimeFormat("en-GB", {
  weekday: "long", year: "numeric", month: "long", day: "numeric"
});
fmt.format(new Date());           // "Wednesday, 22 April 2026"

const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
relative.format(-1, "day");       // "yesterday"
relative.format(3, "week");       // "in 3 weeks"

// Intl.Collator -- locale-aware string sorting
const collator = new Intl.Collator("de"); // German locale
["Mรคuse", "Maus", "Meer"].sort(collator.compare);

// Intl.PluralRules -- handle plural forms correctly
const rules = new Intl.PluralRules("en-US");
rules.select(1);   // "one"
rules.select(2);   // "other"

// Intl.ListFormat -- format lists naturally
const lf = new Intl.ListFormat("en", { type: "conjunction" });
lf.format(["Alice", "Bob", "Carol"]);  // "Alice, Bob, and Carol"

// Intl.Segmenter -- locale-aware text segmentation
const seg = new Intl.Segmenter("en", { granularity: "word" });
[...seg.segment("Hello world!")].map(s => s.segment);  // ["Hello", " ", "world", "!"]

21 What are JavaScript performance patterns โ€” virtual DOM, reconciliation, and rendering?

Performance

// DOM operations are slow -- batching is key
// BAD: forces layout/reflow on every iteration
for (let i = 0; i < 1000; i++) {
  document.getElementById("list").innerHTML += `<li>${i}</li>`;
}

// GOOD: build in memory, single DOM write
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = i;
  fragment.appendChild(li);
}
document.getElementById("list").appendChild(fragment);

// requestAnimationFrame -- batch visual updates to browser's render cycle
function animate() {
  updatePositions();         // do calculations
  requestAnimationFrame(animate); // schedule next frame
}
requestAnimationFrame(animate); // starts the loop

// Avoid layout thrashing -- don't read and write layout in the same frame
// BAD (triggers layout on every iteration):
for (const el of elements) {
  el.style.width = (el.offsetWidth * 2) + "px";  // read then write interleaved
}
// GOOD: batch reads, then writes
const widths = elements.map(el => el.offsetWidth); // all reads
elements.forEach((el, i) => el.style.width = (widths[i] * 2) + "px"); // all writes

// IntersectionObserver -- efficient lazy loading (no scroll event needed)
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadImage(entry.target);
      observer.unobserve(entry.target);
    }
  });
}, { rootMargin: "200px" }); // pre-load 200px before visible

document.querySelectorAll("img[data-src]").forEach(img => observer.observe(img));

// ResizeObserver -- watch element size changes
const ro = new ResizeObserver(entries => {
  for (const entry of entries)
    console.log(entry.contentRect.width, entry.contentRect.height);
});
ro.observe(document.getElementById("chart"));

📝 Knowledge Check

🧠 Quiz Question 1 of 5

What is the key difference between a generator function and a regular function?





🧠 Quiz Question 2 of 5

What does debounce do and why is it used for search inputs?





🧠 Quiz Question 3 of 5

What is the main advantage of using structuredClone over JSON.parse(JSON.stringify()) for deep cloning?





🧠 Quiz Question 4 of 5

What is a common cause of memory leaks in JavaScript applications?





🧠 Quiz Question 5 of 5

What does the Proxy object enable that regular objects cannot do?