
JavaScript interviews test more than syntax recall. Interviewers want to see that you understand *why* the language behaves the way it does — and that you can reason through edge cases on the spot.
Here are 10 questions that come up consistently, from junior to mid-level roles, along with focused sample answers you can adapt.
1. What is a closure and when would you use one?
Sample answer: A closure is a function that retains access to variables from its outer scope even after that outer function has returned. JavaScript creates closures automatically whenever a function is defined inside another function.
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2count persists between calls because the inner function closes over it. Closures are useful for data encapsulation (hiding state from the outside world), factory functions, and memoization. A common interview gotcha: closures in loops. Use let (block-scoped) instead of var to avoid all loop iterations sharing the same variable reference.
2. What is hoisting and how does it differ between `var`, `let`, and `const`?
Sample answer: Hoisting is JavaScript's behavior of moving declarations to the top of their scope during the compilation phase — before any code runs.
| Declaration | Hoisted? | Initialized? | Accessible before declaration? |
|---|---|---|---|
var | Yes | As undefined | Yes (returns undefined) |
let | Yes | No | No (Temporal Dead Zone) |
const | Yes | No | No (Temporal Dead Zone) |
| Function declaration | Yes | Fully | Yes |
The practical takeaway: var is silently initialized as undefined, which hides bugs. let and const throw a ReferenceError if you try to use them before their declaration — which is actually safer behavior. Prefer const by default, let when reassignment is needed, and avoid var in new code.
3. Explain the JavaScript event loop.
Sample answer: JavaScript is single-threaded — it can only do one thing at a time. The event loop is the mechanism that lets it handle asynchronous work without blocking.
Here's the execution order:
- Call stack — synchronous code runs here first.
- Microtask queue — Promise callbacks (
.then,.catch,async/await) drain completely after each task. - Macrotask queue —
setTimeout,setInterval, and I/O callbacks run one at a time, after the microtask queue is empty.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2setTimeout fires *after* the Promise .then even with a 0ms delay — because Promises are microtasks and drain before macrotasks. This distinction trips up a lot of candidates.
4. What is the prototype chain?
Sample answer: Every JavaScript object has an internal link to another object called its prototype. When you access a property that doesn't exist on an object, JS walks up the prototype chain until it either finds the property or reaches null.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound.`;
};
const dog = new Animal('Rex');
dog.speak(); // "Rex makes a sound."dog doesn't have a speak property directly — JS finds it on Animal.prototype. ES6 class syntax is syntactic sugar over this same prototype system. Interviewers often ask you to explain the difference between __proto__, prototype, and Object.getPrototypeOf() — know that prototype is on constructor functions, __proto__ is the actual link between instances.
5. How do Promises work, and when would you use `async/await` instead?
Sample answer: A Promise represents a value that will be available in the future — it's either pending, fulfilled, or rejected. Promises replaced callback hell with a chainable API.
fetch('/api/user')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));async/await is syntactic sugar over Promises that makes async code read like synchronous code:
async function getUser() {
try {
const res = await fetch('/api/user');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}Use async/await when readability matters or when you need sequential async steps. Use Promise combinators (Promise.all, Promise.allSettled) when you want to run multiple async operations in parallel.
6. What does `this` refer to, and how do arrow functions change it?
Sample answer: this in JavaScript refers to the execution context — who called the function. The value depends on *how* the function is invoked:
- Method call (
obj.method()) →thisisobj - Regular function call →
thisisundefined(strict mode) or the global object - Constructor call (
new Fn()) →thisis the new instance - Explicit binding (
.call,.apply,.bind) →thisis whatever you pass
Arrow functions don't have their own this. They capture this from the surrounding lexical scope at the time they're defined. This makes them useful inside class methods or callbacks where you'd otherwise lose the correct this:
class Timer {
constructor() { this.seconds = 0; }
start() {
setInterval(() => {
this.seconds++; // `this` is the Timer instance, not global
}, 1000);
}
}7. What are the key ES6+ features you use regularly?
Interviewers don't want a list — they want to see you explain tradeoffs.
Destructuring — pull values from arrays or objects concisely. Particularly useful in function parameters to name what you need.
Spread / Rest — ... spreads an iterable into individual elements, or collects remaining elements. Common in state updates and function signatures.
Arrow functions — shorter syntax and lexical this. Not a replacement for all functions (avoid them as object methods where you need dynamic this).
Template literals — backtick strings with ${} interpolation. Cleaner than string concatenation.
Modules (import/export) — named and default exports let you split code into reusable files. Interviewers sometimes ask about the difference between CommonJS require and ES modules.
Optional chaining (?.) and nullish coalescing (??) — added in ES2020, now ubiquitous. user?.address?.city avoids throwing if address is undefined; config.timeout ?? 3000 defaults to 3000 only when the left side is null or undefined.
8. How would you explain the difference between `==` and `===`?
Sample answer: === (strict equality) checks value *and* type — no coercion. == (loose equality) coerces types first, which produces surprising results:
0 == false // true (false coerces to 0)
'' == false // true (both coerce to 0)
null == undefined // true (special case)
0 === false // falseThe practical rule: always use === unless you specifically need the null == undefined comparison (which is one of the few legitimate uses of ==). Loose equality makes code harder to reason about and is a frequent source of bugs.
9. What is `debounce` and how would you implement it?
Debounce is a technique that delays calling a function until a specified time has passed since the last invocation — useful for search inputs, resize handlers, and anywhere you want to limit expensive operations.
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const onSearch = debounce((query) => fetchResults(query), 300);
input.addEventListener('input', (e) => onSearch(e.target.value));Interviewers often follow up by asking about throttle (runs at most once per interval, regardless of how many times it's called) — know the difference. Debounce waits for the user to stop; throttle fires on a regular schedule.
10. How do you handle errors in async JavaScript?
Sample answer: Error handling strategy depends on whether you're using Promise chains or async/await.
With .catch() on a chain:
fetchData()
.then(process)
.catch(err => handleError(err));With try/catch in async functions:
async function load() {
try {
const data = await fetchData();
return process(data);
} catch (err) {
handleError(err);
}
}A common mistake: forgetting that an unawaited Promise won't be caught by a surrounding try/catch. If you forget await, the error propagates as an unhandled rejection. Use Promise.allSettled when you want results from multiple Promises regardless of individual failures — unlike Promise.all, it doesn't short-circuit on the first rejection.
Passing the Technical Screen
Strong answers to these questions get you through the coding screen. But the conversation before and after the technical round matters too. If you're preparing for JS interviews, Articuler can find the actual hiring manager for the role and help you prep for the interview on that specific person — their background, what they care about, and how to open the conversation. That 15-minute conversation with the right person often determines whether your technical answers land.
If you want to pair technical prep with a better approach to reaching out, check the tell me about yourself sample answers guide — it covers how to position yourself compellingly in the first 60 seconds of any interview.
FAQ
What JavaScript topics come up most in junior interviews?
Closures, hoisting, the event loop, and this binding are the most common. You'll also likely get at least one question on Promises or async/await, and a small coding exercise (often debounce, array methods, or a simple DOM task).
Do I need to know ES6+ features for a JavaScript interview?
Yes. Interviewers expect you to know destructuring, arrow functions, template literals, spread/rest, and modules. Optional chaining (?.) and nullish coalescing (??) come up frequently at mid-level.
How should I handle a JavaScript question I don't know?
Walk through your reasoning out loud. Say what you do know (related concepts, what the expected behavior might be) and acknowledge the gap directly. Most interviewers value reasoning process over memorized answers.