Skip to main content

JavaScript Interview Questions (2026)

100 real interview questions with in-depth answers — 30 basic, 40 intermediate, 20 advanced. Updated April 2026.

Preparing for a JavaScript Developer role?

JavaScript is a dynamic, prototype-based scripting language standardized as ECMAScript. It runs in browsers and on servers via Node.js, Deno, and Bun. It is single-threaded with an event loop for async I/O.

`var` is function-scoped and hoisted. `let` and `const` are block-scoped. `const` prevents reassignment but not mutation of the referenced object. Prefer `const` by default, `let` only when reassignment is needed.

string, number, bigint, boolean, undefined, null, symbol. Everything else is an object. Primitives are compared by value and are immutable.

`==` performs type coercion and has surprising rules (e.g., `"0" == false`). `===` compares without coercion. Always use `===` unless you have an explicit reason.

Declarations are moved to the top of their scope before code runs. `var` is initialized to `undefined`; `let`/`const` are in the temporal dead zone until their statement executes. Function declarations are fully hoisted.

Shorter function syntax that does not bind its own `this`, `arguments`, `super`, or `new.target`. Arrow functions inherit `this` from the enclosing scope. ```js const add = (a, b) => a + b; ```
`this` is the call-site context. In a method it is the object; in a regular function it is `undefined` (strict) or `globalThis`; in arrow functions it is lexical. `.bind`, `.call`, `.apply` explicitly set it.
A function plus its lexical environment — variables captured when the function was defined remain accessible even after the outer function returns. Closures enable module privacy, currying, and event handlers. ```js function counter() { let n = 0; return () => ++n; } ```
JS runs on a single thread with a call stack, microtask queue (promises), and macrotask queue (setTimeout, I/O). The loop picks tasks when the stack is empty, draining all microtasks before the next macrotask.
Objects representing the eventual result of an async operation, with states pending/fulfilled/rejected. Chain with `.then`/`.catch` or use `async/await` for readable code. ```js const r = await fetch(url); ```
`async` marks a function that returns a promise; `await` pauses execution until the promise settles. It makes async code read top-to-bottom while keeping the non-blocking behavior.
`undefined` means a variable has no assigned value. `null` is an explicit "no value". `typeof null === "object"` is a historical bug.
Backtick strings that support interpolation with `${}` and multiline content. They also enable tagged templates for custom processing. ```js const s = `Hello, ${name}`; ```
Syntax for extracting values from arrays or objects into variables. ```js const { name, age } = user; const [a, b, ...rest] = arr; ```
You can assign defaults in function signatures: `function greet(name = "world"){}`. Evaluated each call for the arguments that are `undefined`.
`...` expands iterables in calls or arrays and object properties in object literals. Its inverse in destructuring/function parameters is "rest". ```js const merged = { ...a, ...b }; ```
Shallow: `{...obj}` or `Object.assign({}, obj)`. Deep: `structuredClone(obj)` (built-in, handles cycles/dates/maps) or a library.
ES modules (`import`/`export`) provide static imports, tree-shakable bundling, and live bindings. CommonJS (`require`/`module.exports`) is Node's historical system. Modern Node supports both.
The Document Object Model is the tree-structured API browsers expose for HTML/XML documents. Use `document.querySelector`, `addEventListener`, and friends to manipulate it.
Attach a single listener to a common ancestor and inspect `event.target` to handle events from many children. It improves performance and lets you handle dynamically added elements.
`map` transforms each element, returning a new array. `filter` keeps elements matching a predicate. `reduce` collapses the array to a single value with an accumulator.
Functions that take or return other functions. Examples: `map`, `filter`, `setTimeout`, custom decorators. First-class functions are the enabling feature.
JavaScript Object Notation — a text format for structured data. Parse with `JSON.parse`, serialize with `JSON.stringify`. No support for functions, undefined, cyclic refs, or dates.
A function passed to another function and invoked later — upon completion, an event, or each iteration. Node's classic pattern uses `(err, result)` callbacks.
`var` is hoisted to the enclosing function; `let` is block-scoped and in a temporal dead zone until declared. `let` avoids many bugs where loop variables leak.
Falsy: `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN`. Everything else is truthy, including `"0"`, `"false"`, empty arrays/objects.
Opt-in (`"use strict"`) stricter parsing: disallows implicit globals, duplicate params, silent failures, `with`. ES modules are always strict.
Use `class` syntax with `constructor` and methods. Supports `extends`, `static`, and private fields (`#x`). ```js class Point { constructor(x,y){ this.x=x; this.y=y } } ```
`NaN` stands for Not a Number. It is the result of invalid math (`0/0`) and is not equal to itself. Test with `Number.isNaN(x)`.
Each object has an internal `[[Prototype]]` reference (accessed via `Object.getPrototypeOf`). When a property is not found, the engine walks up the chain until it is or reaches `null`. Classes are syntactic sugar over prototypes.
After each macrotask (setTimeout, I/O), the runtime drains the entire microtask queue (promises, queueMicrotask, MutationObserver). This means `Promise.resolve().then(...)` runs before `setTimeout(..., 0)`. Starvation can occur if microtasks schedule more microtasks.
Transforming `f(a,b,c)` into `f(a)(b)(c)`. Useful for partial application and composing reusable helpers. ```js const curry = f => a => b => f(a, b); ```
Functions defined with `function*` that use `yield` to produce values lazily. They implement the iterator protocol and support bidirectional communication via `.next(value)`.
Objects link to prototypes via `[[Prototype]]`. Lookups walk the chain. Classes use prototypes under the hood. Favor composition via mixins/factories for flexibility.
An object wrapping another with traps for operations like `get`, `set`, `has`, `deleteProperty`. Proxies power reactivity (Vue 3), observable state, and API mocks.
All invoke a function with an explicit `this`. `call(thisArg, a, b)` passes args individually; `apply(thisArg, [args])` takes an array; `bind(thisArg, ...partial)` returns a new function. Arrow functions ignore these.
Sorts in place; the default comparator coerces to strings, which surprises for numbers. Provide `(a,b) => a - b` for numeric sort. ES2019+ mandates stability.
`Set` stores unique values, `Map` stores keyed pairs with any-typed keys (not just strings). Both maintain insertion order and have O(1) average operations. Prefer them over objects for dynamic collections.
`const` prevents reassignment of the variable binding. `Object.freeze` prevents adding, removing, or modifying direct properties (shallow). For deep immutability, freeze recursively or use a library.
V8 uses a generational mark-and-sweep collector: young generation with a scavenger (Cheney), old generation with mark-compact. Objects surviving enough GCs are promoted. Leaks usually come from unexpected references (closures, globals, listeners).
`for...in` iterates enumerable string keys (including inherited); buggy for arrays. `for...of` iterates iterable values using the iterator protocol. Prefer `for...of` for arrays, maps, sets.
A function receives the literal parts and interpolated values, enabling DSLs (styled-components, graphql-tag, html). ```js function tag(strings, ...values) { ... } tag`Hello, ${name}`; ```
`WeakMap` and `WeakSet` hold weak references so the GC can collect keys with no other references. Useful for associating metadata with DOM nodes or for caches without leaks.
Debounce: only invoke after N ms have passed since the last call (search-as-you-type). Throttle: invoke at most once per N ms (scroll/resize handlers). Both prevent excessive work on rapid events.
Unique primitive identifiers. Common uses: well-known symbols to customize behavior (`Symbol.iterator`, `Symbol.asyncIterator`), creating non-enumerable private-ish keys.
Use an IIFE or module file to encapsulate private state and expose a public API. ES modules make this the default; inside a module, non-exported names are private.
ES modules are static, analyzable, async, and tree-shakable. CommonJS is dynamic (`require` runs at call time) and synchronous. Node supports both; be careful with interop — ESM can import CJS by default export.
`Promise.all` fails fast when any promise rejects. `Promise.allSettled` waits for all and returns an array of results. Use `allSettled` when partial failure is acceptable.
Wrap in try/catch. At the top level, catch with `.catch` on the returned promise or install `process.on("unhandledRejection")`. Avoid swallowing errors silently — always log or rethrow.
Reuse of the current stack frame for a function's tail call, avoiding stack growth. ES2015 spec required it but engines largely do not implement it. Avoid deep recursion in JS.
An iterator has a `next()` method returning `{value, done}`. Objects with `Symbol.iterator` are iterable and work in `for...of`, spread, and destructuring.
A built-in way to cancel fetches, timers, event listeners. Pass the `signal` to APIs that accept it. ```js const ctrl = new AbortController(); fetch(url, { signal: ctrl.signal }); ctrl.abort(); ```
Declared with `#` prefix. Accessible only inside the class — enforced by the runtime, not just naming convention. Combine with `#method()` for private methods.
JS has no built-in deep equality. `===` compares references for objects. Use `JSON.stringify` for simple cases or libraries (lodash isEqual, fast-deep-equal) for accurate, cycle-safe comparison.
Caching a function's return value by its input. In JS, a `Map` keyed by arguments. Works only for pure functions with stable serializable inputs.
Events first capture from the root down to the target, then bubble from the target up. `addEventListener(type, cb, { capture: true })` listens during capture. Most handlers use bubbling.
Remove event listeners, detach observers, clear timers, and release large objects when views unmount. Beware global caches. Use Chrome DevTools Memory panel snapshots to find retention paths.
`??` returns the right side only when the left is `null`/`undefined`. `||` returns the right for any falsy value (`0`, `""`, `false`), which can be a bug.
A function's scope is determined by where it is written, not where it is called from. Closures capture the surrounding lexical environment. It makes code easier to reason about than dynamic scoping.
Background threads that run JS off the main thread, communicating via `postMessage`. Good for CPU-intensive work without blocking the UI. SharedArrayBuffer + Atomics enable real shared memory.
Fetch is promise-based, streams responses, supports AbortController, and has a cleaner API. It does not reject on HTTP error status; check `response.ok` manually. XHR offers upload progress out of the box; fetch needs streams.
Stage 3 proposal using `@decorator` before class, method, or field. They allow annotating and wrapping behavior — observables, DI, validation. TypeScript supports them under a flag.
Track the latest request with a generation counter, AbortController, or a stamp; discard stale results. For mutation, use optimistic updates with reconciliation or a lock/queue.
`?.` short-circuits to `undefined` when the left side is nullish, avoiding TypeErrors. Works with property access, method calls (`a?.()`), and dynamic keys (`a?.[k]`).
Both convert iterables to arrays. `Array.from(iter, mapFn)` takes a mapper argument, avoiding a second iteration. Spread requires the value to be iterable; `Array.from` also works with array-likes that have `length`.
A well-known symbol used as a method name. Defining it on an object makes it iterable and enables `for...of`, spread, destructuring.
They schedule a task in the macrotask queue after at least N ms. Precision is limited by the event loop and tab throttling (browsers clamp to 1s when hidden). For animation, prefer `requestAnimationFrame`.
Shallow copies duplicate the top-level structure; nested objects are shared. Deep copies recursively duplicate. `structuredClone` handles most cases including cycles; spread is shallow.
Pure functions, immutability, higher-order functions, function composition, currying, avoiding side effects. Libraries like Ramda or Lodash/fp formalize these. Reduces bugs and simplifies testing.
Between a `let`/`const` declaration's hoisting and initialization, accessing the variable throws ReferenceError. It catches use-before-declare bugs that `var` silently allowed.
Store state and callbacks; `then` schedules callbacks via microtasks with `queueMicrotask`; `resolve`/`reject` flush queued callbacks. Support chaining by always returning a new promise and adopting thenables. ```js class P { constructor(fn){ this.cbs=[]; fn(v=>this.set("f",v), e=>this.set("r",e)); } set(s,v){ this.s=s; this.v=v; this.cbs.forEach(c=>c()); } then(onF,onR){ return new P((res,rej)=>{ const run=()=>{ try { res((this.s==="f"?onF:onR)(this.v)); } catch(e){ rej(e); } }; this.s ? queueMicrotask(run) : this.cbs.push(run); }); } } ```
Store a map from event name to listener array. `on(name, fn)` pushes; `emit(name, ...args)` iterates copies. Return `off` functions for easy cleanup. Support `once` via a one-shot wrapper that removes itself.
V8 assigns hidden classes (maps) to objects based on their property shape. Code that adds properties in consistent order keeps objects on the same hidden class, enabling inline caches and massive speedups. Adding/removing properties dynamically deoptimizes.
A proper tail call reuses the current stack frame. The ES spec mandated it, but V8 and others worried about debuggability (lost stack traces) and silent behavioral changes. Safari shipped it briefly then rolled back. Today, assume it is absent.
Wrap values in observables; when read inside a reaction, record the dependency. On write, notify dependents. Use a transaction to batch updates. For fine-grained reactivity, use proxies or signal primitives (getter/setter pairs).
Take Heap snapshots before and after an interaction in Chrome DevTools; compare retained sizes. Common culprits: listeners not cleaned up in `useEffect`, closures holding large props, global caches, detached DOM subtrees. Use `why-did-you-render` to spot extra closures.
An async function compiles into a state machine that awaits each promise by returning to the event loop and resuming on resolution. Each `await` is roughly equivalent to `.then` chaining. Errors propagate as rejections that surface via try/catch.
Store the latest args and timer; `debounced(...)` sets/resets the timer. Expose `cancel` to clear the timer, `flush` to invoke immediately with the latest args. Guard against the function being called after cancel.
Ideally, abstractions compile to the same code you'd write by hand. JS is harder than Rust or C++ because of dynamic dispatch, but engines use inline caches and speculation to approach it. Keep object shapes consistent and avoid megamorphic call sites.
ESM supports cycles. During evaluation, already-entered modules return their current (possibly partial) exports. Live bindings let exports update after evaluation completes. Design modules so that initializer code does not depend on uninitialized imports.
Loop up to N attempts; on failure, `await new Promise(r => setTimeout(r, base * 2**i + jitter))`. Retry only idempotent requests or on clearly transient errors. Cap total elapsed time.
Structural: shape determines compatibility (TS, Go interfaces). Nominal: declared name determines it (Java, C#). JS has no static types; TS is structurally typed by default but supports nominal via branded types.
Use a `Map` — iteration order is insertion order. On get, delete and re-set to move to the end; on put, insert and `delete(map.keys().next().value)` when over capacity. O(1) for all operations.
React builds a fiber tree in memory, diffs against the previous tree, and batches DOM updates. This trades some CPU for developer ergonomics and predictable state. Libraries like Solid or Svelte compile directly to DOM updates, achieving better raw throughput.
Observables model streams of values over time with operators for transforming and combining. Great for complex async coordination (autocomplete, websockets, drag). For simple promises, they are overkill.
Use Helmet for headers, rate limit at the edge, validate inputs with Zod, avoid `eval`/`child_process` with user data, keep deps updated, scan with `npm audit` and Snyk, use environment-specific secrets, run as a non-root user.
A replacement for JavaScript's flawed Date, providing immutable date/time primitives with explicit time zones and calendars. Stage 3 proposal; shipped behind flags in some engines. Much better for correctness.
Node's `--loader` flag lets you intercept module resolution and source transformation — used by ts-node, vitest, and others. The new `module.register` API is the recommended way post-20.
Thread an `AbortSignal` through your operation; each step checks `signal.aborted` and throws `AbortError`. Higher-level helpers wrap fetch, timers, and long loops. Signals compose via `AbortSignal.any([a, b])`.
A buffer that can be shared between workers. With `Atomics.wait/notify`, you can build real locks and lock-free data structures. Requires cross-origin isolation (COOP/COEP) due to Spectre.

Frequently Asked Questions

How do I prep for a JavaScript frontend interview?

Cover language fundamentals (closures, scope, async, prototypes), one major framework (React most often), DOM APIs, and basic CSS/accessibility. Practice building small interactive UIs without a framework to prove you understand the language.

Should I study TypeScript too?

Yes — most production JS jobs today use TypeScript. You do not need to be an expert in advanced types, but know generics, unions, narrowing, and utility types well.

What async topics come up most?

Event loop microtasks vs macrotasks, Promise.all/allSettled, async error handling, cancellation with AbortController, and common race-condition patterns.

Do I need to know Node.js for frontend jobs?

A little — most frontends ship with tooling (Vite, Next.js, ESBuild) that runs on Node. You should be comfortable enough to read and tweak build configs and run scripts.

How much DSA does a frontend role require?

Typically medium LeetCode on arrays, strings, and hashmaps, plus a "build this widget" round. Senior roles add system design for the frontend (data fetching, caching, state architecture).

Related Topics

Ready to apply?

TryApplyNow scores matches, tailors resumes, and tracks applications so you can focus on prep, not paperwork.

Try for free →