📰 Expert JavaScript Interview Questions
This lesson targets senior JavaScript engineers and architects. Topics include V8 engine internals, the JIT compiler, hidden classes, garbage collection, TypedArrays, WebAssembly, Content Security Policy, the scheduler API, speculation rules, temporal API, pattern matching proposals, and production architecture decisions. These questions reveal whether you understand JavaScript deeply or just use it.
Questions & Answers
01 How does the V8 JavaScript engine work? What is JIT compilation? ►
V8 Internals V8 (Chrome/Node.js engine) compiles JavaScript to native machine code using a multi-tier JIT system. It does not interpret bytecode line-by-line โ it compiles and optimises hot code paths.
V8 pipeline:
- Parser โ produces an AST from source
- Ignition โ bytecode interpreter, fast startup, generates type feedback
- Maglev โ mid-tier JIT (V8 v11+), compiles hot functions quickly
- TurboFan โ optimising JIT, generates highly optimised native code using type feedback from Ignition
- Deoptimisation โ if assumptions break (e.g., argument type changes), TurboFan deoptimises back to Ignition
// Type feedback -- V8 tracks what types flow through each operation
function add(a, b) { return a + b; }
add(1, 2); // V8 learns: a=int, b=int โ compiles fast integer add
add(1, 2);
add(1, 2); // now "hot" โ TurboFan compiles it with int assumption
add("1", "2"); // DEOPT! string instead of int โ back to interpreted mode
// This is why type stability matters for performance:
// Consistent types โ JIT produces fast code
// Inconsistent types โ JIT deoptimises repeatedly
// Check optimisation status (Node.js)
function myFn(x) { return x * 2; }
// Call multiple times to trigger JIT
for (let i = 0; i < 1000; i++) myFn(i);
// --allow-natives-syntax flag:
// %OptimizeFunctionOnNextCall(myFn)
// myFn(42)
// console.log(%GetOptimizationStatus(myFn)) // 1 = optimised
// Inline caches (ICs) -- V8 caches property access paths
const point = { x: 1, y: 2 };
point.x; // IC miss: look up 'x' in the object's hidden class โ cache the path
point.x; // IC hit: use cached offset directly (fast!)
point.x; // IC hit again
02 What are hidden classes (shapes) in V8 and how do they affect performance? ►
V8 Internals V8 uses hidden classes (called “Shapes” or “Maps” internally) to enable efficient property access. Objects with the same structure share a hidden class, allowing V8 to compile property accesses to simple memory offsets.
// Hidden class transition diagram
const p1 = {}; // HC_empty
p1.x = 1; // HC_x (new hidden class created)
p1.y = 2; // HC_x_y (another transition)
const p2 = {}; // HC_empty
p2.x = 3; // HC_x (same as p1 -- shared!)
p2.y = 4; // HC_x_y (same as p1 -- shared!)
// p1 and p2 share the same hidden class โ V8 compiles access once for both
// โ Different property addition order = different hidden classes
const a = {}; a.x = 1; a.y = 2; // HC: {} โ {x} โ {x,y}
const b = {}; b.y = 1; b.x = 2; // HC: {} โ {y} โ {y,x} DIFFERENT!
// V8 cannot share the inline cache for a and b
// โ
Always add properties in the same order
// โ
Better: initialise all properties in the constructor
class Point {
constructor(x, y) {
this.x = x; // always same order โ same hidden class
this.y = y;
}
}
// โ Adding properties conditionally changes the hidden class
function createPoint(hasZ) {
const p = { x: 1, y: 2 };
if (hasZ) p.z = 0; // some points get HC_x_y, some get HC_x_y_z
return p;
}
// โ delete disrupts hidden classes (property is removed = new HC)
delete point.x; // creates a new hidden class, loses optimisation
// Object.create(null) -- prototype-less object (faster property lookup)
const dict = Object.create(null); // no prototype chain to walk
// Monomorphic, polymorphic, megamorphic inline caches
// Monomorphic: one shape โ fastest (single IC hit)
// Polymorphic: 2-4 shapes โ fast (IC with type check)
// Megamorphic: 5+ shapes โ slow (falls back to hash table lookup)
03 What are TypedArrays and ArrayBuffers? When do you use them? ►
Binary Data ArrayBuffer is a fixed-length raw binary data buffer in memory. TypedArray views provide a typed interface to read and write the buffer โ with zero-overhead numerical access (no boxing).
// ArrayBuffer -- raw memory (16 bytes here)
const buf = new ArrayBuffer(16);
// TypedArray views -- different interpretations of the same buffer
const int8 = new Int8Array(buf); // 16 signed 8-bit integers
const uint16 = new Uint16Array(buf); // 8 unsigned 16-bit integers
const float32 = new Float32Array(buf); // 4 32-bit floats
const float64 = new Float64Array(buf); // 2 64-bit floats
// Write with one view
float32[0] = 3.14;
// DataView -- precise control over byte order (endianness)
const view = new DataView(buf);
view.setInt32(0, 0x12345678, true); // little-endian
view.getInt32(0, false); // read as big-endian
// TypedArrays are used for:
// WebGL (GPU buffer data)
const vertices = new Float32Array([
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Web Audio (audio processing)
const audioBuffer = context.createBuffer(2, sampleRate, sampleRate);
const channelData = audioBuffer.getChannelData(0); // Float32Array
// File/network binary parsing (e.g., PNG header)
async function parsePngHeader(file) {
const buf = await file.arrayBuffer();
const view = new DataView(buf);
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
return sig.every((byte, i) => view.getUint8(i) === byte);
}
// SharedArrayBuffer -- shared between main thread and workers
const shared = new SharedArrayBuffer(4 * 1024); // 4KB shared
const arr = new Int32Array(shared);
Atomics.add(arr, 0, 1); // thread-safe increment
04 How does WebAssembly integrate with JavaScript? ►
WebAssembly WebAssembly (Wasm) is a binary instruction format that runs at near-native speed in the browser. JavaScript and Wasm can interoperate โ JS calls Wasm functions and vice versa.
// Compile and instantiate a Wasm module
const response = await fetch("module.wasm");
const buffer = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(buffer, {
// Import object -- functions Wasm can call in JS
env: {
memory: new WebAssembly.Memory({ initial: 1 }), // 64KB page
log: (value) => console.log("Wasm log:", value),
now: () => performance.now()
}
});
// Call Wasm exports
const { add, fibonacci, malloc, free } = instance.exports;
add(1, 2); // 3 (fast native code)
fibonacci(40); // very fast compared to JS
// Passing strings to Wasm (Wasm only understands numbers)
// Use the shared memory to pass byte arrays
const memory = instance.exports.memory;
const encoder = new TextEncoder();
function callWasmWithString(fn, str) {
const bytes = encoder.encode(str);
const ptr = malloc(bytes.length + 1);
const memView = new Uint8Array(memory.buffer, ptr, bytes.length + 1);
memView.set(bytes);
memView[bytes.length] = 0; // null terminator
const result = fn(ptr, bytes.length);
free(ptr);
return result;
}
// Compile C/C++/Rust โ Wasm
// C via Emscripten: emcc hello.c -o hello.js -s WASM=1
// Rust via wasm-pack: wasm-pack build --target web
// AssemblyScript: TypeScript-like syntax that compiles to Wasm
// Use cases: image/video processing, audio codecs, physics engines,
// cryptography, scientific computing, game engines (Doom3, Unity)
// Wasm limitations: no direct DOM access, no GC (heap managed by Wasm)
// WasmGC proposal (shipping in Chrome 119): garbage-collected Wasm objects
05 What is the Temporal API and how does it fix Date? ►
TC39 Proposal The Temporal API (TC39 Stage 3, shipping in browsers 2024-2025) is a modern replacement for the notoriously broken Date object. It is immutable, has explicit timezone support, and separates calendar date, wall-clock time, and instants.
// Problems with Date:
new Date(“2026-04-22”); // UTC midnight (time zone confusion!)
new Date(2026, 3, 22); // 0-indexed month! April is 3
date.getMonth(); // 0-indexed (0=Jan, 11=Dec)
date.setMonth(13); // mutates the original date
date.toLocaleString(); // browser-dependent output
// Temporal API — clean, immutable, explicit
// PlainDate — calendar date (no time, no timezone)
const today = Temporal.Now.plainDateISO(); // 2026-04-22
const launch = Temporal.PlainDate.from(“2026-04-22”);
const months0 = launch.month; // 4 (1-indexed!)
const nextYear = launch.add({ years: 1 }); // immutable — returns new object
// PlainTime — wall-clock time (no date, no timezone)
const time = Temporal.PlainTime.from(“14:30:00”);
// PlainDateTime — date + time, no timezone
const dt = Temporal.PlainDateTime.from(“2026-04-22T14:30:00”);
// ZonedDateTime — exact instant + timezone
const zoned = Temporal.ZonedDateTime.from({
year: 2026, month: 4, day: 22, hour: 14, minute: 30,
timeZone: “Europe/London”
});
zoned.withTimeZone(“America/New_York”); // convert timezone (immutable)
// Instant — exact point in time (Unix timestamp with nanoseconds)
const now = Temporal.Now.instant();
const epoch = Temporal.Instant.fromEpochSeconds(0);
const elapsed = now.since(epoch);
elapsed.total({ unit: “years” }); // ~56
// Duration arithmetic
const d1 = Temporal.PlainDate.from(“2026-04-22”);
const d2 = Temporal.PlainDate.from(“2027-01-01”);
const diff = d1.until(d2); // Temporal.Duration { months: 8, days: 10 }
// Polyfill: @js-temporal/polyfill
06 What is the TC39 pattern matching proposal? ►
TC39 Proposal The TC39 Pattern Matching proposal (Stage 2) brings match expressions to JavaScript โ similar to Rust’s match or Haskell’s case expressions โ enabling powerful, exhaustive data-driven branching without long if/else chains or switch statements.
// Current workarounds for pattern matching:
// Complex if/else chains, switch statements with fallthrough bugs
// TC39 Pattern Matching proposal syntax
const result = match (response) {
when ({ status: 200, body: String(body) }) : { ok: true, body },
when ({ status: 404 }) : { ok: false, error: “Not found” },
when ({ status: Number(s) if s >= 500 }) : { ok: false, error: `Server error ${s}` },
else : { ok: false, error: “Unknown” }
};
// Pattern types:
// Literal: when (42), when (“hello”)
// Variable: when (String(s)), when (Number(n)) — binds and type-checks
// Array: when ([first, …rest])
// Object: when ({ x, y }) — destructuring
// Guard: when (x if x > 0) — conditional
// Pin: when (^variable) — match against a variable’s value
// Current Stage 2 — not yet in V8/SpiderMonkey
// Polyfill/compile with Babel: @babel/plugin-proposal-pattern-matching
// Comparison with existing alternatives:
// Switch: no block scoping, no patterns, fallthrough bugs
// if/else: verbose, no exhaustiveness checking
// match: concise, exhaustive, pattern-based, an expression (not statement)
// TypeScript users: ts-pattern library provides similar today
import { match, P } from “ts-pattern”;
const output = match(response)
.with({ status: 200, body: P.string }, ({ body }) => ({ ok: true, body }))
.with({ status: 404 }, () => ({ ok: false, error: “Not found” }))
.with({ status: P.number.gte(500) }, ({ status }) => ({ ok: false, error: `${status}` }))
.exhaustive();
07 What are the Scheduler API and Speculation Rules for performance? ►
Performance
// Scheduler API — schedule tasks with priority (Chrome 94+)
// Priorities: “user-blocking”, “user-visible”, “background”
// Schedule low-priority work (doesn’t block user interactions)
scheduler.postTask(() => {
processAnalytics();
}, { priority: “background” });
// User-blocking (high priority — for critical UI updates)
scheduler.postTask(() => {
updateCriticalUI();
}, { priority: “user-blocking” });
// With delay
scheduler.postTask(() => prefetchNextPage(), {
priority: “background”,
delay: 2000
});
// Yield to the browser between chunks of work (prevents long tasks)
async function processLargeDataset(items) {
for (let i = 0; i < items.length; i++) {
process(items[i]);
if (i % 100 === 0) {
await scheduler.yield(); // yield control to browser, then resume
}
}
}
// isInputPending — check if user interaction is waiting
// (Cooperative scheduling API)
function processWhileIdle(tasks) {
while (tasks.length) {
if (navigator.scheduling?.isInputPending()) return; // user is typing/clicking
tasks.shift()();
}
}
// Speculation Rules API — pre-render pages (Chrome 109+)
// <script type=”speculationrules”>
// {
// “prerender”: [{ “where”: { “href_matches”: “/products/*” } }],
// “prefetch”: [{ “where”: { “selector_matches”: “a.prefetch” } }]
// }
// </script>
// Full pre-render (JavaScript execution) for instant navigation
08 What is Content Security Policy (CSP) and how does it protect JavaScript? ►
Security CSP is a browser security mechanism that restricts which resources can load and execute, mitigating XSS attacks.
// HTTP header or meta tag
// Content-Security-Policy: default-src ‘self’; script-src ‘self’ cdn.example.com; …
// Strict CSP (nonce-based) — best practice
// Server generates a random nonce per request
const nonce = crypto.randomBytes(16).toString(“base64″);
// Response header:
// Content-Security-Policy: script-src ‘nonce-RANDOM_NONCE’ ‘strict-dynamic’
// HTML: only scripts with the matching nonce execute
// <script nonce=”RANDOM_NONCE”>…</script>
// <script src=”app.js” nonce=”RANDOM_NONCE”></script>
// Injected script tags (XSS) have no nonce โ blocked
// ‘strict-dynamic’ — propagates trust to scripts loaded by trusted scripts
// Allows dynamically loaded scripts without adding more ‘unsafe’ directives
// CSP directives:
// script-src — which scripts can execute
// style-src — which stylesheets can load
// img-src — which images can load
// connect-src — which URLs can be fetched (fetch, XHR, WebSocket)
// frame-src — which URLs can be framed
// default-src — fallback for all directives
// Inline event handlers and eval() are blocked by CSP
// <button onclick=”…”> — blocked (use addEventListener)
// eval(“code”) — blocked (don’t use eval anyway)
// new Function(“code”) — blocked
// Trusted Types — strict CSP for DOM sinks
// Prevents unsafe DOM manipulation like innerHTML = untrustedHTML
const policy = trustedTypes.createPolicy(“default”, {
createHTML: (input) => DOMPurify.sanitize(input) // must sanitise
});
element.innerHTML = policy.createHTML(userInput); // only Trusted HTML allowed
// CSP reporting (collect violations)
// Content-Security-Policy-Report-Only: … report-to csp-endpoint
// Allows testing CSP without blocking
09 What is the JavaScript garbage collector and how does generational GC work in V8? ►
V8 Internals V8 uses a generational garbage collector: the heap is split into the young generation (short-lived objects) and old generation (long-lived). This matches the generational hypothesis โ most objects die young.
// V8 Heap layout:
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// โ Young generation (Nursery / Scavenger) โ
// โ Semi-space 1 โ Semi-space 2 (to-space) โ
// โ ~8MB each โ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
// โ Old generation (Major GC / Mark-Compact) โ
// โ Old pointer space | Old data space โ
// โ Code space | Map space | Large object space โ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Minor GC (Scavenger) — collects Young generation only
// Very fast (milliseconds): copies live objects from semi-space 1 โ 2
// Dead objects (most of them) are simply left behind
// Objects that survive 2 scavenges are promoted to Old generation
// Major GC (Mark-Compact) — collects Old generation
// Stop-the-world phases (mitigated by incremental + concurrent marking):
// 1. Mark: identify all live objects from GC roots (concurrent with JS execution)
// 2. Compact: move live objects together, update pointers (pauses JS)
// 3. Sweep: reclaim memory from dead objects
// Orinoco (V8’s concurrent GC):
// – Concurrent marking: mark objects while JS runs (background threads)
// – Parallel scavenging: minor GC on multiple threads
// – Incremental compaction: spread compaction over multiple steps
// Write barriers — needed for generational correctness
// If old object gets a reference to a young object, the GC must know
// about it (via the write barrier) to not miss it during minor GC
// V8 uses a remembered set to track old โ young pointers
// Node.js heap inspection
const v8 = require(“v8”);
v8.getHeapStatistics();
// { total_heap_size, used_heap_size, heap_size_limit,
// number_of_native_contexts, number_of_detached_contexts }
// Trigger GC (Node.js with –expose-gc flag)
global.gc(); // development/testing only
// Reduce GC pressure:
// – Reuse objects (object pooling)
// – Use typed arrays instead of object arrays for numerical data
// – Avoid creating large temporary objects in hot loops
// – Use ArrayPool-like patterns for buffers
10 What is monomorphism and why is consistent typing crucial for V8 performance? ►
V8 Internals
// V8 Inline Cache (IC) states:
// Uninitialized: first call, no feedback
// Monomorphic: all calls see same type โ fastest (single IC)
// Polymorphic: 2-4 types โ fast (type dispatch, still compiled)
// Megamorphic: 5+ types โ slow (falls back to property lookup table)
// MONOMORPHIC (fast)
function add(a, b) { return a + b; }
add(1, 2); // int + int
add(3, 4); // int + int (same types โ monomorphic IC)
// V8 compiles: fast integer addition, no type check needed
// POLYMORPHIC (slower but still compiled)
add(1, 2); // int + int
add(1.5, 2); // float + float
add(1n, 2n); // bigint + bigint โ 3 different type combos
// V8 compiles 3 specialised versions + a dispatch
// MEGAMORPHIC (slowest)
function getX(obj) { return obj.x; }
getX({ x: 1 }); // shape A
getX({ x: 1, y: 2 }); // shape B (different hidden class)
getX({ y: 2, x: 1 }); // shape C
getX([1, 2]); // shape D (array, has .x = undefined)
getX(“hello”); // shape E (string has no .x)
// 5+ shapes โ megamorphic โ generic hash lookup every time
// Practical implications:
// โ
Functions that process one type are fast
// โ Generic utility functions that accept any type are slow for hot paths
// Monomorphic container
const nums = [1, 2, 3, 4, 5]; // Fast: all elements are numbers
const mixed = [1, “two”, true, null]; // Slow: mixed types, HOLEY array
// Hole-free arrays are faster than holey arrays
const sparse = [1, , , 4]; // holey — slower element access
const dense = [1, 2, 3, 4]; // dense — fast
// Check your IC state
// node –trace-ic app.js (shows all inline cache misses)
11 What are the JavaScript engine’s approach to tail call optimisation? ►
Engine Proper Tail Call (PTC) optimisation โ when the last operation in a function is a recursive call โ allows the engine to reuse the current stack frame instead of creating a new one, preventing stack overflow in deep recursion.
// Regular recursion — O(n) stack frames
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n – 1); // n * result used AFTER recursive call
}
factorial(10_000); // RangeError: Maximum call stack size exceeded
// Tail call optimisation — accumulator pattern
function factTCO(n, acc = 1) {
“use strict”; // PTC requires strict mode
if (n <= 1) return acc;
return factTCO(n – 1, n * acc); // recursive call IS the final operation
}
// Technically safe in engines that implement PTC
// Reality: V8 does NOT implement PTC (Syntactic Tail Calls proposal abandoned)
// Safari JavaScriptCore DOES implement PTC
// Workarounds for deep recursion in V8:
// 1. Trampolining — manually manage the call stack
const trampoline = (fn) => (…args) => {
let result = fn(…args);
while (typeof result === “function”) result = result();
return result;
};
const fact = trampoline(function step(n, acc = 1) {
if (n <= 1) return acc;
return () => step(n – 1, n * acc); // return a thunk instead of calling
});
fact(100_000); // works! no stack overflow
// 2. Convert to iterative (always safe, often clearest)
function factIterative(n) {
let acc = 1;
while (n > 1) acc *= n–;
return acc;
}
// 3. Increase stack size (Node.js)
// node –stack-size=65536 app.js (for unavoidable deep recursion)
12 What is the JavaScript spec’s Abstract Operations and how does type coercion work? ►
Spec The ECMA-262 specification defines Abstract Operations โ internal algorithms that implement JavaScript’s type coercion rules. Understanding them explains all the “weird” JS behaviour.
// ToPrimitive(input, hint) — converts object to primitive
// hint: “number”, “string”, or “default”
// Calls [Symbol.toPrimitive] if exists, otherwise:
// hint=”string”: toString() then valueOf()
// hint=”number”/”default”: valueOf() then toString()
// ToNumber(value) abstract operation:
// undefined โ NaN
// null โ 0
// false โ 0, true โ 1
// string โ parseFloat, “” โ 0, “hello” โ NaN
// object โ ToPrimitive(hint=”number”) then ToNumber
Number(null); // 0
Number(undefined);// NaN
Number(“”); // 0
Number(” 3 “); // 3 (whitespace trimmed)
Number([]); // 0 ([] โ “” โ 0)
Number([3]); // 3 ([3] โ “3” โ 3)
Number([1,2]); // NaN ([1,2] โ “1,2” โ NaN)
// ToString(value):
// null โ “null”, undefined โ “undefined”
// true โ “true”, false โ “false”
// object โ ToPrimitive(hint=”string”) then ToString
String(null); // “null”
String([1,2,3]); // “1,2,3”
String({}); // “[object Object]”
// The == algorithm (Abstract Equality Comparison)
// Simplified rules:
// null == undefined โ true (special case)
// If types differ: coerce to number and compare
// [] == 0: [] โ “” โ 0; 0 == 0 โ true
// [] == “”: [] โ “” โ true
// {} == “[object Object]”: {} โ “[object Object]” โ true
// Symbol.toPrimitive — override coercion
class Money {
constructor(amount, currency) { this.amount = amount; this.currency = currency; }
[Symbol.toPrimitive](hint) {
if (hint === “string”) return `${this.currency}${this.amount}`;
if (hint === “number”) return this.amount;
return this.amount; // default
}
}
const price = new Money(9.99, “ยฃ”);
`Price: ${price}`; // “Price: ยฃ9.99” (string hint)
price + 1; // 10.99 (number hint)
13 What are service workers and the Cache API? ►
Browser APIs Service Workers are background scripts that run separately from the page. They act as a network proxy โ intercepting requests and serving cached responses โ enabling offline functionality and performance optimisation.
// Register a service worker
if (“serviceWorker” in navigator) {
const reg = await navigator.serviceWorker.register(“/sw.js”, { scope: “/” });
}
// sw.js — the service worker file
// Install — cache app shell
self.addEventListener(“install”, event => {
event.waitUntil(
caches.open(“v1”).then(cache =>
cache.addAll([“/”, “/app.js”, “/styles.css”, “/offline.html”])
)
);
self.skipWaiting(); // activate immediately
});
// Activate — clean up old caches
self.addEventListener(“activate”, event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== “v1”).map(k => caches.delete(k)))
)
);
self.clients.claim(); // take control of existing pages
});
// Fetch — intercept network requests (stale-while-revalidate strategy)
self.addEventListener(“fetch”, event => {
event.respondWith(
caches.match(event.request).then(cached => {
const fresh = fetch(event.request).then(response => {
const clone = response.clone();
caches.open(“v1”).then(cache => cache.put(event.request, clone));
return response;
});
return cached ?? fresh; // serve cached immediately, update in background
})
);
});
// Cache strategies:
// Cache First: serve cache, update in background (stale-while-revalidate)
// Network First: try network, fallback to cache (good for frequently changing data)
// Cache Only: always serve from cache (offline-only assets)
// Network Only: always fetch (analytics, non-cacheable)
// Background Sync: queue requests when offline, replay when back online
14 What are common JavaScript architectural patterns for large applications? ►
Architecture
// Micro-frontend architecture
// Each team owns an independently deployable frontend module
// Integration strategies:
// 1. Build-time: npm packages (tight coupling, synchronised deploys)
// 2. Runtime: Module Federation (Webpack 5)
// 3. Edge: ESI or server-side composition
// Webpack Module Federation
// Remote app (products team)
new ModuleFederationPlugin({
name: “products”,
filename: “remoteEntry.js”,
exposes: { “./ProductList”: “./src/ProductList” }
});
// Host app (shell)
new ModuleFederationPlugin({
name: “shell”,
remotes: { products: “products@https://products.example.com/remoteEntry.js” },
});
// Host usage
const ProductList = React.lazy(() => import(“products/ProductList”));
// Event-driven architecture (for large SPAs)
// Central event bus with typed events
class AppBus extends EventTarget {
emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail })); }
on(type, handler) { this.addEventListener(type, e => handler(e.detail)); }
}
const bus = new AppBus();
bus.on(“user:login”, ({ userId }) => loadUserData(userId));
bus.emit(“user:login”, { userId: 42 });
// Domain-driven design in frontend
// Bounded contexts: auth, billing, orders
// Each context has its own store, services, and UI components
// Cross-context communication: events, not direct imports
// Hexagonal architecture for the frontend
// Core domain (no framework dependencies)
// โ driven by: UI (React/Vue/Angular/Web Components)
// โ drives: HTTP adapter, storage adapter, analytics adapter
15 What is structured concurrency and the TC39 Async Context proposal? ►
TC39 Proposal
// Problem: context is lost across async boundaries
const requestId = “req-abc-123”;
async function handleRequest() {
await processData(); // requestId is unavailable here without explicitly passing it
}
// AsyncLocalStorage (Node.js) — stores context across async boundaries
import { AsyncLocalStorage } from “async_hooks”;
const requestContext = new AsyncLocalStorage();
app.use((req, res, next) => {
requestContext.run({ requestId: req.id, userId: req.userId }, next);
});
function getRequestId() { return requestContext.getStore()?.requestId; }
// TC39 AsyncContext proposal (Stage 2) — same concept for all JS environments
const context = new AsyncContext.Variable({ name: “requestId”, defaultValue: null });
async function handleRequest(id) {
await context.run(id, async () => {
await processData(); // context.get() === id anywhere in this async tree
});
}
function logWithContext(msg) {
console.log(`[${context.get()}] ${msg}`); // has the request ID even deep in callchain
}
// AsyncContext.Snapshot — capture and restore context
const snapshot = AsyncContext.Snapshot.wrap(() => {
// This callback inherits the context at the time of wrap()
processInBackground();
});
setTimeout(snapshot, 1000); // context still available when the callback runs
// Use cases: request tracing, logging, performance profiling,
// dependency injection without prop drilling
16 What are JavaScript performance anti-patterns? ►
Performance
// 1. Large bundle (high Time-to-Interactive)
// BAD: import everything
import _ from “lodash”; // 70KB minified
// GOOD: tree-shake or use individual functions
import debounce from “lodash/debounce”;
// BETTER: native alternatives (most lodash functions have native equivalents)
const debounced = nativeDebounce(fn, 300);
// 2. Layout thrashing
// BAD: read-write interleaved (each read triggers layout)
for (const el of elements) {
const width = el.offsetWidth; // READ — forces layout
el.style.width = width * 2 + “px”; // WRITE — invalidates layout
// next iteration: offsetWidth forces layout again โ thrashing!
}
// 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
// 3. Over-blocking the main thread (tasks > 50ms)
// BAD: process 100k items synchronously
items.forEach(process); // blocks for 2 seconds
// GOOD: chunk with scheduler.yield()
for (const [i, item] of items.entries()) {
process(item);
if (i % 100 === 0) await scheduler.yield();
}
// 4. Unnecessary re-renders (framework agnostic)
// Avoid creating new object/array literals in render
// BAD: every render creates a new array (breaks memoisation)
<Component items={[…data, extra]} /> // new array each render
// GOOD: memoize the derived value
const items = useMemo(() => […data, extra], [data, extra]);
// 5. Synchronous imports for large libraries
import HeavyLib from “heavy-lib”; // 500KB included in initial bundle
// GOOD: dynamic import
const HeavyLib = await import(“heavy-lib”); // loaded on demand
// 6. Long polling instead of Server-Sent Events or WebSocket
// BAD: setInterval(() => fetch(“/updates”), 1000) — 1 request/second
// GOOD: SSE or WebSocket for real-time updates
17 What is the CSS Houdini Worklet API and how does JavaScript interact with CSS? ►
Browser APIs CSS Houdini exposes browser rendering engine hooks to JavaScript, allowing custom paint, layout, and animation behaviours that are composited off the main thread.
// CSS Paint Worklet (Paint API) — custom CSS backgrounds/borders
// myPainter.js (worklet file)
registerPaint(“checkerboard”, class {
static get inputProperties() { return [“–grid-size”]; }
paint(ctx, geom, properties) {
const size = properties.get(“–grid-size”).value || 20;
for (let x = 0; x < geom.width; x += size) {
for (let y = 0; y < geom.height; y += size) {
ctx.fillStyle = (Math.floor(x/size) + Math.floor(y/size)) % 2
? “#000” : “#fff”;
ctx.fillRect(x, y, size, size);
}
}
}
});
// Register in main thread
await CSS.paintWorklet.addModule(“myPainter.js”);
// Use in CSS:
// .element { background: paint(checkerboard); –grid-size: 30; }
// CSS Typed OM (CSSOM v2) — typed property access
// Instead of strings:
element.style.opacity = “0.5”; // string manipulation, no validation
// Typed:
element.attributeStyleMap.set(“opacity”, CSS.number(0.5));
element.computedStyleMap().get(“width”).value; // number, not “100px”
// CSS Custom Properties in JavaScript
element.style.setProperty(“–color”, “red”);
const val = getComputedStyle(element).getPropertyValue(“–color”);
// Animation Worklet — off-main-thread animations
// Compositing-thread animations that don’t block main thread:
const animation = new WorkletAnimation(“spring”, keyframeEffect, document.timeline);
animation.play();
18 What are JavaScript decorators (TC39 Stage 3)? ►
TC39 Proposal JavaScript decorators (TC39 Stage 3, shipping in V8 130+) allow annotating classes and class members with functions that can add behaviour, replace methods, or register them.
// Class decorator — wraps the entire class
function singleton(Class) {
let instance = null;
return class extends Class {
constructor(…args) {
if (instance) return instance;
super(…args);
instance = this;
}
};
}
@singleton
class Config {
constructor(env) { this.env = env; }
}
new Config(“prod”) === new Config(“dev”); // true (same singleton)
// Method decorator — intercepts method calls
function log(target, context) {
const name = context.name;
return function(…args) {
console.log(`${name} called with:`, args);
const result = target.apply(this, args);
console.log(`${name} returned:`, result);
return result;
};
}
class Calculator {
@log
add(a, b) { return a + b; }
}
// Accessor decorator — wraps getter/setter
function range(min, max) {
return function(target, context) {
return {
get() { return target.get.call(this); },
set(value) {
if (value < min || value > max)
throw new RangeError(`${context.name} must be between ${min} and ${max}`);
target.set.call(this, value);
}
};
};
}
class Temperature {
@range(-273.15, 1e9)
accessor celsius = 0;
}
// Field initialiser decorator
function required(target, context) {
return function(initialValue) {
if (initialValue === undefined)
throw new Error(`${context.name} is required`);
return initialValue;
};
}
// Note: TC39 decorators use a new syntax (‘accessor’ keyword for auto accessors)
// TypeScript uses an older, incompatible decorator syntax (–experimentalDecorators)
// Use TypeScript 5.0+ with the new ‘decorators: true’ option for TC39 decorators
19 What is Shadow DOM and Web Components? ►
Web Standards Web Components are a suite of browser APIs for creating reusable custom HTML elements with encapsulated behaviour and styling โ no framework required.
// Custom Element — define a new HTML tag
class CounterElement extends HTMLElement {
static observedAttributes = [“initial”]; // watch these attributes
#count = 0;
#shadow;
constructor() {
super();
this.#shadow = this.attachShadow({ mode: “open” }); // open = JS accessible
this.#render();
}
connectedCallback() { /* element added to DOM */ }
disconnectedCallback() { /* element removed */ this.#cleanup(); }
attributeChangedCallback(name, oldVal, newVal) {
if (name === “initial”) this.#count = parseInt(newVal) || 0;
this.#render();
}
adoptedCallback() { /* moved to new document */ }
#render() {
this.#shadow.innerHTML = `
<style>
:host { display: inline-flex; gap: 8px; font-family: sans-serif; }
button { padding: 4px 12px; cursor: pointer; }
span { min-width: 2ch; text-align: center; }
</style>
<button id=”dec”>-</button>
<span>${this.#count}</span>
<button id=”inc”>+</button>
`;
this.#shadow.getElementById(“inc”).onclick = () => { this.#count++; this.#render(); };
this.#shadow.getElementById(“dec”).onclick = () => { this.#count–; this.#render(); };
}
#cleanup() { /* remove event listeners, observers */ }
}
customElements.define(“app-counter”, CounterElement);
// <app-counter initial=”5″></app-counter>
// HTML Templates (declarative)
const template = document.getElementById(“my-template”);
// <template id=”my-template”><p>Hello, <slot></slot>!</p></template>
const clone = template.content.cloneNode(true);
// Declarative Shadow DOM (SSR-friendly, no JS needed for initial render)
// <my-element>
// <template shadowrootmode=”open”>
// <style>…</style>
// <slot></slot>
// </template>
// Slot content here
// </my-element>
20 What is the WebGPU API and how does it differ from WebGL? ►
Browser APIs WebGPU (shipping in Chrome 113, Safari TP) is the modern GPU API for the web โ direct access to GPU for both rendering and compute workloads. It is the successor to WebGL with a modern, low-level API matching Metal/Vulkan/DirectX 12.
// WebGPU adapter and device
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) { alert(“WebGPU not supported”); return; }
// Configure a canvas
const canvas = document.getElementById(“canvas”);
const context = canvas.getContext(“webgpu”);
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
// Create a shader (WGSL — WebGPU Shading Language)
const shaderCode = `
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
let pos = array(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
return vec4f(pos[i], 0, 1);
}
@fragment fn fs() -> @location(0) vec4f { return vec4f(1, 0.5, 0, 1); }
`;
const module = device.createShaderModule({ code: shaderCode });
const pipeline = device.createRenderPipeline({
layout: “auto”,
vertex: { module, entryPoint: “vs” },
fragment: { module, entryPoint: “fs”, targets: [{ format }] }
});
// GPU compute (machine learning inference on the GPU)
const computeShader = device.createShaderModule({ code: `
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
output[id.x] = input[id.x] * 2;
}
`});
// WebGL vs WebGPU:
// WebGL: OpenGL ES 2.0/3.0 API, stateful, limited compute, CPU driver overhead
// WebGPU: Modern (Vulkan/Metal/DX12 style), stateless command buffers,
// first-class compute shaders, better GPU utilisation, WGSL
21 What are the most important JavaScript performance principles for large-scale applications? ►
Architecture
// 1. Core Web Vitals targets
// LCP (Largest Contentful Paint): < 2.5s (when main content loads)
// INP (Interaction to Next Paint): < 200ms (replaces FID)
// CLS (Cumulative Layout Shift): < 0.1 (visual stability)
// 2. Code splitting — only load what’s needed now
const Router = lazy(() => import(“./Router”)); // split at route level
const HeavyModal = lazy(() => import(“./HeavyModal”)); // split on interaction
// 3. Tree shaking — eliminate dead code
// Requires ES modules (not CJS)
// Ensure no side effects: sideEffects: false in package.json
// 4. Resource hints
// <link rel=”preload” href=”critical.js” as=”script”> — load soon
// <link rel=”prefetch” href=”next-page.js”> — low priority, for later
// <link rel=”preconnect” href=”https://cdn.example.com”> — warm up connection
// <link rel=”dns-prefetch” href=”https://api.example.com”>
// 5. Rendering performance budget
// 1 frame = 16.67ms at 60fps; 8.33ms at 120fps
// Budget breakdown:
// JS execution: 4ms
// Style calc: 1ms
// Layout: 1ms
// Paint: 1ms
// Composite: 1ms
// Remaining: 8ms buffer
// 6. Avoid main thread blocking
// Use Worker for: heavy computation, large data parsing
// Use CSS animations instead of JS for smooth 60fps (compositor thread)
// Use will-change: transform/opacity to promote to GPU layer (sparingly)
// 7. Measure first, optimise second
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries())
if (entry.duration > 50) reportLongTask(entry);
});
observer.observe({ type: “longtask”, buffered: true });
// 8. Speculation rules for near-instant page transitions
// <script type=”speculationrules”>
// {“prerender”: [{“where”:{“selector_matches”:”a[href^=’/products’]”}}]}
// </script>