JavaScript Interview Questions and Answers
A candidate who can recite the definition of a closure and a candidate who can explain why a closure is quietly leaking memory in a specific piece of code are answering the same question, but only one of them is actually going to pass a senior-level interview. This guide covers JavaScript interview questions the way they actually get asked in 2026: less trivia about == versus ===, more about the event loop, modern language features, and whether you can reason about code behavior under real conditions.
Each answer here explains not just what the concept is, but why it matters, since that’s almost always the follow-up question a good interviewer asks next.
How JavaScript Interviews Are Structured Now
The format varies more by company type than it used to. Startups tend to focus on practical JavaScript: building a small feature, debugging a broken piece of code, or extending something that’s already partially written. Larger companies, especially at the senior level, are more likely to include algorithmic questions alongside language-specific ones. Junior-level trivia questions, like the difference between == and ===, are increasingly treated as assumed knowledge rather than something worth asking directly in a mid-to-senior interview, with the real questions instead probing how well you understand why the language behaves the way it does under the hood.
Two other shifts are worth knowing about going in. First, if a job posting mentions TypeScript, expect a JavaScript fundamentals round followed by TypeScript-specific questions on top of it, since most product companies have migrated at least part of their codebase. Second, interviewers now often ask directly about your use of AI coding tools, not as a trick question, but because most companies genuinely expect candidates to use them and want to understand how you decide when to rely on one and when not to.
JavaScript Fundamentals
1. What’s the difference between var, let, and const?
var is function-scoped and gets hoisted to the top of its scope with its value initialized as undefined. let and const are block-scoped and exist in a “temporal dead zone” from the start of the block until their declaration line, meaning accessing them before that line throws an error rather than returning undefined. const also prevents reassignment of the variable binding itself, though it doesn’t make an object or array assigned to it immutable, its properties can still change.
2. What’s the difference between == and ===?
=== (strict equality) compares both value and type without any conversion. == (loose equality) converts operands to a matching type before comparing, which produces results that surprise people, like 0 == '0' returning true while 0 === '0' returns false. Using === by default and reserving == for specific, deliberate cases is the standard recommendation almost everywhere.
3. Explain hoisting in JavaScript.
Hoisting is JavaScript’s behavior of moving variable and function declarations to the top of their scope during the compile phase, before code actually executes. Function declarations are hoisted completely, including their body, so they can be called before the line where they’re written. var declarations are hoisted but not their assigned value, leaving the variable as undefined until the assignment line runs. let and const are hoisted too, but remain inaccessible until their declaration executes.
4. What are the primitive data types in JavaScript?
string, number, boolean, null, undefined, symbol, and bigint. Everything else, objects, arrays, functions, is technically an object. Primitives are compared and copied by value; objects are compared and copied by reference, which explains why two objects with identical contents aren’t considered equal with ===.
5. What is the difference between null and undefined?
undefined means a variable has been declared but hasn’t been assigned a value yet, or a function didn’t explicitly return anything. null is an intentional assignment representing “no value,” set deliberately by a developer. typeof undefined returns "undefined", while typeof null returns "object", a long-standing quirk in the language that’s now considered a historical bug rather than intentional design.
Closures and Scope
6. What is a closure, and why does it matter in practice?
A closure is a function that retains access to variables from its enclosing scope even after that outer function has finished executing. This matters practically for things like creating private state without classes, building function factories, and implementing patterns like memoization, where an inner function needs to remember data across multiple calls.
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2Each call to counter() still has access to the count variable from makeCounter‘s scope, even though makeCounter already finished running.
7. Can closures cause memory leaks? How?
Yes. Because a closure keeps a reference to its enclosing scope alive, any large object referenced inside that scope won’t be garbage collected as long as the closure itself is still reachable, for example, still attached as an event listener that’s never removed. This becomes a real production issue in long-running applications like single-page apps, where accumulated unused closures gradually increase memory usage over the session.
8. What’s the difference between lexical scope and dynamic scope?
JavaScript uses lexical (or static) scope, meaning a function’s access to variables is determined by where it’s physically written in the code, not by where or how it’s called. This is why closures work the way they do: an inner function’s scope is fixed at the point it’s defined, regardless of the context it’s later invoked from.
Asynchronous JavaScript and the Event Loop
9. Explain the JavaScript event loop.
JavaScript runs on a single thread, but the event loop lets it handle asynchronous operations without blocking. The call stack executes synchronous code first. Once it’s empty, the event loop checks the microtask queue (promise callbacks, queueMicrotask) and drains it completely before moving to the macrotask queue (setTimeout, I/O events, UI rendering). This is why a resolved promise’s .then() callback runs before a setTimeout(fn, 0) callback, even though both were technically scheduled around the same time.
10. What’s the difference between a callback, a promise, and async/await?
A callback is a function passed into another function to run once an operation completes, which becomes hard to read once several async steps depend on each other (often called “callback hell”). A promise represents a value that will eventually resolve or reject, and can be chained with .then() and .catch() more readably than nested callbacks. async/await is syntax built on top of promises that lets asynchronous code read like synchronous code, without changing the underlying mechanics.
11. What’s the difference between Promise.all, Promise.allSettled, Promise.race, and Promise.any?
Promise.all resolves when every promise resolves, but rejects immediately if any single one rejects. Promise.allSettled waits for every promise to finish regardless of outcome, returning the status and value or reason for each one, which is useful when you need all results even if some failed. Promise.race resolves or rejects as soon as the first promise settles, whichever happens first. Promise.any resolves as soon as the first promise fulfills, and only rejects if every single one fails.
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/orders'),
fetch('/api/might-fail')
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log('Success:', r.value);
else console.log('Failed:', r.reason);
});12. What is an AbortController, and when would you use one?
AbortController lets you cancel an in-progress asynchronous operation, most commonly a fetch request, by calling .abort() on its associated signal. This matters in real applications for canceling a stale API request when a user navigates away or types a new search query before the previous one finishes, avoiding wasted network calls and race conditions where an old response arrives after a newer one.
Prototypes, Classes, and this
13. Explain prototypal inheritance.
Every JavaScript object has an internal link to another object called its prototype, and when a property or method isn’t found directly on an object, JavaScript looks up the prototype chain until it finds it or reaches null. Classes in JavaScript, introduced in ES6, are largely syntactic sugar over this same prototype-based system rather than a fundamentally different inheritance model like in classical object-oriented languages.
14. How does the value of this get determined in JavaScript?
this is determined by how a function is called, not where it’s defined. Called as a method (obj.method()), this refers to the object before the dot. Called standalone, this is undefined in strict mode (or the global object otherwise). Arrow functions don’t have their own this at all; they inherit it lexically from the enclosing scope at the point they’re defined, which is exactly why arrow functions are commonly used for callbacks inside class methods, to avoid this unexpectedly changing.
15. What’s the difference between call, apply, and bind?
All three let you explicitly set what this refers to inside a function. call invokes the function immediately with arguments passed individually. apply invokes it immediately with arguments passed as an array. bind doesn’t invoke the function at all; it returns a new function with this permanently fixed, useful for passing a method as a callback without losing its intended context.
Modern JavaScript Features (ES2020 and Later)
16. What do optional chaining (?.) and nullish coalescing (??) actually solve?
Optional chaining lets you safely access a deeply nested property without a long chain of manual null/undefined checks: user?.address?.city returns undefined instead of throwing if user or address is missing. Nullish coalescing provides a default value specifically when something is null or undefined, unlike ||, which also triggers on other falsy values like 0 or an empty string, which is often not what you actually want.
const city = user?.address?.city ?? 'Unknown';
const quantity = order.quantity ?? 1; // only defaults if null/undefined, not if quantity is 017. What does structuredClone() do, and why not just use JSON.parse(JSON.stringify(obj))?
structuredClone() creates a deep copy of an object natively, handling data types the JSON trick can’t, like Date objects, Map, Set, and circular references, without silently corrupting them or throwing an error. The older JSON.parse(JSON.stringify()) pattern was a common workaround before this existed, but it converts dates to strings, drops functions and undefined values, and fails outright on circular references.
18. What are toSorted(), toReversed(), and similar array methods, and why were they added?
These are immutable versions of existing array methods. arr.sort() mutates the original array in place; arr.toSorted() returns a new sorted array and leaves the original untouched. The same pairing exists for reverse()/toReversed() and splice()/toSpliced(). They were added specifically to reduce a common class of bugs where a function unexpectedly mutates an array that other parts of the code still hold a reference to.
19. What does Object.groupBy() do?
Object.groupBy() takes an array and a callback function, and groups the array’s elements into an object keyed by whatever the callback returns for each item, removing the need to manually write a reduce() call to achieve the same grouping.
const scores = [85, 92, 45, 67, 78, 30];
const grouped = Object.groupBy(scores, score => score >= 60 ? 'pass' : 'fail');
console.log(grouped); // { pass: [85, 92, 67, 78], fail: [45, 30] }20. What’s the difference between Set.prototype.union() and simply merging two arrays?
Set methods like union(), intersection(), and difference() operate directly on Set objects and automatically handle duplicate removal as part of the operation, since a Set can’t contain duplicate values by definition. Merging two arrays with the spread operator ([...arr1, ...arr2]) is simpler for plain arrays but requires an extra step, wrapping the result in new Set(), to get the same duplicate-free result these methods provide directly.
21. What is a generator function, and when would you actually use one?
A generator, defined with function*, can pause its execution at a yield statement and resume later exactly where it left off, rather than running start to finish in one pass like a normal function. This is useful for producing a sequence of values lazily, one at a time, only when requested, which matters for working with very large or even infinite sequences without generating everything in memory upfront.
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 222. What does the cause option on an Error object let you do?
new Error('message', { cause: originalError }) lets you attach the original underlying error to a new one you’re throwing, preserving the full chain of what actually went wrong instead of losing that context when you wrap a low-level error in a more descriptive, higher-level one. This makes debugging production issues considerably easier, since a caught error can be logged along with the entire chain of causes that led to it, rather than just the final, sometimes vague, message.
DOM and Browser Questions
23. What’s the difference between event bubbling and event capturing?
When an event fires on a nested element, bubbling propagates it upward from the target element through its ancestors, which is the default behavior in the DOM. Capturing does the opposite, propagating downward from the outermost ancestor to the target first. You can opt into capturing by passing { capture: true } as the third argument to addEventListener, though bubbling is what you’ll work with the vast majority of the time.
24. What is event delegation, and why is it useful?
Event delegation attaches a single event listener to a parent element instead of individual listeners to each child, relying on event bubbling to catch events from any child, including ones added dynamically after the listener was set up. This is more memory-efficient for lists with many items and automatically works for elements added later, without needing to manually attach a new listener to each one.
25. Write a debounce function and explain when you’d use it.
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
const handleSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);Debouncing delays running a function until a specified period of silence has passed since the last call, resetting the timer on every new call. It’s the standard approach for search-as-you-type inputs, where you don’t want to fire an API request on every single keystroke, only once the user has actually paused typing.
26. How is throttling different from debouncing?
Throttling guarantees a function runs at most once within a fixed time window, regardless of how many times it’s triggered, rather than waiting for a pause like debouncing does. Throttling fits scroll and resize event handlers, where you want steady, periodic updates during continuous activity rather than one single update after everything stops.
Array Methods and Practical Coding
27. What’s the difference between map(), filter(), and reduce()?
map() transforms every element and returns a new array of the same length. filter() returns a new array containing only elements that pass a test, potentially shorter than the original. reduce() collapses an array down into a single accumulated value, which could be a number, an object, or even a new array, making it the most flexible but also the least immediately readable of the three.
28. How would you flatten a nested array without using Array.flat()?
function flatten(arr) {
return arr.reduce((acc, item) => {
return acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
console.log(flatten([1, [2, 3, [4, 5]], 6])); // [1, 2, 3, 4, 5, 6]This recursive approach checks each element, and if it’s itself an array, recurses into it before concatenating the result, which handles arbitrarily deep nesting rather than just one level.
29. Write a simple deep clone function without relying on structuredClone().
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(deepClone);
const cloned = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}Interviewers often ask for this specifically to check whether you understand recursion and the distinction between primitives (copied directly) and objects or arrays (which need to be copied recursively to avoid the clone sharing references with the original).
30. Implement a curry function that transforms a normal function into a curried one.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6Currying transforms a function that takes multiple arguments into a sequence of functions that each take one argument (or a subset), returning a new function until enough arguments have been collected to actually run the original. It comes up in interviews mainly to test comfort with closures and higher-order functions rather than because it’s something you’ll write from scratch daily, though the underlying pattern shows up regularly in functional-style codebases and utility libraries.
31. What’s the difference between shallow copy and deep copy?
A shallow copy duplicates only the top level of an object or array; any nested objects inside it are still shared by reference with the original. A deep copy duplicates every level recursively, so changes to a nested object in the copy don’t affect the original at all. The spread operator ({...obj}) and Object.assign() both produce shallow copies, which is a common source of subtle bugs when developers assume they’re getting a fully independent copy.
Behavioral and Modern Workflow Questions
32. How do you use AI coding tools like Copilot in your day-to-day work?
This has become a standard question in 2026, and it’s not testing whether you use these tools, most interviewers assume you do. A strong answer describes specific, concrete use cases, generating boilerplate, drafting a first pass at a repetitive function, or writing initial test cases, alongside a clear sense of where you don’t rely on them, such as security-sensitive code or a genuinely novel algorithm where you need full control and understanding of every line.
33. Walk me through how you’d debug a production issue where a page is randomly freezing for users.
A strong answer describes an actual process rather than a guess: reproducing the issue if possible, checking browser performance tools for long tasks blocking the main thread, looking for accidental synchronous operations inside a loop or a render cycle, and checking whether a recent deploy correlates with when the freezing started. Interviewers are listening for a structured, methodical approach here more than a specific correct answer, since production debugging rarely has a single obvious cause.
34. Explain a technical concept from this list to someone non-technical, right now.
This tests communication, not knowledge. A common approach for something like closures: comparing it to a backpack a function carries with it, holding onto a few things it needs to remember from where it was created, even after it moves somewhere else in the code. Concrete, everyday analogies land better here than technically precise but jargon-heavy explanations.
Tips for Preparing Well
Depth beats breadth. Interviewers are increasingly uninterested in candidates who can define one hundred terms shallowly, and far more interested in someone who can explain why closures retain memory, why the event loop processes microtasks before macrotasks, or why a specific piece of code produces a surprising result. Practice writing utilities like debounce, throttle, and deep clone from understanding rather than memorized code, since interviewers can generally tell the difference when asked to modify your solution slightly on the spot. If TypeScript appears in the job posting, expect a dedicated round for it, and don’t treat it as optional prep just because the core language is JavaScript.
Frequently Asked Questions
Are algorithm questions (like on LeetCode) common in JavaScript interviews?
It depends heavily on company size and role. Startups and frontend-specific roles tend to focus more on practical JavaScript, building or debugging a feature, while larger companies often include algorithmic questions in addition to language fundamentals. Preparing for both is the safer approach unless you know specifically which style a company favors.
Do I need to learn TypeScript for JavaScript interviews?
Not for every interview, but it’s increasingly common, especially for React and Node roles at companies that have already migrated their codebase. Learning JavaScript deeply first and adding TypeScript afterward is a reasonable order, since TypeScript builds directly on JavaScript concepts and takes relatively little additional time once the fundamentals are solid.
What salary range should I expect for a JavaScript developer role?
This varies enormously by location, company, and experience level. In the United States, salaries commonly range from around $75,000 for early-career roles to $130,000 or more for experienced developers, with total compensation at large tech companies sometimes exceeding $180,000 for senior engineers. These figures shift significantly outside the US, so checking current, location-specific data is worth doing before relying on any single number.
How long should I spend preparing for a JavaScript interview?
For a junior or fresher role, two to three weeks of focused preparation covering ES6+ fundamentals, DOM manipulation, and basic asynchronous patterns is typically enough. Senior roles generally require a deeper, longer prep cycle covering memory management, design patterns, and performance optimization on top of the fundamentals.
Is it okay to admit I don’t know the answer to a question?
Yes, and it’s usually better than guessing confidently and being wrong. Interviewers generally respond well to a candidate who says “I’m not certain, but here’s how I’d reason through it or find out,” since that reflects how real development work actually happens far more than pretending to know everything on the spot.
Do interviewers really expect knowledge of newer features like Object.groupBy() or toSorted()?
It varies by company and seniority, but this expectation has grown noticeably as these features have become standard in modern codebases. Even if you’re not asked about a specific new method by name, being unaware that immutable array methods or native deep cloning exist can make your answers to related questions (like “how would you avoid mutating this array”) sound outdated compared to a candidate who reaches for the modern built-in solution directly.
Where to Go From Here
These JavaScript interview questions cover the areas that show up most consistently in 2026: fundamentals, closures and scope, asynchronous behavior and the event loop, modern ES2020+ features, DOM basics, and the practical coding challenges that test whether you actually understand a concept rather than just recognize its name. The fastest way to move from recognizing these answers to giving them fluently under pressure is writing the code yourself, repeatedly, rather than only reading through explanations.
If you’re rounding out your front-end fundamentals, our CSS tutorial covers the styling side of the skill set these questions assume you already have. For deeper reference on any specific method or feature mentioned here, MDN Web Docs remains the most reliable and detailed source for JavaScript documentation.