Cookies are sent on every request and can be HttpOnly/Secure — right for auth tokens. localStorage is JS-readable and persists indefinitely — right for non-sensitive prefs. sessionStorage is JS-readable and dies with the tab — right for ephemeral wizard/draft state.
Microtasks (Promise reactions, queueMicrotask, MutationObserver) are drained ENTIRELY after each task and before rendering. Macrotasks (setTimeout, setInterval, DOM events, I/O) run ONE per loop iteration. So all pending Promise callbacks run before the next setTimeout — and an unbounded microtask chain can starve macrotasks and block paint.
Events flow capture (top → target) → target → bubble (target → top). Delegation puts ONE listener on a parent and uses event.target to handle children. Saves memory and works on dynamically added nodes.
async/await is syntax sugar over Promises — same machinery. `await` pauses the async function and schedules its continuation as a microtask when the awaited Promise settles. So `await x` ≈ `.then(continuation)`. The event loop treats both identically: continuations are microtasks, drained fully before the next macrotask.
Duplicate-style question on the event loop and async execution: JS hands async work to Web APIs, which queue callbacks (microtask for Promises, macrotask for timers/events) on completion; the event loop drains all microtasks then runs one macrotask whenever the call stack is empty.
Execution order: (1) all synchronous code on the call stack, (2) drain the entire microtask queue, (3) optional render, (4) one macrotask, then back to step 2. So output order is: sync → microtasks → (render) → one macrotask → microtasks again, etc. Work through console.log/setTimeout/Promise puzzles with this rule.
An execution context is the environment a piece of code runs in — it has a variable environment, scope chain, and `this`. The global context is created first; each function call pushes a new context on the call stack. Async behavior layers on top: Web APIs run async work off-thread and queue callbacks; the event loop pushes them back as new contexts when the stack is empty.
SPAs leak memory when references survive after a route or component unmounts. Detached DOM, event listeners, timers, and closures are the usual suspects.
JavaScript is single-threaded with one call stack. Async work (timers, fetch, events) is handed to Web APIs / the host. When it completes, a callback is queued. The event loop runs: when the call stack is empty, it drains ALL microtasks (Promises, queueMicrotask), then takes ONE macrotask (timer, I/O, event) and repeats — rendering can happen between macrotasks.
The full model: the call stack runs synchronous code; Web APIs handle async work off-thread; completed callbacks land in either the callback (macrotask) queue or the microtask queue; the event loop, when the stack is empty, drains all microtasks then runs one macrotask, repeating. Each component has a distinct role.
An interview theme bundling JS internals: closures (functions remembering their lexical scope), the event loop (single thread + queues scheduling async callbacks), Promises (objects representing future values with microtask reactions), and async/await (sugar over Promises). Expect output-prediction puzzles and 'explain how X works under the hood'.
V8 uses a generational, mostly-concurrent GC: young objects in a fast scavenger, survivors promoted to the old generation collected by mark-sweep-compact.
Four core JS concepts: closures (a function + its remembered lexical scope), the event loop (the scheduler that runs queued async callbacks on the single thread), hoisting (declarations are processed before execution — var/function hoisted, let/const in the temporal dead zone), and currying (transforming f(a,b,c) into f(a)(b)(c) via closures).
A service worker is a background JS thread that intercepts network requests for an origin. Pair with the Cache API to serve responses offline, push notifications, and background sync. Lifecycle: install → activate → fetch.
Click → DOM event dispatched (capture→target→bubble) → handler runs on the main thread → validation/state updates → fetch() called → browser builds the request, applies CORS/credentials/CSP checks, may do a preflight → DNS lookup → TCP handshake → TLS handshake → HTTP request bytes sent. The JS handler returns long before the response; the response callback is queued as a microtask.
Browser does DNS → TCP → TLS → HTTP, then parses HTML into the DOM. CSS builds the CSSOM (render-blocking). DOM + CSSOM → render tree → layout → paint → composite. Synchronous JS blocks the parser; defer/async unblock it.
Node.js is a JS runtime built on V8 that runs JS outside the browser, using libuv for async I/O. Its event loop has distinct phases — timers, pending callbacks, poll, check (setImmediate), close — and between every phase it drains microtasks (Promises) and process.nextTick (which runs even before Promises). Same single-threaded model, different phase structure than the browser.
V8 runs a generational, incremental, mostly-concurrent mark-and-sweep collector. GC is triggered by heap-size thresholds — minor GC (young generation, Scavenge) fires often and is fast; major GC (Mark-Compact, full heap) fires on memory pressure or idle. You can't manually trigger it from JS, only influence it by dropping references and avoiding accidental retainers (closures, listeners, detached DOM, globals).
V8 builds hidden classes (maps) per object shape. `delete` mutates the shape and forces the object into slow dictionary mode, evicting it from inline-cache fast paths.
Identify: confirm growth in Chrome DevTools Memory tab — take heap snapshots over time, compare, look for detached DOM nodes and growing object counts; use the Performance monitor / allocation timeline. Common causes: uncleared timers/intervals, un-removed event listeners, lingering subscriptions, closures holding large objects, caches without eviction, detached DOM. Fix: clean up in effect teardown / componentWillUnmount, use WeakMap, bound caches.