The interviewer shares a screen with four lines of code: a console.log, a setTimeout with zero delay, a resolved promise and another console.log. "What does this print, and in what order?" You have used all four a hundred times, yet you hesitate, because you have never had to explain the order.
That is what JavaScript interviews test: not whether you have used closures or promises, but whether you can say what the engine is doing. Here are the 25 questions that come up most often in frontend and full-stack rounds, with the reasoning an interviewer is listening for.
The short answer
Most JavaScript interview questions come from six areas: scope (var, let, const, hoisting, closures), this and how functions are called, prototypes, type coercion, asynchronous code (the event loop, promises, async/await) and practical browser work (events, copying, performance, memory). For each, learn the rule, one example that proves it and the bug it causes. If you can predict what a five-line snippet prints and say why, you are ready for most of what gets asked.
Scope, hoisting and closures
1. What is the difference between var, let and const?
var is function-scoped: declared inside an if block, it is visible to the whole function. let and const are block-scoped, so they exist only inside the nearest {}. const must be initialised and cannot be reassigned, but it does not make the value immutable: const user = {} still lets you write user.name = 'Asha'. Most teams use const by default, let when they reassign, and no var.
2. What is the temporal dead zone?
let and const declarations are hoisted, but they are not initialised. The stretch from the start of the block to the declaration line is the temporal dead zone, and touching the variable there throws a ReferenceError. Even typeof x throws there. The point is to turn use-before-declaration from a silent undefined into a loud error.
3. What is hoisting?
Before a scope runs, the engine registers its declarations. Function declarations are hoisted completely, so you can call one above where it is written. var declarations are hoisted and initialised to undefined, which is why reading one early gives undefined rather than an error. let, const and class are hoisted into the TDZ.
Likely follow-up: "What happens if you call a function expression before its line?" With var fn = function () {}, fn is undefined at that point, so calling it throws TypeError: fn is not a function. With const, it throws a ReferenceError from the TDZ.
4. What is a closure?
A closure is a function together with the variables from the scope it was created in. The inner function keeps a reference to those variables, not a copy, even after the outer function has returned. That is how function counter() { let n = 0; return () => ++n; } keeps n alive and private between calls. Closures power private state, memoisation and every callback that reads a surrounding variable. Have a real example ready, such as a debounce helper or a React handler reading state.
5. What does this loop print, and how do you fix it?
The classic: for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } prints 3 3 3. var gives one i for the whole function, and by the time the callbacks run the loop has finished and i is 3. Every closure shares it.
Change var to let and it prints 0 1 2, because let in a for loop creates a fresh binding for each iteration, so each callback closes over its own i. Explain the binding, not just the fix.
this and functions
6. How is the value of this decided?
By how the function is called, not where it is written. Check the rules in priority order. Called with new: this is the newly created object. Called with call, apply or bind: this is whatever you passed. Called as a method, obj.fn(): this is obj. Called plainly, fn(): this is undefined in strict mode (and inside classes and ES modules, which are always strict), or the global object in sloppy mode.
Likely follow-up: "Why is this lost in setTimeout(obj.fn)?" The bare function is passed and later called plainly, so the method rule no longer applies.
7. How are arrow functions different from regular functions?
Arrow functions have no this of their own; they use the this of the scope they were defined in, and call or bind cannot change it. They also have no arguments object and cannot be used with new. That makes them right for callbacks inside methods and wrong for object methods that need this to be the object.
8. What is the difference between call, apply and bind?
All three set this explicitly. call invokes the function immediately with arguments listed one by one: fn.call(obj, a, b). apply invokes immediately with arguments as an array: fn.apply(obj, [a, b]). bind does not invoke; it returns a new function with this permanently fixed, ideal for passing a method as a callback.
Objects, prototypes and types
9. How does prototypal inheritance work?
Every object has an internal link to another object, its prototype. When you read a property the object does not have, the engine walks up that chain until it finds it or reaches null. Methods live once on the prototype instead of being copied into every instance. class syntax (ES2015) sets up exactly this chain; class Dog extends Animal still links Dog.prototype to Animal.prototype.
Likely follow-up: "__proto__ versus prototype?" prototype is a property on constructor functions: the object instances will link to. __proto__ is a legacy accessor for an object's actual prototype; prefer Object.getPrototypeOf.
10. What is the difference between == and ===?
=== compares without converting types: different types are never equal. == applies coercion rules first, which produces results like '' == 0 being true and [] == false being true. Use === everywhere. The one common deliberate use of == is value == null, which is true for exactly null and undefined and nothing else. Note that NaN === NaN is false; use Number.isNaN().
11. Which values are falsy?
There are eight: false, 0, -0, 0n, '', null, undefined and NaN. Everything else is truthy, including '0', 'false', [] and {}. So value || 10 replaces a valid 0 with 10; ?? (ES2020) falls back only on null or undefined.
12. What is the difference between null and undefined?
undefined means a value was never assigned: an uninitialised variable, a missing property, a function with no return, a parameter not passed. null is assigned on purpose to say "nothing here". Default parameters apply only for undefined, not null.
13. What does typeof return, and where does it mislead you?
It returns a string: "undefined", "boolean", "number", "bigint", "string", "symbol", "function" or "object". The traps: typeof null is "object" (an original bug kept for compatibility), typeof [] is "object" (use Array.isArray), and typeof NaN is "number".
Asynchronous JavaScript
14. Explain the event loop.
JavaScript runs your code on one thread with a call stack. Timers, network requests and I/O are handed to the browser or Node, which queues a callback when done. The event loop's job: when the call stack is empty, run all pending microtasks (promise callbacks, queueMicrotask, MutationObserver), then take one macrotask (a timer, an I/O callback, a UI event), then drain microtasks again, and in the browser give rendering a chance.
15. What does this print: log 1, setTimeout logging 2, a resolved promise logging 3, log 4?
1 4 3 2. The plain logs run synchronously, so 1 and 4 come first. The .then callback is a microtask and the timer a macrotask; microtasks drain before the next macrotask, so 3 beats 2 despite the zero delay.
Likely follow-up: "Where does code inside an async function before the first await run?" Synchronously, as part of the call. Only the code after each await is scheduled as a microtask.
16. What is a promise, and what problem does it solve?
A promise is an object representing a value that will be available later. It settles once, fulfilled or rejected, and never changes again. It replaced nested callbacks with chains: each .then returns a new promise, and one .catch at the end handles a rejection from any earlier step.
17. How do you handle errors with async/await?
Wrap the awaited calls in try/catch, or attach .catch() to the promise. The bugs interviewers probe: a missing await, so the rejection escapes the try; forEach with an async callback, which neither waits nor catches (use for...of or Promise.all); and unhandled rejections. Since Node.js 15, an unhandled rejection crashes the process by default.
Likely follow-up: "Is sequential awaiting a problem?" Two independent requests awaited one after the other take the sum of both times. Start both, then await Promise.all([a, b]).
18. What is the difference between Promise.all, allSettled, race and any?
Promise.all fulfils with an array of results when every promise fulfils, and rejects as soon as any one rejects; use it when you need all results. Promise.allSettled (ES2020) always waits for all of them and gives you each outcome as { status, value } or { status, reason }; use it when partial failure is acceptable. Promise.race settles with the first to settle, success or failure: the basis of a timeout. Promise.any (ES2021) fulfils with the first success and only rejects, with an AggregateError, if all of them fail.
The browser and practical code
19. What is the difference between debounce and throttle?
Debounce waits until calls stop for a set time, then runs once: right for a search box, where you want one request after the user stops typing. Throttle runs at most once per interval while calls keep coming: right for scroll or resize handlers that need regular updates without firing hundreds of times a second. Both use a closure holding a timer id or timestamp; expect to write debounce.
20. What is event bubbling, and what is event delegation?
An event travels down from the window to the target (capturing), then back up through every ancestor (bubbling), where listeners run by default. Event delegation uses this: put one listener on a parent and use event.target.closest('li') to find which child was clicked. One listener replaces a thousand, and it covers children added later.
Likely follow-up: "Which events do not bubble?" focus, blur, mouseenter and mouseleave. Use focusin and focusout when you need delegation for focus.
21. What is the difference between a shallow copy and a deep copy?
A shallow copy, made with spread or Object.assign, copies the top-level properties, but nested objects are still shared, so changing copy.address.city changes the original. For a deep copy, structuredClone (modern browsers, Node.js 17+) handles Dates, Maps, Sets and cycles, but not functions. The old JSON round-trip trick drops undefined, turns Dates into strings and throws on cycles.
22. Explain map, filter and reduce.
All three take a callback and return a new value without mutating the original array. map transforms each element and returns an array of the same length. filter returns the elements for which the callback returns truthy. reduce folds it into a single value of any shape, such as a sum or a lookup object. Always pass an initial value to reduce; without one, an empty array throws a TypeError.
23. What do destructuring and the spread operator do?
Destructuring pulls values out of arrays or objects into variables: const { name, age = 18 } = user with a default, const [first, ...rest] = list with a rest element. Spread does the reverse, expanding an iterable or object in place: [...a, ...b] to merge arrays, { ...defaults, ...options } to merge objects where later keys win. Spread copies are shallow, as in question 21.
24. What is the difference between ES modules and CommonJS?
CommonJS (require, module.exports) is Node's original system: imports are resolved at runtime, can sit inside an if, and you get the module.exports object as it is at that moment; values you destructure from it do not update later, whereas ES module imports are live bindings. ES modules (import, export) are the language standard: imports are static and resolved before code runs, which is what lets bundlers tree-shake unused code; exports are live bindings; modules are always in strict mode; and top-level await is allowed. Node.js 22.12 and 20.19 and later can require() an ES module as long as it does not use top-level await; before that, CommonJS could only load ESM with a dynamic import().
25. What causes memory leaks in JavaScript, and how do you find them?
The garbage collector frees anything that is no longer reachable, so a leak is something that stays reachable by accident. Usual causes: listeners never removed, intervals never cleared, detached DOM nodes still referenced, caches that only grow, and closures holding large objects. In React, it is often an effect with no cleanup. To find one, compare Chrome DevTools heap snapshots taken before and after repeating an action.
Likely follow-up: "How would you make an object immutable?" Object.freeze stops changes to its own properties but is shallow, so nested objects stay mutable unless you freeze them recursively.
How to practise these
Reading answers is easy; saying them aloud while someone asks "why?" is not. Explain each one to an empty room in under a minute, then write the snippet from memory and predict its output before running it.
If you get stuck, read how to handle a question you don't know; if you know all 25 but still freeze, read why knowing the answer isn't enough.
Frontend rounds move on to React interview questions; backend roles to Node.js interview questions. Interviewing for your first job? Start at the preparation hub for freshers.
To rehearse under real conditions, the frontend interview practice track on 99interview lets you pick JavaScript as your stack and take a scored round with Sia, our AI interviewer, who asks one question at a time and follows up on what you actually said. You choose the difficulty (Beginner, Standard or Senior) and the round length, and get a scored written breakdown when you finish.
For a human read, a live 40-minute Zoom interview with a working engineer scores you on technical depth, problem solving, communication, behaviour and code quality, with written feedback.