Common JavaScript Interview Questions
These are the questions that come up in almost every JavaScript front-end interview. The point of preparing is not to memorise a script — it is to have a mental model strong enough that you can talk through a follow-up without breaking. For each topic below, the goal is a one-sentence answer, a working example, and an idea of what trap the interviewer is hoping to spring.
What is a closure?
A function plus the variables it can still see from the scope where it was created, even after that scope has returned. Closures are how JavaScript hides state without classes and how every event handler "remembers" its surrounding data.
function counter() {
let n = 0;
return () => ++n;
}
const tick = counter();
tick(); tick(); tick(); // 3What does `this` refer to?
this is decided at the call site, not where the function is written. The rules, in priority order:
Called with
new—thisis the new object.Called with
.call,.apply,.bind—thisis whatever you pass.Called as
obj.method()—thisisobj.Otherwise —
thisisundefinedin strict mode (or the global object in sloppy mode).Arrow functions ignore all of the above — they inherit
thisfrom their enclosing lexical scope.
const user = {
name: 'Ada',
greet() { return 'hi ' + this.name; },
greetArrow: () => 'hi ' + this.name, // `this` is NOT `user`
};
user.greet(); // "hi Ada"
const g = user.greet;
g(); // "hi undefined" — call site has no receiverWhat is hoisting?
Before a script runs, the engine "lifts" certain declarations to the top of their scope. Function declarations are hoisted with their body; var is hoisted but initialised to undefined; let and const are hoisted into a temporal dead zone and throw if you touch them before the declaration line.
hello(); // works — function decl is fully hoisted
function hello() { console.log('hi'); }
console.log(a); // undefined — var is hoisted, no value yet
var a = 1;
console.log(b); // ReferenceError — TDZ
let b = 1;How does the event loop work?
JavaScript is single-threaded. The event loop runs one task at a time from the macrotask queue (setTimeout, I/O, UI events). After each task it drains all microtasks (Promise.then, queueMicrotask) before the next task. That single rule explains most of the surprising orderings.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');A D C B
`==` vs `===`
=== (strict equality) compares without converting types. == first coerces the operands and then compares — which is where the spooky behaviour comes from.
0 == ''; // true — both coerce to 0 0 == '0'; // true — '0' becomes 0 '' == '0'; // false — string vs string, no coercion null == undefined; // true (special case) null === undefined; // false NaN == NaN; // false in both — NaN is never equal to anything
`var`, `let`, `const`
var— function-scoped, hoisted, redeclarable. Pre-ES6, basically obsolete in new code.let— block-scoped, in the TDZ until the declaration, reassignable.const— block-scoped, must be initialised, the binding cannot be reassigned. The value can still mutate (const arr = []; arr.push(1)is fine).
for (var i = 0; i < 3; i++) {}
console.log(i); // 3 — var leaks out of the block
for (let j = 0; j < 3; j++) {}
console.log(j); // ReferenceErrorPrototypes and inheritance
Every object has an internal link to another object called its prototype. Property lookups walk the chain until they find a match (or hit null). class is mostly sugar over this — new Cat() sets the instance's prototype to Cat.prototype.
class Animal { speak() { return 'sound'; } }
class Cat extends Animal { speak() { return 'meow'; } }
const c = new Cat();
c.speak(); // 'meow'
Object.getPrototypeOf(c) === Cat.prototype; // true
Object.getPrototypeOf(Cat.prototype) === Animal.prototype; // truePromises and async/await
A Promise is an object that will eventually be fulfilled with a value or rejected with an error. async/await is syntax sugar: an async function returns a promise, and await pauses it until the awaited promise settles.
async function getUser(id) {
try {
const res = await fetch('/api/users/' + id);
if (!res.ok) throw new Error('bad status');
return await res.json();
} catch (err) {
console.error('lookup failed', err);
return null;
}
}A few more quick ones
Pass-by-value or reference? All assignments copy the value. Objects are values that happen to be references — so the reference is copied.
nullvsundefined?undefinedmeans "never assigned";nullis a deliberate "nothing here" you set yourself.Pure function? Same inputs, same output, no side effects.
Debounce vs throttle? Debounce waits for silence, throttle fires at most every N ms.
Shallow vs deep copy?
{...obj}andObject.assignare shallow;structuredClone(obj)is deep.