=== compares value + type; == coerces the operands following a fixed algorithm before comparing. Always use === except for one defensible idiom: `x == null` to check for null OR undefined.
Category
Closures, async, prototypes, modules, and language internals.
72 questions
=== compares value + type; == coerces the operands following a fixed algorithm before comparing. Always use === except for one defensible idiom: `x == null` to check for null OR undefined.
Synchronous code blocks the main thread until it finishes — long loops, big JSON.parse, sync XHR, alert/confirm/prompt, document.write, deep recursion. While blocked, no events, no rendering, no timers. Mitigate: break into chunks (yield to microtask/macrotask), use Web Workers for CPU, async APIs only, and avoid the listed blocking calls in production.
Hoisting: declarations are processed before code runs. `var` declarations hoist to function scope, initialized to `undefined`. Function declarations hoist *with* their value. `let`/`const` hoist but stay in the Temporal Dead Zone until the declaration line — accessing them throws ReferenceError. Class declarations are TDZ too.
Debounce delays the call until activity stops; throttle caps how often the call can fire. Both control noisy events but solve different problems.
Shallow copy duplicates the top level; nested objects are still shared references. Deep copy recursively duplicates every level. Use `structuredClone` for a correct, fast deep copy.
`var` is function-scoped and hoisted to `undefined`. `let`/`const` are block-scoped with a temporal dead zone. `const` forbids reassignment but the value can still be mutated.
Arrow functions inherit `this`/`arguments` lexically, can't be used with `new`, and have no `prototype`. Regular functions get their own `this` based on the call site and can be constructors.
Object: string/symbol keys, prototype chain (collisions like `__proto__`), no insertion order guarantee for integer-like keys, JSON-friendly. Map: any key (including objects, NaN, null), preserves insertion order, has `.size`, no prototype chain. Use Map when keys are non-strings, you need size, or iteration order matters; object for JSON-shaped data.
Classical (Java/C++): classes are blueprints; objects are instances. Inheritance is declarative + compile-time. Prototypal (JS): objects inherit from other **objects** via the [[Prototype]] chain — runtime-flexible. ES6 `class` is syntactic sugar over prototypes. In practice both look similar; the JS difference is that you can compose objects dynamically (`Object.create`, mixins, delegation).
Shallow copy duplicates the top-level container but reuses inner references. Structural sharing reuses every untouched subtree across versions — the basis of efficient immutable data.
Wrap awaits in try/catch or attach `.catch` at the call site. Unhandled rejections in browsers fire `unhandledrejection` event and console warning; in Node, eventually crash (since v15). Distinguish error kinds (network, 4xx, 5xx, AbortError). Use error boundaries (React) at UI level. Don't swallow errors with empty catch — escalate or convert to domain errors.
ESM is statically analyzable, async, the standard. CJS is dynamic, sync, Node-historical. Modern code is ESM; CJS lives on for legacy compatibility. Mixing them is the source of many Node interop bugs.
Events propagate in three phases: capture (document → target), target, bubble (target → document). `addEventListener(fn)` defaults to bubble; pass `{ capture: true }` for capture. Stop with `stopPropagation()` (further ancestors only) or `stopImmediatePropagation()` (same-element siblings too). Some events don't bubble (`focus`, `blur`) — use `focusin`/`focusout`.
`this` is determined at call time by how the function is invoked. `call`/`apply` invoke immediately with a chosen `this`; `bind` returns a new function permanently bound to it.
Next.js SSR renders React on the server per request, sending fully-formed HTML + hydration payload. Pros: fast LCP, SEO, social previews. App Router uses React Server Components + streaming SSR by default; Pages Router uses `getServerSideProps`. Tradeoff: server cost + TTFB depends on data fetches; cache where possible.
Every object has a hidden [[Prototype]] pointer. Property lookup walks this chain. SDKs that need to extend native types do so safely by **subclassing or adding namespaced static helpers**, not patching `Array.prototype` etc. — that pollutes globals and can collide with merchant code. The safe API gives consumers an SDK object whose own prototype chain is private.
Every object has an internal `[[Prototype]]` link to another object. Property lookup walks that chain. `Object.create`, `class`, and constructor functions all set up the same chain.
Recurse with reduce + concat for clarity, or iterate with a stack to avoid stack overflow on deep nesting. Support a depth parameter to match Array.prototype.flat semantics.
Function declarations are fully hoisted (callable before the line). `var` is hoisted and initialized to undefined. `let`/`const`/`class` are hoisted but uninitialized — accessing them before the declaration throws (Temporal Dead Zone).
`async` makes a function return a Promise; `await` pauses the function until the awaited Promise settles. Same semantics as `.then` chains, but reads sequentially. Errors become rejected Promises; catch with try/catch or .catch at the call site. Independent awaits should be `Promise.all`'d, not sequential.
Use `class` for encapsulated kinds (component, model, service). Fields for state, methods on prototype, private fields with `#`, `static` for class-level helpers, `extends` + `super` for inheritance. Favor composition + small classes; avoid deep hierarchies. For pure-data shapes use plain objects + TypeScript types, not classes.
Default to async/await for readability. Use Promise.all for parallel independent work, Promise.allSettled when you want every result regardless of failure, Promise.race / any for first-to-finish. Always wrap awaits in try/catch (or .catch on the call site). Pass an AbortSignal for cancellation. Never await sequentially when work is independent.
Choose by semantics: `Promise.all` rejects on first failure — good for atomic ops. `Promise.allSettled` collects per-item outcomes — good for independent work. Retry transient failures with exponential backoff. Use AbortController to cancel siblings if one critical request fails. Surface partial-success states explicitly in UI.
Fire all with `Promise.all` if independent and you need all results; `Promise.allSettled` if you want every result regardless of failure; `p-limit` or a small async-pool for many requests (avoid hammering the server); chain awaits only when one depends on another's output. Add AbortController for cancel-on-unmount. Disable the button while in flight.
Browser console (script): `this` at top level is `window` (or `globalThis`). Node CommonJS module: top-level `this` is `module.exports` (which is `{}` initially), NOT `global`. Node ESM: top-level `this` is `undefined`. Inside functions: same rules everywhere — depends on call site, strict mode, arrow vs regular.
Delegation: one listener on a common ancestor handles events for many children. Works because events bubble up. Pros: fewer listeners (memory, setup cost), works for dynamically added children. Pattern: `element.addEventListener('click', e => { const item = e.target.closest('[data-id]'); if (!item) return; … })`.
JS is single-threaded; async work uses the event loop. Mechanisms: Web APIs / Node libuv (timers, I/O, network) → task queues (macrotasks for setTimeout, I/O; microtasks for Promises, queueMicrotask). Event loop picks one task → drains microtasks → renders → repeat. Workers give true parallelism. AbortController for cancellation.
Next.js code-splits per route by default — each page becomes its own chunk plus a shared vendor chunk. Dynamic imports (`next/dynamic`) split inside pages. SSR happens per request: server renders the React tree, sends HTML + a `__NEXT_DATA__` payload, browser hydrates. App Router adds RSC (server-only components, ship less JS) and streaming SSR via Suspense.
Next.js loads `.env` files at build time per environment: `.env` (all), `.env.local` (all, gitignored), `.env.development` / `.env.production` (per NODE_ENV). `NEXT_PUBLIC_*` are inlined into the client bundle; everything else is server-only. `.env.local` overrides — for secrets and local config. Build-time inlining means changing public vars requires a rebuild.
`useActionState` (React 19) wraps an async server/client action. It returns `[state, dispatch, isPending]` — `state` is the action's last return value, `dispatch` invokes the action (queues across calls), `isPending` reflects in-flight status. Designed for forms: `<form action={dispatch}>` works with progressive enhancement. Replaces a lot of useState + useTransition boilerplate.
async/await is syntax sugar over promises and generators. `await` pauses the function, yields to the microtask queue, and resumes when the promise settles. Same semantics, dramatically better readability and error handling.
`Promise.all` rejects as soon as any input rejects — short-circuits. `Promise.allSettled` always resolves once every input has settled, with an array of `{status, value|reason}` objects. Use `allSettled` for independent best-effort work (dashboard cards, batch operations, analytics) where partial success is meaningful.
Use modern DOM APIs (`querySelector`, `closest`, `dataset`, `classList`), prefer event delegation, batch DOM writes after reads to avoid layout thrash, scope styles via CSS classes/custom properties not inline, clean up listeners with AbortController on teardown. Keep DOM operations declarative-ish: template via `<template>` or DOMParser, swap entire subtrees instead of micromanaging.
Don't store auth tokens in localStorage — any JS on the origin (XSS, malicious extension) can read them. Use **httpOnly + Secure + SameSite=Lax/Strict cookies** so the browser sends them automatically but JS can't read. Add CSRF protection (double-submit or SameSite). Keep tokens short-lived; refresh server-side; rotate on suspicious activity.
Return a function that resets a timer on every call and only invokes fn after `wait` ms of silence. Forward `this` and arguments, expose cancel/flush, and optionally support a leading-edge call.
Iterate the array applying the callback with an accumulator. If initial value supplied, start there; else use the first defined element as initial and start from index 1. Skip holes (sparse). Throw TypeError on empty array with no initial. Pass (acc, cur, i, arr) signature.
Return a new function that calls the original with a fixed `this` and partially applied args. Handle the `new`-as-constructor case so the bound `this` is ignored when called with `new`.
Map each input to a promise that resolves with {status:'fulfilled', value} on success or {status:'rejected', reason} on failure. Pass the wrapped array to Promise.all so the outer promise never rejects.
Map<event, Set<handler>>. on/off/emit, with `on` returning an unsubscribe function. Handle errors per-handler so one throw doesn't break the rest. Bonus: once, namespacing, wildcard.
Maintain an active count and a waiting queue. Each enqueue returns a promise; when active < K, run; otherwise wait. On finish, pull the next waiter. Bonus: cancellation, retries, prioritization.
Maintain a counter of running tasks and a FIFO queue of pending ones. `schedule(task)` returns a Promise. If running < max, fire it; else push to queue. On task completion, drain one from the queue. Variants: per-key concurrency, retry, priority, AbortSignal cancellation.
Return a function that either accepts another argument and returns itself, or returns the running total when called with no argument. Implementation hinges on closure + a base case.
Cache by a stable key derived from arguments. Return the cached Promise (so concurrent calls with the same args share one in-flight request — request deduplication). On rejection, evict so retries are possible. For object args, use deep-equality keying (JSON.stringify with sorted keys, or a structured-clone hash). TTL for staleness; capacity bound for memory.
State machine: pending → fulfilled or rejected (transitions once). then/catch/finally chain by returning a new Promise. Resolve thenables. Schedule callbacks as microtasks (queueMicrotask) to match spec timing.
`<script>` (default): parser-blocking. `async`: fetched in parallel, executed ASAP, may interleave with parsing. `defer`: fetched in parallel, executed after parse in order. `type="module"`: defer by default. `modulepreload`: warm the cache for module dependencies. Rule of thumb: `defer` app code, `async` independent (analytics), preload critical, modulepreload module graphs.
Object: keys are strings/symbols, key order is mostly insertion (with integer-key quirks), prototype chain pollution. Map: any key type (objects, NaN), guaranteed insertion order, .size in O(1), better for frequent add/remove. Use Map for dictionaries; Object for shaped records.
Primitives compared by value: `1 === 1` is true. Objects compared by reference: `{a:1} === {a:1}` is false (different references). `===` (strict equality) skips coercion; `==` coerces (`1 == "1"` is true). For deep object equality, use `Object.is` for special cases or a deepEqual utility. `Object.is(NaN, NaN)` is true; `NaN === NaN` is false.
Classic 'what's the output' problems hinge on: microtask queue order (Promises before setTimeout), synchronous code first, async function continuations are microtasks, await suspends even when value is already resolved, Promise.resolve().then(...) doesn't immediately invoke. Trace stack → micro → macro and you'll predict any ordering.
`call(thisArg, ...args)` invokes immediately with a given `this`. `apply(thisArg, argsArray)` is the same but with args as an array. `bind(thisArg, ...partials)` returns a new function with `this` permanently set. The polyfill closes over `thisArg` + partials and uses `apply` (or `call`) internally — plus a `new.target` check so the bound function still works as a constructor.
all = fail-fast aggregation. allSettled = collect every outcome. race = first to settle (resolve or reject). any = first to *resolve*, ignores rejections until all fail.
A Promise is a placeholder for a future value — pending → fulfilled or rejected, settled once. For multiple async ops: `Promise.all` (fail-fast parallel), `Promise.allSettled` (parallel, never rejects, returns per-result status), `Promise.race` (first to settle), `Promise.any` (first to fulfill, ignores rejections). Use `for await…of` or a worker pool when you need bounded concurrency.
Semver strictly: breaking → major, additive → minor, fixes → patch. Use **Changesets** so every contributor's PR articulates the impact; CI consumes changesets to bump versions + write changelog. Pre-release channels (`next`, `canary`) for risky changes. Two-week stabilization. Maintain prior major branches with security patches. Codemods for breaking migrations.
Debounce input (250–300ms), cancel stale requests with AbortController, race-condition guard via request-id or sequence number, cache results by query, render a listbox combobox with ARIA, keyboard nav (↑↓ Enter Esc), and highlight match. Use React Query for caching + request dedup. Test for double-fire on backspace and quick paste.
Spread expands an iterable/object into elements; rest collects the remainder into an array/object. Destructuring binds positions/keys to variables with optional defaults and renames. All shallow — nested values still share references.
Three strategies: (1) write the library in TypeScript and ship `.d.ts` from build — best for new code; (2) hand-author `.d.ts` alongside JS — pragmatic for existing JS libs; (3) DefinitelyTyped (`@types/foo`) — community-maintained, used when you don't control the library. Use `tsd` / `expect-type` for type tests; treat types as part of the API surface.
Modern engines use **TimSort** (V8 since ~2018) — stable, O(n log n). Default comparator is **string** (lexicographic): `[1, null, 5, 2, undefined].sort()` → `[1, 2, 5, null, undefined]` because `undefined` is always moved to the end and `null` stringifies to `"null"`. Always pass a comparator for numbers.
A closure is a function bundled with the variables in scope at the time it was created — it remembers and can mutate those variables long after the outer function has returned.
Objects representing the eventual outcome of an async operation. Three states: pending → fulfilled or rejected, settled once and then immutable. Read via `.then` / `.catch` / `.finally` or await. Static aggregators: `Promise.all`, `allSettled`, `race`, `any`. Replaced callbacks with composable async.
The browser will discard or crash the tab. Chrome shows the 'Aw, Snap!' page or silently kills background tabs to reclaim memory (tab discarding). Tabs in different processes don't take each other down (Site Isolation). Common causes: leaks (event listeners, detached DOM, big closures), unbounded caches, large textures/canvases, huge in-memory data.
A Promise is an object representing the eventual result of an async operation. States: pending → fulfilled or rejected (once, then immutable). Create with `new Promise((resolve, reject) => …)`. Use for: fetch, timers, file I/O, any async API — and chain with `.then` / `.catch` or await. Promises moved JS off callback hell.
Closure = function + the variables of its enclosing scope, kept alive because the function references them. Real uses: private state in factories/modules, memoization caches, partial application/currying, React hooks capturing renders' props, debounce/throttle holding their timer, event handlers needing per-instance config.
Currying transforms an n-arg function into a chain of unary calls: `f(a, b, c)` becomes `f(a)(b)(c)`. Implemented with closures. Useful for composition, point-free style, and reusable handlers. Distinct from partial application (which pre-fills some args once).
`new F(args)` does four things: (1) create an empty object whose prototype is `F.prototype`, (2) call `F` with `this` bound to the new object, (3) if `F` returns an object, use that; otherwise use the new object, (4) return the result. Powers class instantiation and constructor functions. Arrow functions can't be `new`'d.
Top-level `this` in a script is the global object (`window` in browsers, `globalThis` in Node sloppy). In ES modules and strict mode it's `undefined`. Inside functions, it depends on how they're called; arrows inherit lexically.
Debounce: delay invoking until N ms have passed since the last call. Implementation captures a timer in a closure; each call clears the prior timer and schedules a new one. Variants: leading edge (call immediately, then ignore), trailing (default), `flush` / `cancel` methods, AbortSignal support.
`bind` returns a new function with `this` permanently bound and optionally some pre-applied arguments. Implementation: `Function.prototype.myBind = function(thisArg, ...preset) { const fn = this; return function (...rest) { return fn.apply(thisArg, [...preset, ...rest]); }; }`. Subtlety: bound function called with `new` should ignore thisArg and construct.
`bind` returns a new function with `this` pre-set and optional args pre-applied. Polyfill: closure capturing thisArg + preset args, returning a function that calls the original via `apply`. Handle `new`: when called as constructor, ignore thisArg and use the freshly constructed `this`. Set `bound.prototype = Object.create(fn.prototype)` so instanceof checks work.
Return a new Promise. Track a results array and a fulfilled count. For each input promise, on fulfill: write the value at its index, increment count, resolve outer when count === length. On reject: reject the outer Promise immediately (short-circuit). Handle the empty-array case (resolve with []) and non-Promise inputs (wrap with Promise.resolve).
A Promise wraps an async operation: `new Promise((resolve, reject) => …)`. `async/await` is syntactic sugar — `async` makes a function return a Promise; `await` pauses until the awaited Promise settles. Same semantics, cleaner control flow.
Dedupe with a Set, then sort. One-liner: [...new Set(arr)].sort((a,b)=>a-b). The catch interviewers test: default .sort() is lexicographic, so you MUST pass a numeric comparator for numbers.
Write a Promise-returning function that resolves with a value. Most common form: `function task() { return new Promise(res => setTimeout(() => res(value), ms)); }`. Or short: `return Promise.resolve(value)`. Real-world: wrap a non-Promise API (geolocation, FileReader, setTimeout) so it composes with async/await.
Reduce into a Map (or plain object): for each element, increment its count. `new Map` if keys may be objects or non-strings; object literal if all keys are strings/numbers (faster lookup, JSON-friendly). `Object.create(null)` to avoid prototype-chain key collisions like `__proto__`.