📰 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,"&").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"));