JavaScript Interview Questions and Answers
64 hand-picked JavaScript interview questions with
detailed answers. Open the interactive version above to search, filter
by difficulty, run code, bookmark questions and track your progress.
What is a closure? Give a real use.
A closure is a function that keeps access to variables from its outer (lexical) scope even after the outer function has returned. The inner function "remembers" the environment it was created in.
Uses: data privacy (private variables), function factories, and holding state in callbacks — e.g. a debounce keeps its timer id in a closure.
function counter() { let n = 0; return () => ++n; }
const next = counter();
next(); // 1 → n survives but stays private
var vs let vs const, and what is hoisting?
- var — function-scoped, hoisted and initialised to
undefined, re-declarable. - let — block-scoped, hoisted but in the Temporal Dead Zone (error if used before declaration).
- const — block-scoped, assigned once. The binding is constant, not the object — you can still mutate a
const array/object.
Hoisting: declarations are moved to the top of their scope at compile time. var and function declarations are usable early; let/const are not.
Explain the event loop, microtasks vs macrotasks.
JS is single-threaded. The call stack runs synchronous code. Async callbacks wait in queues and the event loop pushes them onto the stack when it's empty.
- Microtask queue — Promise
.then, queueMicrotask, await continuations. Fully drained after each task. - Macrotask queue —
setTimeout, setInterval, I/O, UI events.
Microtasks always run before the next macrotask.
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// → 1, 4, 3, 2
== vs === ?
== compares after type coercion (0 == "0" is true). === checks value and type with no coercion.
Always use === unless you deliberately want coercion (e.g. x == null to catch both null and undefined).
0 == ''; // true
0 === ''; // false
null == undefined; // true
How does the this keyword work?
this depends on how a function is called:
- Regular call → global object, or
undefined in strict mode. - Method call
obj.fn() → obj. - Arrow function → no own
this; inherits from the enclosing scope. call / apply / bind → set this explicitly.
const obj = {
name: 'A',
greet() { setTimeout(() => console.log(this.name), 0); }
};
obj.greet(); // 'A' — arrow keeps this
Promise vs async/await, and how do you handle errors?
A Promise represents a future value (pending → fulfilled/rejected). async/await is syntactic sugar that lets asynchronous code read synchronously.
Errors: .catch() on a chain, or try/catch around await. Use Promise.all for parallel and Promise.allSettled when you need every result regardless of failures.
async function load() {
try {
const res = await fetch('/api/user');
return await res.json();
} catch (e) { /* handle */ }
}
Debounce vs throttle — what and when?
Debounce delays execution until events stop firing for a specified time. If another event occurs before the delay ends, the timer resets. It is ideal for search boxes, autocomplete, and validation.
Throttle limits execution to at most once every specified interval, regardless of how many events occur. It is ideal for scroll, resize, drag, and mouse movement events.
// Debounce
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}
// Throttle
function throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}
map vs forEach vs reduce.
map returns a new transformed array. forEach just iterates and returns nothing. reduce folds an array into a single value (sum, group-by, flatten, etc.).
const nums = [1, 2, 3];
nums.map(n => n * 2); // [2, 4, 6]
nums.reduce((a, b) => a + b, 0); // 6
Shallow copy vs deep copy.
Shallow copy creates a new object but copies only the first level. Nested objects and arrays are still shared references, so modifying them affects both the original and the copied object.
Deep copy creates a completely independent clone, including all nested objects and arrays. Modern JavaScript provides structuredClone() for deep cloning. The older JSON.parse(JSON.stringify()) approach works only for simple JSON data and loses Dates, Maps, Sets, functions, undefined values, and class instances.
const original = {
name: 'John',
address: {
city: 'Bangalore'
}
};
// Shallow Copy
const shallow = { ...original };
shallow.address.city = 'Mumbai';
console.log(original.address.city); // Mumbai
// Deep Copy
const deep = structuredClone(original);
deep.address.city = 'Delhi';
console.log(original.address.city); // Mumbai
console.log(deep.address.city); // Delhi
Explain prototypal inheritance.
Prototypal inheritance is JavaScript's way of sharing properties and methods between objects. Every object has a hidden link called a [[Prototype]]. If a property or method is not found on an object, JavaScript automatically looks for it in its prototype, then continues searching up the prototype chain until it finds it or reaches null.
class Animal {
speak() {
return 'Some sound';
}
}
class Dog extends Animal {
bark() {
return 'Woof!';
}
}
const dog = new Dog();
console.log(dog.bark());
console.log(dog.speak());
What is the Temporal Dead Zone (TDZ)?
The span between entering a scope and the point where a let/const variable is declared. The binding is hoisted but not initialised, so accessing it in that window throws a ReferenceError.
console.log(x); // ReferenceError (TDZ)
let x = 5;
Spread vs Rest operator.
Although both use the ... syntax, they perform opposite operations. Spread expands an array or object into individual elements, while Rest collects multiple values into an array or object.
const merged = [...a, ...b]; // Spread
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
} // Rest in function parameters
const { id, ...others } = obj; // Rest in object destructuring
null vs undefined.
undefined — a variable declared but not assigned, or a missing property/return. Set by the engine.
null — an intentional 'no value', set by the developer.
Quirk: typeof null is 'object' (a historical bug).
const name = input ?? 'Guest'; // keeps 0 and '' but replaces null/undefined
What is a higher-order function?
A function that takes a function as an argument and/or returns a function. Examples: map, filter, setTimeout, and any function factory.
const withLog = fn => (...args) => { console.log(args); return fn(...args); };
What is currying?
Currying is a functional programming technique that transforms a function with multiple arguments into a sequence of functions, each taking one argument. Each function remembers its argument using a closure until all required arguments are provided. Currying enables partial application, code reuse, and function composition.
// Curried Function
const add = a => b => c => a + b + c;
console.log(add(1)(2)(3)); // 6
// Partial Application
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(10)); // 20
console.log(triple(10)); // 30
What is event delegation?
Event delegation is a technique where you attach a single event listener to a parent element instead of adding listeners to each child. It works because events bubble from the target element up to its parent, allowing the parent to handle events from its children.
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
const item = event.target.closest('.item');
if (item) {
console.log(item.dataset.id);
}
});
Event bubbling vs capturing.
When an event fires, it travels capturing phase (top → target) then bubbling phase (target → top). Listeners run in the bubbling phase by default; pass { capture: true } for the capture phase.
stopPropagation() halts travel; preventDefault() cancels the default action.
What is callback hell and how do you avoid it?
Callback Hell is a situation where asynchronous callbacks become deeply nested, making code hard to read and maintain. It is commonly avoided using Promises and async/await.
function getUser(id, callback) {
setTimeout(() => callback({ id, name: 'John' }), 1000);
}
function getOrders(user, callback) {
setTimeout(() => callback(['Laptop', 'Phone']), 1000);
}
function getPayment(orders, callback) {
setTimeout(() => callback('Payment Successful'), 1000);
}
// Callback Hell
getUser(1, (user) => {
getOrders(user, (orders) => {
getPayment(orders, (payment) => {
console.log(payment);
});
});
});
How do you make an object immutable?
You can make an object immutable using Object.freeze(). It prevents adding, removing, or modifying an object's properties. However, Object.freeze() is shallow, meaning nested objects can still be modified unless they are also frozen.
const user = Object.freeze({
name: 'John',
age: 25
});
user.age = 30; // Ignored (or throws in strict mode)
console.log(user.age); // 25
When would you use a Set or a Map?
Use a Set when you need to store unique values or quickly check if a value exists. Use a Map when you need to store key/value pairs where keys can be of any data type (including objects), while preserving insertion order.
const unique = [...new Set([1, 1, 2, 3])];
// [1, 2, 3]
const map = new Map();
map.set('name', 'John');
map.set(1, 'One');
map.set({ id: 1 }, 'Object Key');
What do ?. and ?? do?
Optional chaining ?. short-circuits to undefined instead of throwing when accessing a property on null/undefined.
Nullish coalescing ?? returns the right side only when the left is null/undefined (unlike ||, it keeps 0, '', and false).
const city = user?.address?.city ?? 'Unknown';
Function declaration vs expression vs arrow function.
- Function Declaration — fully hoisted, can be called before its definition, has its own
this and arguments. - Function Expression — assigned to a variable, not callable before initialization, commonly used for callbacks and closures.
- Arrow Function — concise syntax, lexically binds
this, has no own arguments, prototype, or new. Best suited for callbacks and functional programming.
function decl() {} // hoisted
const expr = function () {};// not hoisted
const arrow = () => {}; // lexical this
What is an IIFE and the module pattern?
An IIFE (Immediately Invoked Function Expression) is a function that runs immediately after it is created. It creates its own private scope, so variables inside it cannot be accessed from outside. The Module Pattern uses an IIFE to keep data private while exposing only the functions that should be accessible.
const counter = (function () {
let count = 0; // Private variable
return {
increment() {
count++;
},
getCount() {
return count;
}
};
})();
counter.increment();
console.log(counter.getCount()); // 1
Explain destructuring with defaults and renaming.
Unpack arrays/objects into variables in one statement, with defaults for missing values and renaming for objects. Also enables clean swaps and nested extraction.
const { name = 'Guest', id: userId } = user;
const [first, , third] = list;
[a, b] = [b, a]; // swap
Object.keys(), Object.values(), Object.entries() and Object.fromEntries().
These methods help you work with JavaScript objects. Object.keys() returns an array of property names, Object.values() returns an array of values, Object.entries() returns key-value pairs, and Object.fromEntries() converts key-value pairs back into an object.
const user = {
name: 'John',
age: 25
};
console.log(Object.keys(user));
// ['name', 'age']
console.log(Object.values(user));
// ['John', 25]
console.log(Object.entries(user));
// [['name', 'John'], ['age', 25]]
const updated = Object.fromEntries(
Object.entries(user).map(([key, value]) => [key, String(value)])
);
console.log(updated);
// { name: 'John', age: '25' }
JSON.stringify / parse — gotchas?
JSON.stringify() converts a JavaScript object into a JSON string, while JSON.parse() converts a JSON string back into a JavaScript object.
Common gotchas: undefined, functions, and symbols are omitted; Date objects become ISO strings; NaN and Infinity become null; BigInt throws an error; circular references cause JSON.stringify() to fail. Use a replacer and reviver to customize serialization and deserialization.
const obj = {
name: 'John',
created: new Date(),
score: NaN
};
const json = JSON.stringify(obj, null, 2);
console.log(json);
const parsed = JSON.parse(json, (key, value) => {
if (key === 'created') return new Date(value);
return value;
});
console.log(parsed);
How does fetch() work, and how do you cancel a request?
fetch() is used to make HTTP requests. It returns a Promise that resolves to a Response object. Unlike many developers expect, fetch() does not throw an error for HTTP status codes like 404 or 500; you must check response.ok yourself. To cancel an ongoing request, use an AbortController.
const controller = new AbortController();
fetch('https://api.example.com/users', {
signal: controller.signal
})
.then(response => {
if (!response.ok) {
throw new Error('Request failed');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.log(error));
// Cancel the request
controller.abort();
Promise.all() vs Promise.allSettled() vs Promise.race() vs Promise.any().
These Promise methods are used to handle multiple asynchronous operations. Promise.all() waits for all promises to succeed, Promise.allSettled() waits for all promises regardless of success or failure, Promise.race() returns the first promise that settles, and Promise.any() returns the first promise that successfully resolves.
const p1 = Promise.resolve('A');
const p2 = Promise.reject('Failed');
const p3 = Promise.resolve('C');
Promise.all([p1, p3]);
Promise.allSettled([p1, p2, p3]);
Promise.race([p1, p2, p3]);
Promise.any([p2, p3]);
What are generators and iterators?
An iterator is an object that allows sequential access to a collection using the next() method. A generator is a special function declared with function* that automatically creates an iterator. It can pause execution using yield and resume later, making it ideal for lazy evaluation, large datasets, and infinite sequences.
// Generator Function
function* numbers() {
yield 1;
yield 2;
yield 3;
}
const gen = numbers();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Using for...of
for (const n of numbers()) {
console.log(n);
}
What is a Symbol and where is it used?
A Symbol is a unique and immutable primitive value introduced in ES6. It is mainly used as a unique object property key so that it does not conflict with other property names. Even if two Symbols have the same description, they are always different.
const id = Symbol('id');
const user = {
name: 'John',
[id]: 101
};
console.log(user[id]); // 101
What are WeakMap and WeakSet? When should you use them?
WeakMap and WeakSet are similar to Map and Set, but they store objects only using weak references. If an object is no longer used anywhere else in the application, JavaScript can automatically remove it from a WeakMap or WeakSet during garbage collection, helping prevent memory leaks.
const cache = new WeakMap();
const user = {
name: 'John'
};
cache.set(user, {
lastLogin: 'Today'
});
console.log(cache.get(user));
// { lastLogin: 'Today' }
Why does a for-loop with var log the wrong value in setTimeout()?
var is function-scoped, so all setTimeout() callbacks share the same i variable. By the time they execute, the loop has finished and i has its final value. let is block-scoped, so each loop iteration gets its own copy of i, producing the expected output.
// Using var
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Output: 3 3 3
// Using let
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Output: 0 1 2
typeof vs instanceof.
Use typeof to check the type of primitive values like strings, numbers, booleans, undefined, and functions. Use instanceof to check whether an object was created from a particular class or constructor, such as Array, Date, or custom classes.
typeof 'Hello'; // 'string'
typeof 123; // 'number'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof function() {}; // 'function'
typeof []; // 'object'
Array.isArray([]); // true
[] instanceof Array; // true
new Date() instanceof Date; // true
How do you check for NaN correctly?
Use Number.isNaN() to check if a value is NaN. Avoid the global isNaN() because it converts (coerces) values before checking, which can produce unexpected results.
Number.isNaN(NaN); // true
Number.isNaN('foo'); // false
isNaN('foo'); // true (coerces 'foo' to NaN)
isNaN('123'); // false
How do you update the DOM efficiently?
To update the DOM efficiently, reduce the number of DOM changes. Instead of updating elements one by one, batch your changes and apply them together. You can use a DocumentFragment, toggle CSS classes instead of repeatedly changing inline styles, and use requestAnimationFrame() for smooth visual updates.
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // Updates the DOM once
What is a pure function and why prefer it?
A pure function always produces the same output for the same input and does not cause side effects. It does not modify external variables, mutate its arguments, perform I/O operations, or depend on external state. Pure functions are predictable, easy to test, reusable, cacheable (memoization), and form the foundation of functional programming, Redux, and NgRx reducers.
// ✅ Pure Function
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5
// ❌ Impure Function
let total = 0;
function addToTotal(value) {
total += value;
return total;
}
console.log(addToTotal(5)); // 5
console.log(addToTotal(5)); // 10
What are the data types in JavaScript?
Primitive (immutable, copied by value): string, number, boolean, bigint, symbol, undefined, null.
Non-primitive (reference type): object — which includes arrays, functions, and dates.
typeof 'a'; // 'string'
typeof 10n; // 'bigint'
typeof null; // 'object' (historical bug)
typeof []; // 'object'
How do you convert between strings and numbers?
Number → string: String(n), n.toString(), or a template literal `${n}`.
String → number: Number(s), parseInt(s) (integer), parseFloat(s) (decimal), or the unary +s.
Number('123'); // 123
parseInt('123.45'); // 123
parseFloat('123.45');// 123.45
+'123'; // 123
(123).toString(); // '123'
Synchronous vs asynchronous programming.
Synchronous programming executes code one statement at a time. Each operation must finish before the next one starts, so long-running tasks block the execution.
Asynchronous programming allows time-consuming operations like API calls, timers, and file I/O to run without blocking the main thread. Once completed, the result is handled later using callbacks, Promises, or async/await, keeping the application responsive.
// Synchronous
console.log('Start');
console.log('Processing...');
console.log('End');
// Asynchronous
console.log('Start');
setTimeout(() => {
console.log('Timer Finished');
}, 2000);
console.log('End');
// Output:
// Start
// End
// Timer Finished
Is JavaScript single-threaded or multi-threaded?
JavaScript is single-threaded — one call stack, one thing at a time. It feels concurrent because of the event loop: async work is offloaded to the browser/Node APIs and their callbacks are queued back onto the stack when it's free. (Web Workers add real parallel threads, but they don't share the main thread.)
What is an event listener?
An event listener is a function that waits for a specific event (such as a click, key press, or mouse movement) to occur on an HTML element. When that event happens, the function is automatically executed. Event listeners are usually attached using addEventListener().
const btn = document.getElementById('save');
btn.addEventListener('click', () => {
console.log('Button clicked');
});
slice() vs splice().
slice() returns a new array without changing the original array. splice() modifies the original array by adding, removing, or replacing elements, and returns the removed elements.
const arr = ['a', 'b', 'c', 'd'];
const copied = arr.slice(1, 3);
console.log(copied); // ['b', 'c']
console.log(arr); // ['a', 'b', 'c', 'd']
const removed = arr.splice(1, 2);
console.log(removed); // ['b', 'c']
console.log(arr); // ['a', 'd']
call() vs apply() vs bind().
All set this explicitly. call invokes immediately with arguments listed individually; apply invokes immediately with arguments as an array; bind returns a new function with this permanently bound (call it later).
greet.call(obj, 'hi', '!');
greet.apply(obj, ['hi', '!']);
const bound = greet.bind(obj); bound('hi', '!');
What are template literals?
Template literals are strings enclosed in backticks (`) instead of quotes. They allow you to embed variables and expressions using ${...} and create multi-line strings without using string concatenation.
const name = 'John';
const age = 25;
const message = `Hello ${name}, you are ${age} years old.`;
console.log(message);
const multiLine = `Line 1
Line 2
Line 3`;
How does garbage collection work in JavaScript?
JavaScript uses automatic garbage collection (GC) to reclaim memory occupied by objects that are no longer reachable. Modern JavaScript engines (such as V8) primarily use the Mark-and-Sweep algorithm. Developers cannot manually free memory, but they can prevent memory leaks by removing unnecessary references, clearing timers, and unregistering event listeners.
// Object becomes unreachable
let user = {
name: 'John'
};
user = null; // Previous object is now eligible for garbage collection
// Prevent memory leaks
const timer = setInterval(() => {
console.log('Running...');
}, 1000);
clearInterval(timer);
How do you add or access properties dynamically on an object?
Use bracket notation when the key is dynamic or not a valid identifier: obj[key] = value. Dot notation (obj.key) only works for fixed, valid names.
const student = { name: 'A' };
const field = 'rollNumber';
student[field] = 42; // { name: 'A', rollNumber: 42 }
for...of vs for...in.
for...of loops through the values of an iterable like an Array, String, Map, or Set. for...in loops through the keys (property names) of an object. Use for...of for arrays and for...in for objects.
const fruits = ['Apple', 'Banana', 'Orange'];
// for...of (values)
for (const fruit of fruits) {
console.log(fruit);
}
// Apple
// Banana
// Orange
const user = {
name: 'John',
age: 25
};
// for...in (keys)
for (const key in user) {
console.log(key, user[key]);
}
// name John
// age 25
setTimeout() vs setInterval().
setTimeout() executes a function once after a specified delay. setInterval() executes a function repeatedly at a fixed time interval until it is stopped.
// Runs once after 2 seconds
const timeoutId = setTimeout(() => {
console.log('Executed once');
}, 2000);
// Cancel if needed
clearTimeout(timeoutId);
// Runs every 2 seconds
const intervalId = setInterval(() => {
console.log('Runs repeatedly');
}, 2000);
// Stop the interval
clearInterval(intervalId);
What is variable shadowing?
When a variable declared in an inner scope has the same name as one in an outer scope, the inner one shadows (hides) the outer within that block. Common with function parameters and block-scoped let/const.
let x = 1;
function f() { let x = 2; return x; } // inner x shadows outer
f(); // 2, outer x still 1
Generator function vs normal function.
A normal function runs from start to finish and returns a single value. A generator function (written using function*) can pause its execution using yield and continue later. Instead of returning a value directly, it returns an iterator that produces values one at a time.
function* numbers() {
yield 1;
yield 2;
yield 3;
}
const iterator = numbers();
console.log(iterator.next());
// { value: 1, done: false }
console.log(iterator.next());
// { value: 2, done: false }
console.log(iterator.next());
// { value: 3, done: false }
console.log(iterator.next());
// { value: undefined, done: true }
Mutable vs immutable in JavaScript.
Mutable values can be changed after they are created, while immutable values cannot. In JavaScript, primitive values (string, number, boolean, null, undefined, symbol, bigint) are immutable, whereas objects and arrays are mutable.
// Immutable (String)
let name = 'John';
let upper = name.toUpperCase();
console.log(name); // 'John'
console.log(upper); // 'JOHN'
// Mutable (Array)
const numbers = [1, 2];
numbers.push(3);
console.log(numbers); // [1, 2, 3]
// Mutable (Object)
const user = { name: 'John' };
user.age = 25;
console.log(user); // { name: 'John', age: 25 }
What are truthy and falsy values?
In a boolean context (if, &&, !), every value coerces to true or false. There are exactly 8 falsy values: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is truthy — including "0", "false", [], and {}.
if ([]) console.log('runs'); // [] is truthy!
Boolean('0'); // true
Boolean(0); // false
Boolean(NaN); // false
What does "use strict" do?
'use strict' enables Strict Mode, which makes JavaScript more secure by preventing common mistakes. For example, it throws an error if you use an undeclared variable, disallows duplicate parameter names, and changes some unsafe JavaScript behaviors into errors.
'use strict';
x = 10; // ❌ ReferenceError: x is not defined
let y = 20; // ✅ Correct
console.log(y);
find, filter, some, every, includes — what do they do?
- filter — returns a new array of all items passing a test.
- find — returns the first matching item (or
undefined). - findIndex — the index of the first match (or
-1). - some —
true if at least one item passes. - every —
true if all items pass. - includes —
true if the array contains a value (uses ===).
const nums = [1, 2, 3, 4];
nums.filter(n => n % 2 === 0); // [2, 4]
nums.find(n => n > 2); // 3
nums.some(n => n > 3); // true
nums.every(n => n > 0); // true
nums.includes(2); // true
What is an Execution Context in JavaScript?
An Execution Context is the environment in which JavaScript code runs. It contains information about variables, functions, the value of this, and how the code should execute. Every JavaScript program starts with a Global Execution Context, and every function call creates a new Function Execution Context.
let message = 'Hello';
function greet(name) {
const text = message + ' ' + name;
console.log(text);
}
greet('John');
What is Lexical Scope?
Lexical Scope means JavaScript determines where variables are accessible based on where functions and variables are written in the code, not where they are called. An inner function can access variables from its own scope as well as all outer scopes. This behavior is known as the scope chain and is the foundation of closures.
const name = 'John';
function outer() {
const city = 'Bangalore';
function inner() {
console.log(name);
console.log(city);
}
inner();
}
outer();
What is the difference between localStorage, sessionStorage, and Cookies?
localStorage, sessionStorage, and Cookies are browser storage mechanisms used to store data on the client side. localStorage stores data permanently until it is manually removed, sessionStorage stores data only for the current browser tab, while Cookies are small pieces of data that can automatically be sent to the server with every HTTP request.
// localStorage
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme'));
// sessionStorage
sessionStorage.setItem('user', 'John');
console.log(sessionStorage.getItem('user'));
// Cookie
document.cookie = 'language=en; max-age=3600';
__proto__ vs prototype — what's the difference?
prototype — a property that exists only on functions: the object that will become the prototype of instances created with new. Methods you want shared go here.
__proto__ — the actual internal link ([[Prototype]]) that every object has, pointing at the object it inherits from. Lookup walks this chain.
They meet at construction: new Foo() sets the instance's __proto__ to Foo.prototype.
function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return this.name + ' woofs'; };
const d = new Dog('Rex');
Object.getPrototypeOf(d) === Dog.prototype; // true — the link
d.bark(); // found via the chain: d -> Dog.prototype -> Object.prototype
What do Array flat() and flatMap() do?
flat(depth = 1) — flattens nested arrays by the given depth (Infinity for fully flat). flatMap(fn) — map then flat(1) in one pass: each element maps to an array, results are concatenated.
flatMap doubles as a map-and-filter in one step: return [] to drop an element, [x] to keep one, [x, y] to expand.
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
orders.flatMap(o => o.items); // List<List<T>> -> List<T>
nums.flatMap(n => n % 2 ? [n * 10] : []); // filter + map in one pass
What are reflow and repaint?
Reflow (layout) — the browser recomputes geometry: positions and sizes of elements. Triggered by DOM structure changes, size/position CSS, font changes, window resize. Expensive — it can cascade through descendants and ancestors.
Repaint — redrawing pixels without geometry change (color, background, visibility). Cheaper than reflow; every reflow implies a repaint, not vice versa.
Layout thrashing — the killer pattern: interleaving writes with reads of layout properties (offsetHeight, getBoundingClientRect) forces a synchronous reflow per iteration.
// thrashing: read-write-read-write forces layout each loop
items.forEach(el => { el.style.width = el.offsetWidth + 10 + 'px'; });
// batched: read all, then write all
const widths = items.map(el => el.offsetWidth);
items.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'; });
ESM import/export vs CommonJS require — what's the difference?
- CommonJS (
require/module.exports) — Node's original system. Synchronous, dynamic (require anywhere, even conditionally), exports are a copied value at call time. - ESM (
import/export) — the language standard. Static: imports/exports are declared at top level and analyzable without running the code — which is what enables tree-shaking, live read-only bindings, and async loading (import() for code splitting).
Browsers and modern bundlers are ESM-native; Node supports both ("type": "module" or .mjs). Angular/TS code is written as ESM and compiled.
// CommonJS — dynamic, sync
const { readFile } = require('fs');
if (dev) { const mock = require('./mock'); } // legal
// ESM — static graph + async dynamic import for splitting
import { map } from 'rxjs';
const { Chart } = await import('chart.js'); // lazy chunk
Polyfill vs transpilation (Babel)?
Two different compatibility problems:
- Transpilation — rewriting new syntax into old syntax at build time (Babel/TypeScript/esbuild): arrow functions → functions, optional chaining → ternary chains. Syntax can always be rewritten.
- Polyfill — runtime code that implements missing APIs (
Array.prototype.flat, fetch, Promise) by patching globals (core-js). APIs can't be transpiled away — the function either exists in the browser or must be provided.
A build targets browsers via browserslist; the toolchain decides which syntax to lower and which polyfills to include.
// you write:
const name = user?.profile?.name ?? 'guest';
// transpiled for old targets:
var name = (user && user.profile && user.profile.name) != null ? ... : 'guest';
// polyfill (conceptually):
if (!Array.prototype.flat) { Array.prototype.flat = function (d) { ... }; }
Implement a memoize function.
Memoization caches a pure function's results by argument, so repeat calls return instantly. The classic implementation is a closure over a Map keyed by the serialized arguments.
Points to mention while writing it: only valid for pure functions; the key strategy (JSON.stringify breaks on object key order and non-serializable args — a nested Map or WeakMap per object arg is the robust version); unbounded cache growth (real libraries add LRU eviction).
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const slowSquare = n => { for (let i = 0; i < 1e7; i++); return n * n; };
const fast = memoize(slowSquare);
fast(9); // computed
fast(9); // cached — instant
ES6+ features rundown — which do you actually use?
The working set worth naming with a use for each:
- let/const — block scope; arrow functions — lexical this;
- Template literals, destructuring, spread/rest, default params — everyday ergonomics;
- Promises → async/await (ES2017) — async flow;
- Modules (import/export), classes;
- Map/Set, Symbol, generators;
- Later highlights: optional chaining
?. + nullish ?? (ES2020), Promise.allSettled, flat/flatMap (ES2019), Array.at(-1), top-level await, structuredClone, #private fields (ES2022).
const { name = 'guest', ...rest } = user ?? {};
const tags = [...new Set(items.flatMap(i => i.tags))];
const last = tags.at(-1);
await Promise.allSettled(jobs.map(j => run(j)));