JavaScript interviews assess both foundational runtime knowledge and practical engineering problem-solving. Whether you are preparing for a senior frontend role or designing interview loops for your team, these 25 core questions cover the essential concepts every JavaScript engineer should master.
1. Primitives vs. Reference Types
JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, symbol, and null. All primitives are immutable and passed by value.
Reference types (Object, Array, Function, Map, Set) are mutable and passed by reference:
let a = 10;
let b = a;
b = 20;let obj1 = { name: "Alice" }; let obj2 = obj1; obj2.name = "Bob"; console.log(obj1.name); // "Bob" (reference types share memory location) ```
2. var vs let vs const
- •
var: Function-scoped, hoisted withundefinedinitialization, allows redeclaration. - •
let: Block-scoped, hoisted but resides in the Temporal Dead Zone (TDZ) until evaluation, prevents redeclaration. - •
const: Block-scoped, resides in TDZ, requires immediate initialization, prevents variable reassignment (though internal object properties remain mutable).
function scopeExample() {
if (true) {
var x = 1;
let y = 2;
const z = 3;
}
console.log(x); // 1
// console.log(y); // ReferenceError: y is not defined
}3. Lexical Scope and Closures
A closure is the combination of a function bundled together with references to its surrounding state (lexical environment). Closures give inner functions access to outer function scope variables even after the outer function has returned.
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment() { count++; return count; },
decrement() { count--; return count; },
getValue() { return count; }
};const counter = createCounter(5); console.log(counter.increment()); // 6 console.log(counter.getValue()); // 6 ```
4. Variable Hoisting & Temporal Dead Zone (TDZ)
Hoisting is JavaScript's default behavior of moving declarations to the top of the current scope during execution context creation.
console.log(a); // undefined (var hoisted)// console.log(b); // ReferenceError: Cannot access 'b' before initialization (TDZ) let b = 10; ```
5. Execution Context and the this Binding
The value of this is determined dynamically at call time (except for arrow functions, which inherit this lexically from their enclosing context):
1. Implicit Binding: obj.method() → this is obj.
2. Explicit Binding: fn.call(ctx), fn.apply(ctx), fn.bind(ctx).
3. New Binding: new Constructor() → this is the newly created instance.
4. Default Binding: Plain function invocation → window (browser non-strict), undefined (strict mode).
6. Prototypes and Prototypal Inheritance
Every JavaScript object has an internal [[Prototype]] link to another object. Property lookups traverse up the prototype chain until the property is found or null is reached.
const animal = { eats: true };
const dog = Object.create(animal);console.log(dog.bark); // true (own property) console.log(dog.eats); // true (inherited from animal prototype) ```
7. Promises and Microtasks
A Promise represents the eventual completion or failure of an asynchronous operation. Promise resolution callbacks (.then(), .catch(), .finally()) are scheduled as microtasks.
8. async / await Syntax
Syntactic sugar built on top of Promises and Generators. An async function always returns a Promise. await pauses execution inside the function until the awaited Promise resolves or rejects.
async function fetchUserData(userId) {
try {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error("User not found");
return await res.json();
} catch (err) {
console.error("Failed to fetch user:", err);
throw err;
}
}9. Event Loop: Microtasks vs Macrotasks
The Event Loop executes tasks in the following order:
1. Execute synchronous script in call stack.
2. Drain the Microtask Queue completely (Promise callbacks, queueMicrotask, MutationObserver).
3. Pick one task from the Macrotask Queue (setTimeout, setInterval, requestAnimationFrame, I/O).
4. Render UI updates if needed.
5. Repeat.
10. Map vs Filter vs Reduce
- •
map: Transforms every element, returning a new array of equal length. - •
filter: Returns a new array containing elements that satisfy a predicate condition. - •
reduce: Accumulates elements into a single value (number, object, or array).
const numbers = [1, 2, 3, 4, 5];
const doubledEvensSum = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2)
.reduce((acc, curr) => acc + curr, 0); // (2*2) + (4*2) = 1211. Debounce Implementation
Debouncing ensures a function is not called until a specified delay has elapsed since its last invocation.
function debounce(fn, delayMs) {
let timerId = null;
return function (...args) {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this, args);
timerId = null;
}, delayMs);
};
}
```12. Throttle Implementation
Throttling guarantees a function executes at most once per specified time interval.
function throttle(fn, intervalMs) {
let lastExecTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastExecTime >= intervalMs) {
lastExecTime = now;
fn.apply(this, args);
}
};
}13. Shallow Copy vs. Deep Copy
- •Shallow Copy: Copies top-level properties. Nested objects retain shared references (
Object.assign(), spread operator{...obj}). - •Deep Copy: Recursively duplicates all nested objects and arrays (
structuredClone(), custom recursive copy function).
const original = { a: 1, b: { c: 2 } };
const shallow = { ...original };const deep = structuredClone(original); deep.b.c = 42; // Independent copy ``` *Practice implementing custom Deep Clone in an interview session.*
14. Strict Equality (===) vs Loose Equality (==)
=== checks equality without type coercion. == performs implicit type conversion before comparison, leading to subtle bugs:
0 == '0'; // true
0 === '0'; // false
null == undefined; // true
null === undefined; // false15. Destructuring & Rest / Spread Operators
Destructuring unpacks values from arrays or properties from objects into distinct variables. Rest (...) collects remaining properties, while spread expands iterables into elements.
const { name, ...details } = { name: "Pairlet", type: "Live Coding", price: 0 };
console.log(details); // { type: "Live Coding", price: 0 }16. ES Modules vs CommonJS
- •ES Modules (
import/export): Static structure, evaluated at compile/parse time, supports tree-shaking, strict mode by default. - •CommonJS (
require()/module.exports): Dynamic loading, evaluated synchronously at runtime (Node.js legacy default).
17. Error Handling Best Practices
Always wrap asynchronous operations in try/catch blocks or attach .catch() handlers. Re-throw unhandled domain errors or wrap them in custom Error subclasses.
18. JavaScript Memory Leaks
Common causes of memory leaks in JavaScript applications:
1. Unexpected global variables.
2. Forgotten timers (setInterval) or unremoved event listeners.
3. Out-of-DOM references holding detached DOM nodes.
4. Unbound closures keeping large scopes alive.
19. Garbage Collection Mechanics
JavaScript engines use a Mark-and-Sweep algorithm. Starting from root references (globalThis, stack variables), the collector marks all reachable objects. Any unmarked objects are deemed unreachable and garbage collected.
20. Event Delegation
Event delegation leverages event bubbling to attach a single event listener to a parent element rather than attaching individual listeners to multiple child nodes.
document.getElementById("list").addEventListener("click", (e) => {
if (e.target && e.target.nodeName === "LI") {
console.log("Clicked item:", e.target.textContent);
}
});21. Currying
Currying transforms a function with multiple arguments into a sequence of nested functions that each accept a single argument.
const curry = (fn) =>
function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...nextArgs) => curried.apply(this, args.concat(nextArgs));const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6 ```
22. Generator Functions & Iterators
Generators (function*) allow pausing and resuming function execution using the yield keyword, yielding sequence iterators on demand.
23. Concurrency in Single-Threaded JS
JavaScript achieves non-blocking concurrency despite being single-threaded by delegating asynchronous I/O (network requests, timers, file system ops) to the underlying system via Libuv (in Node) or Web APIs (in browsers).
24. Object.freeze() vs Object.seal()
- •
Object.freeze(): Prevents adding, deleting, or modifying existing property values (shallow immutability). - •
Object.seal(): Prevents adding or deleting properties, but allows modifying existing property values.
25. Symbol & WeakMap / WeakSet
- •
Symbol: Guaranteed unique primitive identifier, ideal for non-colliding object property keys. - •
WeakMap/WeakSet: Holds weak references to key objects, allowing key objects to be garbage-collected if no other references exist.
---
Practice JavaScript Interviews Live Are you evaluating JavaScript candidates or preparing for an upcoming technical loop? You can practice coding problems live in a zero-setup collaborative editor. [Create a free Pairlet interview room](https://www.pairlet.dev/interview/new) or explore our [coding problem library](https://www.pairlet.dev/problems).
Frequently Asked Questions
What is the difference between primitives and reference types in JavaScript?
Primitive values (number, string, boolean, null, undefined, symbol, bigint) are immutable and stored directly by value. Objects, arrays, and functions are reference types, stored in memory as references; mutating a property alters all references pointing to that object.
How does the JavaScript event loop handle microtasks vs macrotasks?
Microtasks (Promises, queueMicrotask, MutationObserver) are processed continuously until the microtask queue is empty after every task execution. Macrotasks (setTimeout, setInterval, setImmediate, I/O) are picked one at a time from the task queue after the microtask queue has drained completely.
Conduct Live Coding Interviews with Zero Friction
No candidate sign-up required. Create an instant room, share the link, and code together in real time with shared code execution.