Modern JavaScript Interview Questions for Frontend Development
25 Modern JavaScript questions with answers for frontend development roles — 10 easy, 11 medium, 4 hard.
About Modern JavaScript
Modern JavaScript is the foundation of every frontend interview. This topic covers ES modules, destructuring, optional chaining, Map and Set, promise combinators, async pitfalls, immutability, classes with private fields and Proxies, along with coding tasks such as throttle, Promise.all polyfills, retries with backoff and request cancellation.
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 ES modules and CommonJS?
ES modules use import and export, are statically analysable, load asynchronously and are the standard in browsers and modern Node. CommonJS uses require and module.exports, loads synchronously and was Node's original system. Because ES module imports are static, bundlers can tree-shake unused exports, which shrinks bundles. Mixing the two causes interop issues, so new frontend projects use ES modules with type: module in package.json.
Destructuring pulls values out of arrays or objects into variables. Defaults apply only when the value is undefined, not when it is null. For example, const { name, city = 'Delhi' } = user sets city to Delhi if the property is missing. Function parameters can be destructured too, like function render({ title, size = 'md' } = {}), which avoids errors when no argument is passed and documents expected options clearly.
03
Explain optional chaining and the nullish coalescing operator.
Optional chaining ?. stops evaluation and returns undefined if the value before it is null or undefined, so user?.address?.pincode never throws. Nullish coalescing ?? returns the right side only when the left is null or undefined, unlike || which also replaces 0, false and empty strings. For example, const qty = input ?? 1 keeps a valid 0, while input || 1 would wrongly change 0 into 1.
04
When should you use Map and Set instead of plain objects and arrays?
Map allows keys of any type, keeps insertion order, has a size property and performs well with frequent additions and deletions, so it suits caches and lookups by object. Set stores unique values with fast has() checks, ideal for tracking selected ids. Plain objects are better for fixed-shape records and JSON. Arrays are better when order and index access matter and duplicates are allowed.
05
What are pure functions and why does immutability matter in frontend code?
A pure function returns the same output for the same input and has no side effects such as changing external variables. Immutability means creating new objects instead of modifying existing ones, for example using spread syntax to update state. Together they make code predictable and easy to test, and they let frameworks detect changes with a simple reference comparison, which enables efficient re-rendering.
A higher-order function takes a function as an argument or returns one, like map, filter or a custom withLogging(fn) wrapper. Currying transforms a function with many arguments into a chain of single-argument functions, so add(2)(3) returns 5. Currying makes partial application easy, for example creating formatINR = formatCurrency('INR') once and reusing it. Overusing it can reduce readability for teams unfamiliar with functional style.
07
Flatten a deeply nested array without using Array.prototype.flat.
A recursive reduce handles any depth:
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]
In real code, arr.flat(Infinity) does the same. For extremely deep nesting, an iterative version with a stack avoids call stack overflow.
08
Predict the output order of synchronous code, promises and setTimeout.
Synchronous code runs first, then microtasks, then macrotasks:
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
queueMicrotask(() => console.log('D'));
console.log('E');
// Output: A, E, C, D, B
A and E are synchronous. C and D are microtasks queued in order. B is a macrotask and waits until the microtask queue is empty, even with a 0 ms delay.
09
Editing one item in a copied list also changes the original list. Why?
Copying an array with spread or slice creates a new array, but the objects inside are still the same references. Changing copy[0].qty changes the original object too. The fix is to create new objects for changed items, for example list.map(item => item.id === id ? { ...item, qty: item.qty + 1 } : item), or use structuredClone when a full deep copy is genuinely needed.
10
Why do you want to build a career in frontend development?
I like work where users see the result directly. In college I built a timetable app for my class, and watching classmates use it daily taught me how small UI details matter. Frontend combines logic, design and performance, and the ecosystem keeps evolving, which keeps me learning. I want to build fast, accessible interfaces for products used by millions of people across India.
11
Compare Promise.all, Promise.allSettled, Promise.race and Promise.any.
Promise.all resolves with all results when every promise succeeds, but rejects immediately if any fails. Promise.allSettled always waits for all and returns each outcome with status fulfilled or rejected, useful for dashboards loading independent widgets. Promise.race settles with the first promise to settle, often used for timeouts. Promise.any resolves with the first success and rejects only if all fail, useful for trying multiple mirrors.
12
Why does await inside forEach not wait for each item?
forEach ignores the promise returned by an async callback, so all callbacks start almost at once and forEach finishes before any await completes. Code after it runs too early. For sequential processing, use a for...of loop with await inside. For parallel processing with a final result, use await Promise.all(items.map(async item => ...)). Choosing between them depends on whether order and rate limits matter.
A tagged template is a function placed before a template literal, like html`<p>${name}</p>`. The function receives the static string parts as an array and the interpolated values separately, so it can escape, transform or validate values before joining them. Libraries use this pattern for safe HTML templating, CSS-in-JS and GraphQL queries. Normal template literals simply interpolate values and support multi-line strings.
14
How do private class fields and getters work in modern JavaScript?
Fields prefixed with #, such as #balance, are truly private and cannot be accessed outside the class body, unlike the old underscore convention. Getters and setters, written get total() and set total(v), let you expose computed or validated properties while keeping internal state hidden. Static fields and static blocks are also supported. These features make classes safer for building reusable UI components and service objects.
Throttle allows a function to run at most once in a given interval:
function throttle(fn, wait) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= wait) {
last = now;
fn.apply(this, args);
}
};
}
window.addEventListener('scroll', throttle(updateProgressBar, 100));
Unlike debounce, which waits for calls to stop, throttle fires regularly during continuous events such as scrolling or dragging.
16
Write a fetch wrapper that retries with exponential backoff.
Keep a controller per request and abort the previous one:
let controller;
async function search(term) {
controller?.abort();
controller = new AbortController();
try {
const res = await fetch('/api/search?q=' + encodeURIComponent(term), {
signal: controller.signal
});
renderResults(await res.json());
} catch (err) {
if (err.name !== 'AbortError') showError(err);
}
}
This prevents slow old responses from overwriting newer results.
18
Search results sometimes show data for an older query. What is happening?
This is a race condition: requests for 'ja' and 'java' are in flight together, and if the older one responds last, it overwrites the newer results. I would cancel the previous request with AbortController, or tag each request with an id and ignore responses that are not the latest. Debouncing the input reduces the number of requests, and caching repeated queries makes responses faster and more consistent.
19
Users in India see order dates shifted by a day. What is the likely bug?
The server probably sends a date like 2026-11-12 without time zone, and new Date('2026-11-12') treats date-only strings as UTC midnight, while other formats are parsed as local time, causing shifts around midnight. I would send full ISO timestamps with offsets, store times in UTC, and format for display with Intl.DateTimeFormat('en-IN', { timeZone: 'Asia/Kolkata' }). The Temporal API, now arriving in browsers, makes this safer.
20
Errors from failed promises are silently disappearing in production. How do you catch them?
Promises without a catch produce unhandled rejections that do not stop the app but hide failures. I would add a global window.addEventListener('unhandledrejection') handler and window.onerror that report errors to a monitoring tool like Sentry with source maps. In code, every async call should have try and catch or a .catch, and ESLint rules such as no-floating-promises in TypeScript projects catch missing handlers.
21
How would you explain a difficult JavaScript concept to a junior teammate?
Situation: a junior developer kept writing bugs with async code. Task: I wanted them to understand promises rather than copy fixes. Action: I drew the event loop on a whiteboard, wrote three small console examples for them to predict, and then reviewed one of their real pull requests together. Result: within two weeks, their async bugs stopped and they started explaining the concept to the next new joiner.
22
What are WeakMap and WeakRef, and when are they useful?
A WeakMap holds object keys weakly, so when no other reference to the key exists, the entry can be garbage collected. This makes it ideal for attaching private metadata or caching computed data for DOM nodes without causing memory leaks. WeakMap keys are not enumerable. WeakRef holds a weak reference to a single object and is rarely needed, mostly for caches, because garbage collection timing is unpredictable.
A Proxy wraps an object and intercepts operations like get, set, has and deleteProperty through handler traps. Reflect provides matching methods that perform the default behaviour, so traps can add logic and then call Reflect.set. Reactive frameworks such as Vue 3 use Proxies to track which properties a component reads and re-render when they change. Proxies add overhead, so they suit framework internals more than hot loops.
JSON.parse runs on the main thread, so a huge payload blocks rendering and input. The best fix is to avoid sending that much: paginate, filter on the server or request only needed fields. If the data is truly needed, parse and process it inside a Web Worker and send back only what the UI needs, or stream it with NDJSON and process chunks. Compression reduces transfer time but not parse time.
Frequently asked questions
How important is Modern JavaScript in Frontend Development interviews?
Modern JavaScript is the foundation of every frontend interview. This topic covers ES modules, destructuring, optional chaining, Map and Set, promise combinators, async pitfalls, immutability, classes with private fields and Proxies, along with coding tasks such as throttle, Promise.all polyfills, retries with backoff and request cancellation.
How should I use the Modern JavaScript sheet?
Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.