Free Handbook · Runs in your browser

Interview Questions

Sixty JavaScript interview questions in three tiers — junior, mid-level and senior — each with a model answer and, where it matters, what the interviewer is actually testing; then the coding round walked through step by step and a take-home checklist.

0 / 111 lessons🔥 0 day streak
ShareXLinkedIn

Module 15 · what you'll be able to do

  • Answer the 25 questions every junior JavaScript interview draws from
  • Handle the 25 mid-level questions on the event loop, this, prototypes, async and the browser
  • Reason through the 10 senior questions on architecture, performance and judgement
  • Run a coding round the way strong candidates do: clarify, brute force, improve, test
  • Submit a take-home that reviewers rate highly
01

Junior — 25 questions

These are asked in nearly every first-round JavaScript interview and in most screening calls. Answer each out loud before opening it; an answer you can only recognise is not one you can give under pressure.

JuniorWhat is the difference between let, const and var?

let and const are block-scoped and cannot be used before their declaration; const also cannot be reassigned (its object contents can still change). var is function-scoped, hoisted as undefined, and can be redeclared. Modern code: const by default, let when it changes, never var.

JuniorWhat is the difference between == and ===?

=== compares type and value with no conversion. == converts first, which gives surprises like 0 == "" and [] == false being true. Always use ===; the one idiomatic == is x == null to catch both null and undefined.

JuniorWhat are the primitive types?

string, number, boolean, undefined, null, bigint and symbol. Everything else — arrays, functions, dates, Maps — is an object. Primitives are immutable and compared by value; objects are compared by reference.

JuniorWhat does typeof null return, and why?

"object" — a bug from the first implementation in 1995 that cannot be fixed because existing code depends on it. Check for null with x === null, and for "null or undefined" with x == null.

JuniorWhat is the difference between null and undefined?

undefined means "no value has been assigned" — what you get from a missing property, an unassigned variable or a function without return. null is a value you assign deliberately to mean "empty". JSON.stringify keeps null and drops undefined.

JuniorWhat is a truthy or falsy value?

In a boolean context, exactly eight values are falsy: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including "0", "false", [] and {}.

JuniorWhat does "5" + 3 give, and "5" - 3?

"53" and 2. + concatenates if either side is a string; every other arithmetic operator converts to numbers. Convert explicitly with Number() to avoid relying on it.

JuniorWhat is an arrow function and how does it differ from a regular function?

