Modern JavaScript (ES6+) Interview Questions for MERN Stack
25 Modern JavaScript (ES6+) questions with answers for mern stack roles — 9 easy, 10 medium, 6 hard.
About Modern JavaScript (ES6+)
Modern JavaScript is the foundation of every layer in the MERN stack. This topic covers scope, hoisting, closures, the event loop, Promises, async/await, destructuring, spread and rest, modules and prototypes. Interviewers use these questions to test whether you truly understand how JavaScript runs, and they often include output-prediction puzzles and small coding tasks like debounce or a Promise.all polyfill.
Sheet progressStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
What is the difference between var, let and const in JavaScript?
var is function-scoped, hoisted with the value undefined and can be redeclared. let and const are block-scoped and sit in the temporal dead zone until their declaration line runs. const blocks reassignment of the binding, but an object or array declared with const can still be mutated. In modern MERN code use const by default, let when the value must change, and avoid var.
Hoisting means declarations are processed before code runs. Function declarations are hoisted fully, so you can call them before they appear. var declarations are hoisted but initialised to undefined, so console.log(x) before var x = 5 prints undefined. let, const and class declarations are hoisted too, but accessing them before the declaration throws a ReferenceError because of the temporal dead zone. Function expressions assigned to variables follow the variable rules.
A closure is a function that remembers the variables of the scope where it was created, even after that outer function has returned. For example, a makeCounter function can return an inner function that increments a private count variable. Closures power data privacy, function factories, memoization, debounce and throttle helpers, and React hooks, where event handlers capture props and state from the render that created them.
04
What is the difference between == and === in JavaScript?
== compares values after type coercion, so 0 == '0' and null == undefined are both true. === compares value and type without coercion, so 0 === '0' is false. Coercion rules are hard to remember and cause subtle bugs, so teams use === everywhere, often enforced by an ESLint rule. A common accepted exception is value == null, which checks for both null and undefined in one step.
05
How do arrow functions differ from regular functions?
Arrow functions have a shorter syntax and do not have their own this, arguments, super or new.target; they take this from the surrounding lexical scope. They cannot be used as constructors with new. This makes them ideal for callbacks, array methods and React event handlers. They are a poor choice for object methods that need this to refer to the object, or for prototype methods.
A Promise represents the eventual result of an asynchronous operation. It starts in the pending state and settles once, either fulfilled with a value or rejected with a reason, and never changes after that. You consume it with then, catch and finally, or with await inside an async function. Promises replaced deeply nested callbacks in Node.js APIs and make error propagation through a chain much easier.
07
Explain destructuring, the spread operator and rest parameters.
Destructuring unpacks values from arrays or objects into variables, for example const { name, email } = user, with optional defaults and renaming. Spread (...) expands an iterable or object, used to copy arrays, merge objects or pass arguments. Rest uses the same syntax to collect remaining items, as in function sum(...nums) or const { password, ...safeUser } = user. In React, spread is used constantly to create new state objects immutably.
08
A for loop using var with setTimeout inside prints 3, 3, 3 instead of 0, 1, 2. Why, and how do you fix it?
var is function-scoped, so all three callbacks close over the same variable, which has become 3 by the time the timers fire. The simplest fix is to declare the loop variable with let, because let creates a fresh binding for every iteration. Older fixes include wrapping the body in an immediately invoked function or passing i as the third argument to setTimeout.
09
Tell me about a time you had to learn a new JavaScript concept quickly to finish a task.
Situation: in my final-year project our dashboard froze while loading data from three APIs. Task: I had to fix it before the demo in two days. Action: I studied Promises and async/await properly, rewrote the nested callbacks, and used Promise.all to load the APIs in parallel with proper error handling. Result: load time dropped from 6 seconds to under 2, and I documented the pattern for my team.
10
How does the JavaScript event loop work with microtasks and macrotasks?
JavaScript runs one call stack. When the stack is empty, the event loop first drains the entire microtask queue, which holds Promise callbacks, queueMicrotask and await continuations. Only then does it pick the next macrotask, such as a setTimeout callback, an I/O callback or a UI event. This is why a resolved Promise callback always runs before a setTimeout of 0 ms scheduled at the same time.
11
How does async/await improve on Promise chains, and how do you handle errors with it?
async/await is syntax over Promises that lets asynchronous code read top to bottom like synchronous code. An async function always returns a Promise, and await pauses that function until the Promise settles. Errors are handled with try/catch/finally instead of chained catch calls. A common mistake is awaiting independent calls one after another; use Promise.all to run them in parallel when they do not depend on each other.
12
What is the difference between a shallow copy and a deep copy?
A shallow copy duplicates only the top level, so nested objects are still shared references. Spread syntax and Object.assign make shallow copies. A deep copy duplicates every nested level. structuredClone(obj) is the built-in way in modern browsers and Node.js and supports Dates, Maps and Sets, while JSON.parse(JSON.stringify(obj)) loses functions, undefined and Dates. In React state updates, shallow copies of each changed level are usually enough.
13
Compare Promise.all, Promise.allSettled, Promise.race and Promise.any.
Promise.all resolves with all results when every Promise fulfils and rejects as soon as any one rejects. Promise.allSettled always waits for all and returns status objects, useful when partial failure is fine. Promise.race settles with the first Promise to settle, often used for timeouts. Promise.any resolves with the first fulfilment and rejects with an AggregateError only if all reject, useful for trying multiple mirrors.
14
What is the output order of console.log('A'), setTimeout(() => console.log('B'), 0), Promise.resolve().then(() => console.log('C')) and console.log('D')?
The output is A, D, C, B. A and D are synchronous and run immediately on the call stack. The Promise callback C goes into the microtask queue, and the setTimeout callback B goes into the macrotask queue. After the synchronous code finishes, the event loop drains all microtasks first, printing C, and only then runs the timer callback, printing B, even with a 0 ms delay.
15
A search box fires an API request on every keystroke and overloads the backend. How would you solve it?
Debounce the input so the request goes out only after the user stops typing for around 300 ms. Also skip very short queries, cancel the previous request with AbortController so stale responses cannot overwrite newer ones, and cache recent results. On the server side, add rate limiting and an index on the searched field. Throttling is the alternative when you need periodic updates, like scroll tracking.
16
How do you debug a JavaScript bug that a user reports but you cannot reproduce locally?
Situation: a user reported a checkout button doing nothing, but it worked for me. Task: find the cause quickly. Action: I asked for browser, device and steps, checked our error-tracking logs, and found a TypeError only on an older Safari version due to an unsupported method. I reproduced it on a cloud device, added a polyfill and a test. Result: the fix shipped the same day and I added browser targets to our build.
Debounce delays a call until a quiet period has passed. Each new call clears the previous timer.
function debounce(fn, delay = 300) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const onSearch = debounce((q) => fetchResults(q), 400);
It relies on a closure over timer and keeps this and arguments intact.
18
Write a function to flatten a nested array without using Array.prototype.flat.
Recursion with reduce is the cleanest approach.
function flatten(arr) {
return arr.reduce((acc, item) =>
Array.isArray(item) ? acc.concat(flatten(item)) : acc.concat(item), []);
}
flatten([1, [2, [3, [4]]], 5]); // [1, 2, 3, 4, 5]
For very deep arrays, an iterative version with an explicit stack avoids hitting the recursion limit.
19
Group an array of order objects by their status using reduce.
reduce builds an object whose keys are statuses and values are arrays.
const grouped = orders.reduce((acc, order) => {
(acc[order.status] ||= []).push(order);
return acc;
}, {});
// { paid: [...], pending: [...] }
Newer runtimes also offer Object.groupBy(orders, (o) => o.status), which returns the same shape with a null prototype.
20
How does prototypal inheritance work, and how does class syntax relate to it?
Every object has an internal link to a prototype object. When a property is missing, JavaScript walks this prototype chain until it finds it or reaches null. Class syntax is mostly sugar over this model: methods defined in a class live on Constructor.prototype, and extends sets up the chain between prototypes. Classes add stricter behaviour, such as requiring new and supporting private fields with the # prefix.
21
You need to call an external API 500 times but it allows only 5 concurrent requests. How would you implement this?
Use a concurrency-limited queue. Keep a pool of five workers; each worker picks the next item from a shared index, awaits the request, and loops until the list is exhausted. Run the workers with Promise.all. Libraries such as p-limit do this neatly. Add retries with backoff for 429 responses and collect failures separately instead of failing the whole batch, so one bad item does not stop the job.
22
A single-page app gets slower the longer a user keeps it open. What JavaScript memory leaks would you check for?
Look for event listeners, intervals and subscriptions added without cleanup, closures holding large objects, detached DOM nodes still referenced in variables, growing caches or global arrays, and WebSocket handlers registered on every render. In React, these usually come from useEffect without a cleanup function. Use the Chrome DevTools Memory tab to take heap snapshots before and after an action and compare retained objects.
It must keep result order and reject on the first failure.
function promiseAll(items) {
return new Promise((resolve, reject) => {
const results = [];
let done = 0;
if (items.length === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then((value) => {
results[i] = value;
done += 1;
if (done === items.length) resolve(results);
}, reject);
});
});
}
Promise.resolve handles plain values mixed with Promises.
24
Write a deep clone function that handles nested objects, arrays and dates.
In production prefer structuredClone, but interviewers often ask for a manual version.
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== 'object') return value;
if (value instanceof Date) return new Date(value);
if (seen.has(value)) return seen.get(value);
const copy = Array.isArray(value) ? [] : {};
seen.set(value, copy);
for (const key of Object.keys(value)) copy[key] = deepClone(value[key], seen);
return copy;
}
The WeakMap prevents infinite loops on circular references.
25
Write an async helper that retries a failing call with exponential backoff.
Wait longer after each failure and give up after a limit.
async function retry(fn, retries = 3, base = 500) {
for (let attempt = 0; ; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt >= retries) throw err;
const wait = base * 2 ** attempt + Math.random() * 100;
await new Promise((r) => setTimeout(r, wait));
}
}
}
The random jitter stops many clients from retrying at the same moment.
Frequently asked questions
How important is Modern JavaScript (ES6+) in MERN Stack interviews?
Modern JavaScript is the foundation of every layer in the MERN stack. This topic covers scope, hoisting, closures, the event loop, Promises, async/await, destructuring, spread and rest, modules and prototypes. Interviewers use these questions to test whether you truly understand how JavaScript runs, and they often include output-prediction puzzles and small coding tasks like debounce or a Promise.all polyfill.
How should I use the Modern JavaScript (ES6+) sheet?
Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.