JavaScript Coding Interview Questions With Solutions
The JavaScript questions that interviewers ask most often test the language itself, not algorithms: hoisting, this binding, the event loop, closures, and a few array utilities written by hand. The twelve questions below cover that set, grouped by topic, each with a solution short enough to write on a whiteboard. Read the question, write your own answer first, then compare. In an interview the code is the smaller part of the answer, and the explanation of why it works is what is graded.
Language fundamentals
1. What does this print, and why? The question tests hoisting, which is the way JavaScript moves declarations to the top of their scope before running the code.
console.log(a); // undefined, var is hoisted and set to undefined var a = 1; console.log(b); // ReferenceError, b exists but cannot be read yet let b = 2;
A var declaration is hoisted with the value undefined, so the first line prints without error. A let or const declaration is also hoisted, but it cannot be read before its line runs, and that gap is called the temporal dead zone.
2. What is this inside a regular function and inside an arrow function?
const user = { name: "Ada", regular() { return this.name; }, arrow: () => this.name, }; user.regular(); // "Ada" user.arrow(); // undefined, arrow functions take this from outside const detached = user.regular; detached.call(user); // "Ada", call sets this explicitly
A regular function receives this from how it is called. An arrow function has no this of its own and uses the one from the scope where it was written.
3. Reverse a string without built-in helpers. No split, reverse, or join.
function reverse(str) { let out = ""; for (let i = str.length - 1; i >= 0; i--) out += str[i]; return out; } reverse("abc"); // "cba"
The usual follow-up is about emoji, which occupy two positions and get split by this loop.
Asynchronous JavaScript
4. In what order do these lines print? The question tests the event loop, which is the scheduler that runs callbacks after the current code finishes.
console.log("start"); setTimeout(() => console.log("timeout"), 0); Promise.resolve().then(() => console.log("promise")); console.log("end"); // start, end, promise, timeout
Synchronous code runs first. Promise callbacks go in the microtask queue, which is emptied before any timer callback runs, so "promise" prints before "timeout".
5. Write Promise.all by hand. It takes an array of promises and resolves with their results in the same order, or rejects on the first failure.
function promiseAll(promises) { return new Promise((resolve, reject) => { const results = new Array(promises.length); let remaining = promises.length; if (remaining === 0) return resolve(results); promises.forEach((p, i) => Promise.resolve(p).then((value) => { results[i] = value; if (--remaining === 0) resolve(results); }, reject)); }); }
Writing to results[i] rather than pushing keeps the order stable, because promises can finish in any sequence.
6. Write a debounce function. Debouncing delays a call until the caller has been quiet for a set time, which is how search boxes avoid one request per keystroke.
function debounce(fn, waitMs) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), waitMs); }; } const onResize = debounce(() => console.log("resized"), 300);
Functions and closures
7. Write a counter factory. A closure is a function that keeps access to the variables of the scope where it was created, even after that scope has finished.
function makeCounter() { let count = 0; return () => ++count; } const next = makeCounter(); next(); // 1 next(); // 2
The variable count is private, because nothing outside makeCounter can reach it except the returned function.
8. Write a curry function. Currying turns a function of several arguments into a chain of functions that each take some of them.
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn(...args); return (...more) => curried(...args, ...more); }; } const add = curry((a, b, c) => a + b + c); add(1)(2)(3); // 6 add(1, 2)(3); // 6
9. Write a memoize function. Memoization saves the result of each call so a repeated argument returns the stored value instead of running the function again.
function memoize(fn) { const cache = new Map(); return (arg) => { if (!cache.has(arg)) cache.set(arg, fn(arg)); return cache.get(arg); }; } const fastSquare = memoize((n) => n * n);
The interviewer will ask about functions with several arguments, where the cache key must be built from all of them.
Arrays and objects
10. Flatten a nested array.
function flatten(arr) { return arr.reduce( (out, item) => out.concat(Array.isArray(item) ? flatten(item) : item), [] ); } flatten([1, [2, [3, [4]]]]); // [1, 2, 3, 4]
Mention arr.flat(Infinity) as the built-in, then explain the recursive version, because the recursion is what is tested.
11. Deep clone an object. A shallow copy shares nested objects with the original, while a deep clone copies every level.
function deepClone(value) { if (value === null || typeof value !== "object") return value; if (Array.isArray(value)) return value.map(deepClone); const copy = {}; for (const key of Object.keys(value)) copy[key] = deepClone(value[key]); return copy; }
The built-in structuredClone also handles dates and maps. The usual follow-up is about circular references, which this version does not handle.
12. Remove duplicates from an array.
const nums = [3, 1, 3, 2, 1]; const unique = [...new Set(nums)]; // [3, 1, 2] const uniqueBy = (items, key) => { const seen = new Set(); return items.filter((item) => !seen.has(item[key]) && seen.add(item[key])); };
A Set keeps insertion order, so the first occurrence of each value survives. The second version handles objects, where equality has to be defined by a chosen field.
If you are new to JavaScript
The questions above assume you already write JavaScript daily. If you are still learning, start with the smaller exercises in What Javascript coding challenges are perfect for beginners?, and use the concept-ordered list in Best Coding Challenges for Beginners for the algorithm side.
How to Prepare
- Explain before you type. For every question above, say what the output will be and why, then run the code to check.
- Know which round asks these. Language questions appear in phone screens and pair-programming rounds. What type of coding questions will be asked in an interview? explains how they differ from algorithm rounds.
- Cover the algorithm side separately. Grokking the Coding Interview teaches the patterns behind array, string, and tree questions, with every solution available in JavaScript.
- Practice under time. A mock interview with a live interviewer shows whether you can explain the event loop while writing the code.

GET YOUR FREE
Coding Questions Catalog

$98

$197

$72