A shorter syntax (x => x * 2) with two real differences: it has no own this (it uses the surrounding scope's), and no arguments object; it also cannot be used with new. Use arrows for callbacks; use methods or declarations where this matters.

JuniorWhat is hoisting?

Declarations are processed before code runs. Function declarations are fully hoisted (callable above their line); var is hoisted as undefined; let/const are hoisted but in the temporal dead zone until their line, so using them early throws a ReferenceError.

JuniorWhat is a closure?

A function that remembers the variables of the scope where it was created, even after that scope has returned. A counter factory returning () => ++count is the classic example. Closures capture variables, not values — the var-in-a-loop bug shows the difference.

JuniorWhat is the difference between map, filter, reduce and forEach?

map returns a new array of transformed items; filter a new array of the items that pass; reduce collapses to one value; forEach returns undefined and exists for side effects. None of them mutate the original.

JuniorHow do you copy an array or object?

Shallow: [...arr], arr.slice(), { ...obj }, Object.assign({}, obj) — nested objects are still shared. Deep: structuredClone(obj) (not functions). b = a is not a copy; it is a second name for the same object.

JuniorWhat does [10, 9, 1].sort() return?

[1, 10, 9]: the default sort converts to strings and compares character by character. Pass a comparator for numbers: sort((a, b) => a - b).

JuniorWhat is JSON and how do you use it in JavaScript?

A text format for data: objects, arrays, strings, numbers, booleans and null, with double-quoted keys. JSON.stringify(value) produces it; JSON.parse(text) reads it and throws a SyntaxError on bad input, so wrap it in try/catch when the text is untrusted.

JuniorWhat is the DOM?

The browser's tree of objects representing the page. JavaScript reads and changes it through document: querySelector to find elements, properties like textContent to change them, addEventListener to react to the user. Frameworks are abstractions over exactly these calls.

JuniorWhat is event bubbling?

After an event fires on an element it travels up through its ancestors, triggering their listeners too. That is what makes delegation work — one listener on a list handles clicks on any item, including ones added later. stopPropagation() halts it.

JuniorWhat is a Promise?

An object representing a value that will be available later: pending, then fulfilled with a value or rejected with an error, exactly once. You attach handlers with .then/.catch/.finally, or use await. Chaining .then keeps async steps flat instead of nested callbacks.

JuniorWhat does async/await do?

An async function always returns a promise; inside it, await pauses that function until a promise settles and gives you its value — or throws its rejection, so ordinary try/catch works. It is syntax over promises, not a different mechanism.

JuniorWhat is the difference between setTimeout(fn, 0) and calling fn()?

fn() runs now, synchronously. setTimeout(fn, 0) queues it as a macrotask: it runs after the current code finishes and after all pending microtasks (promise callbacks). "0 ms" means "as soon as the event loop gets to it", not immediately.

JuniorWhat is NaN and how do you check for it?

"Not a Number" — the result of an invalid numeric operation (Number("abc"), 0 / 0). It is the only value not equal to itself, so x === NaN is always false; use Number.isNaN(x). It is contagious: any arithmetic with NaN gives NaN.

JuniorWhy is 0.1 + 0.2 !== 0.3?

Numbers are IEEE-754 binary floats and 0.1, 0.2 and 0.3 cannot be represented exactly, so the sum is 0.30000000000000004. Compare with a tolerance (Math.abs(a - b) < Number.EPSILON), and never do money in floats — use integer cents.

JuniorWhat is template literal syntax?

Strings in backticks that can span lines and embed expressions with ${…}: `Hello, ${user.name}!`. It replaces string concatenation with + and is the normal way to build strings.

JuniorWhat is destructuring?

Pulling values out of arrays or objects into variables in one statement: const { name, age = 0 } = user, const [first, ...rest] = items. It works in function parameters too and supports defaults, renaming and nesting.

JuniorWhat is the spread operator?

... expands an iterable or object in place: copying ([...arr], { ...obj }), merging ({ ...defaults, ...options }), passing an array as arguments (Math.max(...nums)). As a parameter it is "rest": gather the remaining arguments into an array.

JuniorWhat is the difference between a module and a script?

A module (<script type="module">, or .mjs / "type": "module" in Node) has its own scope, is strict mode, can import and export, and runs once no matter how many files import it. A classic script shares the global scope. New code is modules.

02

Mid-level — 25 questions

For roles with two to five years of experience. The interviewer is checking that you understand what the engine does underneath — the event loop, this, prototypes — and can make sensible trade-offs in a real codebase.

Mid-levelExplain the event loop.

JavaScript runs on one thread with a call stack. Slow operations are handed to the host (browser, libuv); when they finish, their callbacks go into queues. When the stack is empty, the event loop drains the microtask queue (promise callbacks) completely, then runs one macrotask (timer, I/O), then microtasks again. This is why 1; setTimeout(2); Promise.resolve().then(3); 4 prints 1, 4, 3, 2 — and why a long loop freezes the page.

What they are really testing: The single most asked mid-level question. A follow-up will nest a promise inside a timeout.

Mid-levelWhat are the four rules for this?

(1) obj.method() → the object. (2) A plain call → undefined in strict mode, the global object otherwise. (3) new Fn() → the new object. (4) Arrow functions have no own this — they use the enclosing scope's. call/apply/bind set it explicitly. The value depends on how the function is called, not where it is written.

Mid-levelWhat is the prototype chain?

Every object has a link to a prototype object. Property lookup checks the object, then its prototype, then the prototype's prototype, until null. Methods on Array.prototype are found this way by every array. class syntax sets this chain up: methods go on Class.prototype, and extends links prototypes.

Mid-levelHow does class differ from constructor functions? Is it just sugar?

Mostly sugar over constructor + prototype, with real differences: classes must be called with new, their bodies are strict mode, methods are non-enumerable, class declarations are not hoisted (TDZ), and they support #private fields, static blocks and super properly. The object model underneath is unchanged.

Mid-levelWhat is the difference between microtasks and macrotasks?

Microtasks: promise reactions, queueMicrotask, MutationObserver — drained completely after every task, before rendering. Macrotasks: timers, I/O, UI events — one per loop turn. A microtask that queues another microtask runs before any timer, which can starve rendering if abused.

Mid-levelWhat is the difference between Promise.all, allSettled, race and any?

all: all fulfil → array; rejects fast on the first rejection. allSettled: waits for all, never rejects, gives status per item. race: first to settle wins, either way — used for timeouts. any: first to fulfil wins; rejects only if all reject, with an AggregateError.

Mid-levelHow would you run async tasks in sequence vs in parallel?

Parallel: start them all, then await Promise.all(promises). Sequence: for (const x of items) await work(x). A common bug is items.forEach(async …), which starts everything and awaits nothing. Rate-limited parallelism needs a small pool (p-limit or a hand-rolled queue).

Mid-levelWhat is an unhandled promise rejection and how do you avoid it?

A promise that rejects with no handler attached. Browsers log it; Node terminates the process. Avoid it by giving every promise an owner: await inside try/catch, a trailing .catch, and an explicit .catch(log) on fire-and-forget calls. Register process.on("unhandledRejection") as a last resort to log and exit cleanly.

Mid-levelExplain generators and give a use case.

function* returns an iterator; each yield hands out a value and pauses until next() is called. Uses: lazy sequences (infinite or huge, consumed on demand), custom iterables via *[Symbol.iterator], and pipelines like take(map(source)) that do no work until consumed. Async generators (for await) model streams.

Mid-levelWhat is the difference between shallow and deep equality, and how do you compare objects?

=== on objects compares identity — two equal-looking objects are not equal. Deep equality means recursively comparing contents. Options: JSON.stringify for plain data (order-sensitive, drops undefined), a library (lodash isEqual), or a test framework's toEqual. React relies on identity for change detection, which is why immutable updates matter.

Mid-levelWhy immutable updates? Show one.

Frameworks detect change by reference (prev !== next, O(1)) rather than comparing contents. Mutating keeps the reference, so nothing re-renders; and shared mutable state is a bug source. setTasks(prev => prev.map(t => t.id === id ? { ...t, done: true } : t)) creates a new array and a new object for the changed item only.

Mid-levelWhat is debouncing vs throttling?

Both limit how often a function runs. Debounce waits until calls stop for N ms, then runs once — search-as-you-type. Throttle runs at most once per N ms while calls continue — scroll handlers. Both are closures holding a timer or a timestamp.

Mid-levelWhat is memoisation, and when is it wrong?

Caching a function's results by its arguments so repeated calls are O(1). Right for pure, expensive functions with repeated inputs (Fibonacci, layout computations, selectors). Wrong when the function has side effects, when inputs rarely repeat (the cache just eats memory), or when arguments are objects you cannot key reliably.

Mid-levelWhat is the difference between Map and a plain object?

Map: any key type, guaranteed insertion order, size, directly iterable, no inherited keys, better for frequent add/delete. Object: string/symbol keys, JSON-serialisable, dot syntax. Use objects for records with known fields; Maps for dictionaries keyed by data.

Mid-levelHow does Array.prototype.sort work, and is it stable?

V8 uses Timsort (merge sort + insertion sort on runs), O(n log n), and the spec has required stability since ES2019, so equal items keep their order — you can sort by one key then another. The default comparator is string-based; always pass one for numbers. sort mutates; toSorted does not.

Mid-levelWhat is a WeakMap and why would you use it?

A Map whose keys must be objects and are held weakly: if nothing else references the key, the entry can be garbage-collected. Use it to attach metadata or private state to objects you do not own (DOM nodes, third-party instances) without leaking memory. It is not iterable, by design.

Mid-levelWhat is CORS?

The browser's same-origin policy blocks a page from reading responses from another origin unless that server sends Access-Control-Allow-Origin (and, for non-simple requests, answers a preflight OPTIONS). It protects users, not servers; the fix is on the server (or a proxy through your own backend). Nothing client-side bypasses it.

Mid-levelWhat is the difference between localStorage, sessionStorage and cookies?

localStorage: ~5 MB of strings per origin, persists, synchronous, never sent to the server. sessionStorage: same but per tab, cleared on close. Cookies: small, sent with every request to the domain, can be HttpOnly (invisible to JS — right for session tokens) and Secure. Never put secrets in local/sessionStorage.

Mid-levelWhat is tree-shaking and why do ES modules enable it?

Removing unused exports from the bundle. ESM imports are static — analysable without running the code — so a bundler can see that import { a } from "lib" never uses b and drop it. CommonJS require is a dynamic call, so nothing can be dropped safely.

Mid-levelWhat are the differences between CommonJS and ES modules?

CJS: require/module.exports, synchronous, dynamic, Node's original system. ESM: import/export, static, asynchronous loading, top-level await, works in browsers, tree-shakeable. ESM can import CJS; CJS can only reach ESM with dynamic import() (or require in Node 22+ for sync-compatible modules).

Mid-levelHow do you handle errors in Express?

Throw (or next(err)) from handlers; in Express 5 rejected promises from async handlers are forwarded automatically. Register an error middleware with four arguments last: it maps known errors (a custom class with a status) to responses, logs unknown ones, and never leaks stack traces to clients.

Mid-levelExplain Symbol.iterator.

A well-known symbol naming the method that makes an object iterable. If obj[Symbol.iterator]() returns an iterator (an object with next() returning { value, done }), then for…of, spread, destructuring and Array.from all work on it. Writing it as a generator method is the easy way.

Mid-levelWhat happens when you type a URL and press Enter?

DNS, TCP and TLS handshakes, an HTTP request; the server returns HTML. The browser parses it into the DOM, fetches CSS (render-blocking) and scripts (blocking unless defer/module), builds the render tree, lays out, paints. DOMContentLoaded fires when parsing is done, load when subresources are. Performance work is about shortening that critical path.

Mid-levelWhat is a race condition in front-end code and how do you prevent one?

Two async operations finishing in the wrong order — a search box where the response for "ab" arrives after "abc" and overwrites it. Prevent it by ignoring stale responses (a request id or a cancelled flag in the closure), aborting the previous request with AbortController, or serialising with a queue.

Mid-levelWhat does "use strict" change?

Undeclared assignments throw instead of creating globals; this in plain calls is undefined instead of the global object; writes to read-only properties throw; duplicate parameter names and octal literals are errors; eval and arguments are restricted. It is automatic in modules and classes, which is why it rarely needs writing today.

03

Senior — 10 questions

Senior questions have no single right answer. They test judgement: how you diagnose, what you would ask first, what you would refuse to do, and how you bring other engineers along. The answers here show the shape of a strong response, not a script.

SeniorA Node API handles 50 requests per second fine, then falls over at 200. Where do you look?

First, is it CPU or I/O? Event-loop lag (a long task starving the loop) versus a saturated resource (database pool, upstream API, file descriptors). I would measure event-loop delay and pool wait times before touching code. Typical causes: a synchronous JSON.parse of large bodies, an await inside a loop that should be parallel, an unbounded Promise.all that opens 10,000 connections, a pool of size 10 with 200 concurrent queries, or missing indexes making each query slow. The fix is usually one of: move CPU work off the loop, bound concurrency, add a cache, or fix the query.

What they are really testing: Diagnosis before prescription, and whether you know Node's specific failure modes.

SeniorHow would you structure a large front-end codebase so it stays maintainable?

By feature, not by type: features/tasks/ containing its components, hooks, API calls and tests, rather than global components/ and hooks/ folders. Strict TypeScript at the boundaries (API responses, props). Colocated tests. A thin shared layer for design system and utilities with an explicit public API per module. Enforce import boundaries with lint rules so features cannot reach into each other. And keep state local by default — global state is the thing that turns a codebase into a hairball.

SeniorWhen would you choose server-side rendering over a client-rendered SPA, or vice versa?

SSR (or static generation) when first paint, SEO and low-end devices matter — content sites, e-commerce, anything a search engine must index. Client rendering when the app is behind a login and highly interactive — dashboards, editors — where the bundle is loaded once and the server's job is data. Modern frameworks blend them: render the shell and content on the server, hydrate the interactive parts. The question to ask is "who lands here, on what device, and what do they need in the first second?".

SeniorA junior engineer's pull request wraps every function in try/catch and logs the error. What do you tell them?

That catching everywhere means handling nowhere: the logs will show the same error five times from five layers, and callers cannot tell success from failure because functions now return undefined on error. Errors should propagate to the boundary that can do something — the request handler, the UI error boundary, main() — and be logged once with context there. Catch low only when you can genuinely recover (retry, fallback) or need to add context and re-throw with cause.

SeniorHow do you approach performance in a React application that "feels slow"?

Measure first — React DevTools profiler and the browser Performance tab — because intuition is usually wrong. The common causes in order: re-rendering huge subtrees because state lives too high, expensive work in render (sorting a big list every keystroke), lists without virtualisation, bundle size delaying first load, and layout thrash from reading and writing DOM in a loop. Fixes match: move state down, memoise derived data, virtualise, code-split, batch DOM work. React.memo everywhere is not a strategy.

SeniorHow would you add TypeScript to a large JavaScript codebase without stopping feature work?

Incrementally, enforced. Turn on allowJs and checkJs so existing files type-check with inferred types; add // @ts-check or rename to .ts file by file, starting at the leaves (utilities, API clients) where types flow outward. Start with strict: false, then ratchet: forbid new any with a lint rule, track the count, tighten one flag at a time. Type the API boundary first — that is where the bugs are.

SeniorExplain how you would design an authentication flow for a SPA talking to an API.

Short-lived access tokens in memory, a refresh token in an HttpOnly, Secure, SameSite cookie, so JavaScript never touches the long-lived credential and XSS cannot steal it. A refresh endpoint that rotates tokens. CSRF is mitigated by SameSite plus a header the browser will not send cross-origin. On the server: validate, never trust the client's claims, rate-limit the login. And I would use a proven library or provider rather than hand-rolling the crypto.

SeniorWhat are memory leaks in JavaScript, and how do you find one?

Objects kept alive by references you forgot: event listeners never removed, timers never cleared, closures capturing large objects, caches without bounds, detached DOM nodes still referenced. Symptoms: heap growing across navigations or requests. Find it with heap snapshots (DevTools Memory tab, or --inspect for Node): take two snapshots across the suspected action and compare what was retained and by which path.

SeniorA colleague wants to add a fourth state-management library to the app. How do you respond?

Ask what problem the current tools fail to solve — usually the answer is that state is in the wrong place, not that the library is wrong. Then: what is the cost of a fourth mental model for every engineer, the migration path for the other three, and the bundle. If there is a genuine gap (server cache, form state), pick one tool that covers it, write a decision record, and plan to remove one of the others. Consistency beats the best tool for each job.

SeniorWhat would you look for reviewing a JavaScript pull request from a mid-level engineer?

In order: does it do what the ticket asked and is that visible in tests? Are errors handled at the right layer and are promises owned? Is state updated immutably where a framework needs it? Any ==, var, missing await, forEach-async, unbounded Promise.all? Does it introduce a dependency we do not need? Are names and file placement consistent with the codebase? I comment on behaviour and risk, let the formatter handle style, and ask questions rather than issue verdicts.

04

The coding round, walked through

A live coding round is 30–45 minutes on one or two problems, usually in a shared editor. The interviewer is not grading whether you finish; they are grading how you think, whether you test, and whether they would want to pair with you. The sequence below wins rounds even when the solution is not optimal.

  1. 1
    Clarify (2 min)

    Restate the problem. Ask about input size, empty input, duplicates, what to return when there is no answer. Write the contract as a comment.

  2. 2
    Example (1 min)

    Work one small case by hand and write it down. It becomes your first test.

  3. 3
    Brute force out loud (2 min)

    "The obvious approach is every pair, O(n²)." Say it, then say what is being repeated. Many interviewers are happy to see the brute force coded first.

  4. 4
    Pick the pattern (1 min)

    Map? Two pointers? Sort first? Stack? Name it, and why. Then code.

  5. 5
    Code (15 min)

    Talk while you type — what each line is for. Use real names. Handle the edge cases you listed. Do not optimise yet.

  6. 6
    Test (5 min)

    Walk through your hand example line by line, then an edge case. Find your own bug before they do — it scores higher than not having one.

  7. 7
    Complexity + improvements (2 min)

    State time and space. Say what you would change with more time. Stop talking.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
[ 'a', 'b' ]
[]
[]
[ 'y', 'z' ]
[ 'a', 'b' ]

What loses the round

  • Silence for five minutes, then a wall of code
  • Jumping to the clever solution and getting it subtly wrong
  • "It should work" without running through an example
  • Arguing with the hint
  • Not knowing what sort does with numbers or what forEach returns

What wins it

  • Thinking out loud, including "I'm not sure, let me check with an example"
  • A correct brute force, then an improvement you can explain
  • Testing your own code before they ask
  • Taking the hint and building on it
  • Fluent use of Map, Set, destructuring, arrow functions and array methods
05

The take-home checklist

A take-home is graded by someone opening a folder they have never seen. Everything below is about the first five minutes of that experience. Treat it as a pull request to a team you want to join.

  • README first: what it does, how to run it in two commands (npm install && npm start), how to run the tests, and a short "decisions and trade-offs" section. Reviewers read this before any code.
  • It runs from a clean clone. Node version pinned in engines and .nvmrc, lockfile committed, .env.example for any config. Test it in a fresh directory.
  • Tests exist and pass. Not 100% coverage — the core logic and the two ugliest edge cases. npm test green.
  • Structure: a small src/ with modules named for what they do, not utils.js with 400 lines. Types (TypeScript or JSDoc) at the boundaries.
  • Errors handled at the edges: bad input gives a clear message, not a stack trace; every promise is owned; nothing is swallowed.
  • Lint and format pass. A .prettierrc and an ESLint config show you work like a team member.
  • No secrets, no node_modules, no dist/, no commented-out code, no console.log debugging left behind.
  • Stop at the brief. Doing more than asked reads as not listening. Put ideas in the "next steps" section of the README instead.
What a reviewer writes about the ones that pass
"Ran first time. Tests cover the tricky part. Small functions, clear names, obvious where to add the next feature. README explains why they chose X over Y. Would merge." That is the whole target.

Finish the JavaScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.