JavaScript interview questions for freshers
The basics every first round checks: types, equality, variables, array methods and browser questions.
What is JavaScript and where does it run?
JavaScript is a dynamically typed, single-threaded language that runs in an engine such as V8 (Chrome, Node.js). It runs web pages, servers and command line tools, and its event loop lets one thread wait on many slow operations. See What is JavaScript.
What is the difference between var, let and const?
var is function scoped and hoisted as undefined; let and const are block scoped and throw a ReferenceError before their line. const also forbids reassignment.
function demo() {
if (true) {
var a = 1;
let b = 2;
}
console.log(typeof a, typeof b);
}
demo();
const user = { name: 'Ada' };
user.name = 'Grace';
console.log(user.name);The trap: const does not freeze the object, so user.name still changes. See let, const and var.
What are the data types in JavaScript?
There are eight: seven primitives (number, string, boolean, undefined, null, bigint, symbol) and object. Arrays, functions and dates are objects.
const values = [42, 'hi', true, undefined, null, 10n, Symbol('id'), {}, [], function () {}];
console.log(values.map(v => typeof v).join(', '));
console.log(Array.isArray([]), Array.isArray({}));The traps: typeof null and typeof [] are both 'object', so use value === null and Array.isArray.
What is the difference between == and === in JavaScript?
=== compares value and type with no conversion; == converts the operands to a common type first.
console.log(0 == '0', 0 === '0');
console.log(null == undefined, null === undefined);
console.log('' == 0, [] == 0);Use === everywhere except value == null, which matches only null and undefined. Details in equality operators.
What is the difference between null and undefined?
undefined means no value was assigned: an uninitialized variable, a missing property, a function with no return. null is a value you assign on purpose to mean empty.
let a;
const b = null;
const user = {};
console.log(a, b, user.email);
console.log(typeof a, typeof b);
console.log(a == b, a === b);JSON.stringify drops undefined properties but keeps null, which matters when you send data to an API.
What are truthy and falsy values in JavaScript?
A falsy value becomes false in a boolean context. There are eight: false, 0, -0, 0n, '', null, undefined and NaN, plus the browser's legacy document.all (see MDN's falsy list); everything else is truthy, '0' and {} included.
const values = [0, -0, 0n, '', null, undefined, NaN, false, '0', 'false', [], {}];
console.log(values.filter(Boolean).length);
console.log(values.map(v => (v ? 'T' : 'F')).join(''));The trap: if (items) is true for an empty array, so check items.length. See truthy and falsy.
What is the difference between an arrow function and a regular function?
An arrow function has no this of its own; it uses the this of the surrounding code. It also has no arguments or prototype and cannot be called with new.
'use strict';
const team = {
name: 'core',
members: ['ana', 'raj'],
arrow() {
return this.members.map(m => `${m}@${this.name}`);
},
regular() {
return this.members.map(function (m) {
return `${m}@${this?.name}`;
});
},
};
console.log(team.arrow().join(' '));
console.log(team.regular().join(' '));The inner regular function gets its own this, undefined in strict mode, so the second line is ana@undefined raj@undefined.
What is the difference between the spread and rest operators?
Both are written .... Spread expands an iterable or object into separate items; rest collects the remaining items into an array or object, in parameters and destructuring.
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
const nums = [1, 2, 3];
console.log(sum(...nums));
const more = [...nums, 4];
const settings = { theme: 'dark', ...{ theme: 'light', lang: 'en' } };
console.log(more.join(','), settings.theme, settings.lang);In an object literal the later key wins, so settings.theme is light. Spread copies one level deep.
What is destructuring in JavaScript?
Destructuring unpacks array items or object properties into variables in one statement, with renaming, defaults and nesting.
const user = { name: 'Ada', role: 'admin', address: { city: 'Pune' } };
const { name, role: title, address: { city }, age = 30 } = user;
console.log(name, title, city, age);
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);[a, b] = [b, a] swaps without a temporary. Destructuring a missing nested object throws a TypeError, so default it: address: { city } = {}.
What is the difference between map, filter and reduce?
map transforms every item into a new array of the same length, filter keeps the items that pass a test, and reduce folds the array into one value. None of them change the original.
const orders = [
{ item: 'pen', price: 10, qty: 3 },
{ item: 'book', price: 250, qty: 1 },
{ item: 'bag', price: 900, qty: 0 },
];
const total = orders
.filter(o => o.qty > 0)
.map(o => o.price * o.qty)
.reduce((sum, x) => sum + x, 0);
console.log(total);Always give reduce an initial value, or an empty array throws a TypeError.
What is a higher-order function in JavaScript?
A higher-order function takes a function as an argument, returns one, or both.
function repeat(n, action) {
for (let i = 1; i <= n; i++) action(i);
}
repeat(3, i => console.log('step', i));
function multiplier(factor) {
return x => x * factor;
}
const double = multiplier(2);
console.log([1, 2, 3].map(double).join(','));repeat takes a function and multiplier returns one; map, setTimeout and addEventListener are built-in examples. See higher-order functions.
What is the difference between for...in and for...of?
for...in loops over an object's enumerable string keys, inherited ones included. for...of loops over the values of an iterable such as an array, string, map or set.
const arr = ['a', 'b'];
arr.extra = 'x';
for (const k in arr) console.log('in:', k);
for (const v of arr) console.log('of:', v);for...in also shows extra. A plain object is not iterable, so loop it with for...of over Object.entries(obj).
What is the difference between slice and splice?
slice(start, end) returns a copy of part of an array and leaves the original alone. splice(start, deleteCount, ...items) removes and inserts in place and returns the removed items.
const nums = [10, 20, 30, 40, 50];
console.log(nums.slice(1, 3).join(','), nums.join(','));
const removed = nums.splice(1, 2, 'x');
console.log(removed.join(','), nums.join(','));slice excludes the end index; toSpliced (ES2023) is splice without the mutation.
What is NaN and how do you check for it?
NaN is the number value from a failed numeric operation, like Number('12px'). It is the only value not equal to itself, so x === NaN is always false.
console.log(typeof NaN, NaN === NaN);
console.log(isNaN('hello'), Number.isNaN('hello'));
console.log(Number.isNaN(Number('12px')));Use Number.isNaN: the global isNaN converts its argument first, so isNaN('hello') is true.
When should you use a Map instead of a plain object?
Use a Map when keys are not known in advance, are not strings, or change often; use a plain object for fixed records.
const m = new Map();
const key = { id: 1 };
m.set(key, 'object key').set(1, 'number key').set('1', 'string key');
console.log(m.size, m.get(key), m.get(1), m.get('1'));
const o = {};
o[1] = 'number key';
o['1'] = 'string key';
console.log(Object.keys(o).length, o[1]);The object ends up with one key, because non-symbol keys become strings. See the hash map visualization.
How do you remove duplicates from an array in JavaScript?
Use [...new Set(arr)]: a Set keeps unique values in first-appearance order, in O(n).
const ids = [3, 1, 3, 2, 1];
const unique = [...new Set(ids)];
console.log(unique.join(','));
console.log(new Set(ids).has(2));
const users = [{ id: 1 }, { id: 1 }];
console.log(new Set(users).size);The trap: a Set compares objects by reference, so the last line prints 2. To dedupe objects by a field, key a Map by that field.
What is the difference between ?? and ||?
a || b returns b when a is any falsy value; a ?? b only when a is null or undefined. Use ?? when 0, '' or false are valid.
const settings = { volume: 0, title: '', theme: null };
console.log(settings.volume || 50, settings.volume ?? 50);
console.log(settings.title || 'Untitled', JSON.stringify(settings.title ?? 'Untitled'));
console.log(settings.user?.name, settings.theme ?? 'light');It pairs with optional chaining: user?.name ?? 'Guest' gives the default instead of throwing when user is missing.
What does this print: copying an object versus copying a number?
const a = { score: 1 };
const b = a;
b.score = 2;
let x = 1;
let y = x;
y = 2;
console.log(a.score, x);Predict the output
b = a copies the reference, so both names point at one object. y = x copies the number, so x stays 1. Copy an object with { ...a } (one level) or structuredClone(a).
What is event bubbling and event delegation?
An event travels down to its target (capturing), then bubbles back up through every ancestor. Event delegation puts one listener on a parent and checks event.target to find the child that was clicked.
document.querySelector('#list').addEventListener('click', (event) => {
const item = event.target.closest('li');
if (!item) return;
console.log('clicked', item.dataset.id);
});
It also handles items added later. stopPropagation() stops bubbling; preventDefault() stops the browser's default action.
What is the difference between async and defer on a script tag?
Both download an external script without blocking HTML parsing. async runs it as soon as it arrives, in any order; defer runs it after parsing, in document order.
Use defer for code that touches the DOM or depends on another script, async for independent scripts such as analytics. Module scripts are deferred by default.
What is the difference between localStorage, sessionStorage and cookies?
What is strict mode in JavaScript?
Strict mode, turned on with 'use strict', turns silent mistakes into errors. ES modules and class bodies are always strict.
- assigning to an undeclared variable throws instead of creating a global
thisin a plain function call isundefined, not the global object- writing to a read-only property throws a
TypeError
JavaScript closures, scope and hoisting interview questions
How JavaScript decides which variable a name refers to, and the closure patterns built on it.
What is a closure in JavaScript?
A closure is a function that keeps access to the variables of the scope it was created in, even after that scope has finished. Each outer call creates a separate set.
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count,
};
}
const a = makeCounter();
const b = makeCounter();
a.increment();
a.increment();
b.increment();
console.log(a.get(), b.get());a and b count separately, and nothing outside can touch count. More in closures.
What is hoisting in JavaScript?
Declarations are processed before the code runs, so names exist from the start of their scope. Function declarations are hoisted with their body, var as undefined, and let, const and class cannot be used before their line.
console.log(greet());
console.log(typeof later);
function greet() {
return 'hoisted';
}
var later = () => 'not yet';
try {
console.log(notYet);
} catch (e) {
console.log(e.name);
}
let notYet = 1;A function expression assigned to var behaves like the var, so typeof later is undefined.
What is the temporal dead zone?
The temporal dead zone is the time between entering a scope and the line that declares a let, const or class; touching the name then throws a ReferenceError.
function check() {
try {
return value;
} catch (e) {
return e.message;
}
}
console.log(check());
const value = 1;
console.log(check());It is about when code runs, not where it is written: the second call, after the declaration, returns 1.
What is scope in JavaScript, and what is lexical scope?
Scope is where a name is visible: global, module, function or block (let, const, class inside {}). Lexical scope means a function sees the variables where it was written, not where it is called.
const level = 'global';
function outer() {
const level = 'outer';
return function inner() {
return level; // 'outer', found by walking up where inner was defined
};
}
Lookups walk outward to the global scope, and a name never found throws a ReferenceError.
What does this print: arrow functions created in a var loop?
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map(f => f()).join(' '));Predict the output
var makes one i shared by all three arrows, and it is 3 by the time they run. With let, each iteration gets its own i and it prints 0 1 2.
What is an IIFE and why was it used?
An IIFE (immediately invoked function expression) is defined and called in one expression, giving its variables a private scope.
const config = (() => {
const secret = 'abc123';
return { masked: secret.slice(0, 2) + '****' };
})();
console.log(config.masked, typeof secret);Modules and block scope replaced it for privacy; it survives as (async () => { ... })() to use await at the top level of a script.
How do you write a memoize function?
Memoization caches results by argument, so a repeated call returns the stored answer. A closure holds the cache.
function memoize(fn) {
const cache = new Map();
return n => {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
}
let calls = 0;
const square = memoize(n => {
calls++;
return n * n;
});
square(4);
square(4);
square(5);
console.log(calls);calls is 2 because the second square(4) hit the cache. It suits only pure functions, and an unbounded cache leaks memory.
What is the difference between debounce and throttle?
Debounce runs once after calls stop for a set time; throttle runs at most once per time window. Debounce suits search boxes, throttle suits scroll handlers.
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
function throttle(fn, ms) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= ms) {
last = now;
fn(...args);
}
};
}
const search = debounce(q => console.log('searching', q), 50);
search('j');
search('ja');
search('jav');
const onScroll = throttle(y => console.log('scroll', y), 1000);
onScroll(1);
onScroll(2);
onScroll(3);This prints scroll 1 at once (calls 2 and 3 fall in the same window), then searching jav 50 ms later.
this, prototypes and classes in JavaScript
How this is bound, how the prototype chain works, and what classes add.
How does this work in JavaScript?
this is decided by how a function is called, not where it is defined. By priority:
new Fn(): the new object.call,applyorbind: the object passed.obj.fn():obj.- a plain
fn():undefinedin strict mode, the global object otherwise.
Arrow functions skip all four and use the surrounding this.
'use strict';
function who() {
return this?.name;
}
const user = { name: 'Ada', who };
class Person {
constructor(name) {
this.name = name;
}
}
console.log(user.who(), who(), who.call({ name: 'Grace' }), new Person('Linus').name);More in the this keyword.
What is the difference between call, apply and bind?
All three set this. call and apply invoke at once, with arguments listed or as an array; bind returns a new function with this and any given arguments fixed.
'use strict';
function introduce(greeting, punct) {
return `${greeting}, I am ${this.name}${punct}`;
}
const ada = { name: 'Ada' };
console.log(introduce.call(ada, 'Hi', '.'));
console.log(introduce.apply(ada, ['Hello', '.']));
const bound = introduce.bind(ada, 'Hey');
console.log(bound('?'));Use bind to pass a method as a callback without losing its object.
What does this print: a method copied into a variable and called?
'use strict';
const user = {
name: 'Ada',
getName() {
return this?.name;
},
};
const getName = user.getName;
console.log(user.getName(), getName(), getName.call(user));Predict the output
this is set at call time. The copied getName() is a plain call, so in strict mode this is undefined; call(user) sets it back. Passing user.getName as a callback hits the same bug; fix it with bind.
What is the prototype chain?
Every object links to a prototype, another object or null. A property the object lacks is looked up along that chain until null, which is how inheritance works.
const animal = {
eats: true,
describe() {
return `${this.name} eats: ${this.eats}`;
},
};
const dog = Object.create(animal);
dog.name = 'Rex';
console.log(dog.describe());
console.log(Object.getPrototypeOf(dog) === animal, Object.hasOwn(dog, 'eats'));
console.log(Object.getPrototypeOf(Object.prototype));Reads go up the chain; an assignment creates an own property unless the chain holds a setter or read-only property of that name. See prototypes.
What is the difference between __proto__ and prototype?
prototype is a property of constructors and classes: what instances made with new inherit from. __proto__ is an accessor on Object.prototype that exposes an object's actual prototype, so in browsers and Node.js instance.__proto__ === Constructor.prototype.
function User(name) {
this.name = name;
}
const u = new User('Ada');
console.log(Object.getPrototypeOf(u) === User.prototype, User.prototype.constructor === User);
console.log(typeof User.prototype, u.prototype);__proto__ is deprecated; use Object.getPrototypeOf and Object.setPrototypeOf.
Are JavaScript classes different from constructor functions?
Mostly no: a class is syntax over constructor functions and prototypes, and typeof a class is 'function'. The differences are that class bodies are strict, methods are non-enumerable, classes need new, and declarations sit in the temporal dead zone.
class Shape {
constructor(name) {
this.name = name;
}
area() {
return 0;
}
toString() {
return `${this.name} with area ${this.area()}`;
}
}
class Circle extends Shape {
constructor(r) {
super('circle');
this.r = r;
}
area() {
return Math.round(Math.PI * this.r ** 2);
}
}
console.log(String(new Circle(2)));
console.log(typeof Shape, Object.getPrototypeOf(Circle.prototype) === Shape.prototype);In a subclass, call super() before touching this.
What does the new keyword do? Can you implement it?
new Fn(args) creates an empty object, sets its prototype to Fn.prototype, calls Fn with this bound to it, and returns it, unless Fn returns an object of its own.
function myNew(Ctor, ...args) {
const obj = Object.create(Ctor.prototype);
const result = Ctor.apply(obj, args);
return result !== null && (typeof result === 'object' || typeof result === 'function')
? result
: obj;
}
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return 'hi ' + this.name;
};
const u = myNew(User, 'Ada');
console.log(u.greet(), u instanceof User);Get the last step right: a returned primitive is ignored, a returned object replaces the instance.
How do you make private properties in JavaScript?
Use private fields, written with #. Only code inside the class body can reach them; outside access is a syntax error.
class Account {
#balance = 0;
deposit(n) {
this.#balance += n;
return this;
}
get balance() {
return this.#balance;
}
}
const acc = new Account().deposit(100);
console.log(acc.balance, Object.keys(acc).length, '#balance' in acc);The field is invisible to Object.keys, JSON.stringify and a string in check, so this prints 100 0 false.
What is the difference between const and Object.freeze?
const stops the variable from being reassigned; Object.freeze makes the object itself read-only, one level deep.
'use strict';
const config = Object.freeze({ port: 3000, db: { host: 'localhost' } });
try {
config.port = 8080;
} catch (e) {
console.log(e.constructor.name);
}
config.db.host = 'remote';
console.log(config.port, config.db.host, Object.isFrozen(config.db));Writing to a frozen property throws in strict mode and is ignored otherwise, and config.db is still writable.
Async JavaScript interview questions: promises and the event loop
Callbacks, promises, async/await and the event loop, including the ordering questions.
What is callback hell and how do you avoid it?
Callback hell is async steps nested inside each other's callbacks, so the code drifts right and error handling repeats at every level.
getUser(id, (err, user) => {
if (err) return handle(err);
getOrders(user, (err, orders) => {
if (err) return handle(err);
getInvoice(orders[0], (err, invoice) => {
if (err) return handle(err);
send(invoice);
});
});
});
Rewrite it with async/await and one try/catch, and run independent steps with Promise.all. More in callbacks.
What is a promise in JavaScript?
A promise stands for a value that will be ready later. It is pending, fulfilled or rejected, and once settled it never changes; read it with .then, .catch or await.
const p = new Promise(resolve => setTimeout(() => resolve('done'), 10));
console.log('pending first');
p.then(v => console.log('then:', v)).finally(() => console.log('settled'));.then returns a new promise, which is what makes chaining work. See promises.
How do async and await work, and how do you handle errors with them?
An async function always returns a promise. await pauses the function until a promise settles, then gives you the value or throws the rejection, so errors use an ordinary try/catch.
function getUser(id) {
return new Promise((resolve, reject) =>
setTimeout(() => (id > 0 ? resolve({ id, name: 'Ada' }) : reject(new Error('bad id'))), 10));
}
async function show(id) {
try {
const user = await getUser(id);
console.log('got', user.name);
} catch (err) {
console.log('failed:', err.message);
} finally {
console.log('done', id);
}
}
show(1).then(() => show(-1));await pauses only the current function, not the thread. See async and await.
Explain the event loop in JavaScript.
JavaScript runs your code on one call stack. When the stack empties, the event loop runs every queued microtask (promise callbacks, code after an await), then the next macrotask (a timer, I/O, a UI event), draining microtasks again after each one.
console.log('script start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
queueMicrotask(() => console.log('microtask'));
console.log('script end');So timeout prints last, after both microtasks. Diagrams in the event loop.
What does this print: a zero-delay timer and a promise callback?
const log = [];
log.push('A');
setTimeout(() => log.push('B'), 0);
Promise.resolve().then(() => log.push('C'));
log.push('D');
setTimeout(() => console.log(log.join(' ')), 10);Predict the output
Synchronous code runs first, pushing A and D. Then the microtask queue drains (C), and only then does the timer run (B). Microtasks always run before the next macrotask, even with a 0 delay.
What is the difference between Promise.all, allSettled, race and any?
all fulfills when every promise does and rejects on the first rejection; allSettled waits for all and never rejects; race settles like the first one to settle; any fulfills with the first success and rejects with an AggregateError only when all reject.
const ok = (v, ms) => new Promise(r => setTimeout(() => r(v), ms));
const fail = (msg, ms) => new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms));
async function main() {
console.log('all:', (await Promise.all([ok('a', 20), ok('b', 10)])).join(','));
try {
await Promise.all([ok('a', 20), fail('boom', 10)]);
} catch (e) {
console.log('all rejected:', e.message);
}
const settled = await Promise.allSettled([ok('a', 20), fail('boom', 10)]);
console.log('allSettled:', settled.map(r => r.status).join(','));
console.log('race:', await Promise.race([ok('slow', 30), ok('fast', 10)]));
console.log('any:', await Promise.any([fail('x', 5), ok('first ok', 20)]));
}
main();Typical uses: all when every result is needed, race for timeouts.
What is the difference between awaiting in sequence and in parallel?
Two awaits in a row run one after the other, so the times add. Starting both and awaiting Promise.all runs them together, so you wait only for the slowest.
const wait = ms => new Promise(r => setTimeout(r, ms));
const elapsed = t => Math.round((Date.now() - t) / 100) * 100;
async function main() {
let t = Date.now();
await wait(100);
await wait(100);
console.log('sequential ~', elapsed(t), 'ms');
t = Date.now();
await Promise.all([wait(100), wait(100)]);
console.log('parallel ~', elapsed(t), 'ms');
}
main();A common bug is array.forEach(async ...), which waits for nothing; use for...of or Promise.all(array.map(...)).
How do you convert a callback function into one that returns a promise?
Wrap the call in new Promise and resolve or reject from the callback. A generic promisify does this for any function with a Node-style (err, result) callback.
function promisify(fn) {
return (...args) =>
new Promise((resolve, reject) =>
fn(...args, (err, result) => (err ? reject(err) : resolve(result))));
}
function divideLater(a, b, cb) {
setTimeout(() => (b === 0 ? cb(new Error('divide by zero')) : cb(null, a / b)), 10);
}
const divide = promisify(divideLater);
divide(10, 2).then(r => console.log('result', r));
divide(1, 0).catch(e => console.log('error', e.message));Node.js ships the helper as util.promisify.
What happens to a promise rejection that nobody handles?
If a rejected promise has no handler when the microtask queue drains, the runtime reports an unhandled rejection: browsers log it and fire unhandledrejection, and Node.js 15 and later crash the process by default unless an unhandledRejection handler is set (see --unhandled-rejections).
Common causes are a promise started with no await or .catch, async callbacks passed to forEach, and return promise inside try without await, which that catch never sees. Fix it where the promise is created.
Can microtasks block the event loop?
Yes. The loop drains every microtask, including ones queued during the drain, before the next macrotask, so an endless chain starves timers, I/O and rendering.
let ticks = 0;
setTimeout(() => console.log('timer ran after', ticks, 'microtasks'), 0);
function spin() {
if (++ticks < 100000) queueMicrotask(spin);
}
spin();Split long work with setTimeout (or setImmediate in Node.js) between chunks, or move it to a worker.
What does this print: code before and after await null in an async function?
const log = [];
async function f() {
log.push('f1');
await null;
log.push('f2');
}
f();
log.push('main');
setTimeout(() => console.log(log.join(' ')), 0);Predict the output
An async function runs synchronously until its first await, so f1 goes in first. await null schedules the rest as a microtask and returns to the caller, which pushes main. Then f2 runs, before the timer prints.
How do you cancel a fetch request or other async work?
Pass an AbortController's signal to fetch and call controller.abort(); the fetch rejects with an AbortError. A promise cannot be cancelled, but the signal stops the work behind it.
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('/api/search?q=js', { signal: controller.signal });
console.log(await res.json());
} catch (err) {
if (err.name === 'AbortError') console.log('cancelled');
else throw err;
} finally {
clearTimeout(timer);
}
AbortSignal.timeout(5000) replaces the manual timer; its error is a TimeoutError, as MDN's AbortSignal.timeout page shows.
JavaScript interview questions for experienced developers
For 3 to 10 years of experience: memory, modules and the less common built-ins.
What causes memory leaks in JavaScript applications?
A leak is memory you no longer need that is still reachable, so the collector cannot free it. Usual sources: listeners and setInterval callbacks never removed, caches that only grow (cap them or use a WeakMap), closures holding a large object, and references to removed DOM nodes.
To find one, compare heap snapshots in Chrome DevTools taken before and after repeating an action.
How does garbage collection work in JavaScript?
Engines free memory by reachability: from the roots (globals, the call stack, live closures) the collector marks everything it can reach and frees the rest. Because it is mark and sweep, unreachable cycles are freed too.
V8 also collects new objects often and cheaply, and moves survivors to an old generation checked less often.
What is a WeakMap and when would you use one?
A WeakMap holds its keys weakly: when nothing else references a key, the entry can be collected. Keys must be objects (or, since ES2023, symbols not created with Symbol.for), and there is no size or iteration.
const meta = new WeakMap();
function track(obj) {
meta.set(obj, { seenAt: 0 });
}
let user = { name: 'Ada' };
track(user);
console.log(meta.has(user), meta.get(user).seenAt);
console.log(typeof meta.size, typeof meta.forEach);
user = null; // nothing else holds the object, so its entry can be collectedUse it to attach data to objects you do not own, such as DOM nodes; MDN's WeakMap page lists which keys are allowed.
What are generators and iterators?
A generator function (function*) returns an iterator: each yield pauses and hands out a value, and next() resumes. An iterator is anything whose next() returns { value, done }; an iterable has a [Symbol.iterator]() that returns one.
function* ids(start = 1) {
let id = start;
while (true) {
const reset = yield id++;
if (reset) id = start;
}
}
const gen = ids();
console.log(gen.next().value, gen.next().value, gen.next(true).value, gen.next().value);
const range = {
from: 1,
to: 4,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) yield i;
},
};
console.log([...range].join(','));The value passed to next(true) becomes the result of the paused yield, which resets the count. See iterators and generators.
What is the difference between ES modules and CommonJS?
CommonJS (require, module.exports) is Node.js's original system and loads modules synchronously at runtime. ES modules (import, export) are the standard: static, resolved before the code runs, and supported in browsers.
// CommonJS
const { readFile } = require('fs');
module.exports = { parse };
// ES modules
import { readFile } from 'node:fs/promises';
export function parse(text) { /* ... */ }
const lib = await import('./heavy.js'); // dynamic import, loads on demand
ES modules also export live bindings and allow tree shaking and top-level await. Since Node.js 20.19 and 22.12, require() can load an ES module that has no top-level await (see the Node.js docs on require(esm)). More in CommonJS vs ES modules.
What are Proxy and Reflect used for?
A Proxy wraps an object and intercepts operations on it (get, set, delete, in) with handler functions called traps. Reflect has one method per trap that does the default, so a trap can add logic and forward.
const user = { name: 'Ada', age: 36 };
const safe = new Proxy(user, {
get(target, key) {
return key in target ? Reflect.get(target, key) : `no field "${String(key)}"`;
},
set(target, key, value) {
if (key === 'age' && !Number.isInteger(value)) throw new TypeError('age must be an integer');
return Reflect.set(target, key, value);
},
});
console.log(safe.name, safe.email);
try {
safe.age = 'old';
} catch (e) {
console.log(e.message);
}Common uses are validation, defaults and reactivity (Vue 3). Proxies slow every access, so keep them off hot paths.
What is type coercion in JavaScript?
Coercion is JavaScript converting a value to another type on its own. + concatenates if either side is, or converts to, a string; -, *, / and unary + convert to numbers.
console.log('5' + 3, '5' - 3, '5' * '2');
console.log(true + 1, JSON.stringify([] + []), [1, 2] + [3]);
console.log(+'', +' 42 ', +'4px');Arrays first become their items joined with commas, so [1, 2] + [3] is '1,23'. More in type coercion.
Why do you need BigInt in JavaScript?
Numbers are 64-bit floats, so integers are exact only up to Number.MAX_SAFE_INTEGER (2^53 minus 1); past it, different integers can compare equal. BigInt (an n suffix) stores any integer exactly.
console.log(Number.MAX_SAFE_INTEGER);
console.log(9007199254740992 === 9007199254740993);
const big = 9007199254740993n;
console.log(big + 1n, typeof big);
try {
console.log(big + 1);
} catch (e) {
console.log(e.constructor.name);
}Mixing BigInt and number in arithmetic throws a TypeError, and JSON.stringify cannot serialize one, which is why APIs send 64-bit IDs as strings.
What is a Symbol in JavaScript?
A Symbol is a primitive that is always unique, used as an object key that cannot collide with another. Object.keys, for...in and JSON.stringify skip symbol keys.
const id = Symbol('id');
const user = { name: 'Ada', [id]: 42 };
console.log(user[id], Object.keys(user).join(','), JSON.stringify(user));
console.log(Symbol('id') === Symbol('id'), Symbol.for('id') === Symbol.for('id'));Symbol.for returns the same symbol for the same name from a global registry. Built-ins such as Symbol.iterator are the language's hooks.
What is the difference between a shallow copy and a deep copy?
A shallow copy is a new top-level object that shares nested objects with the original; a deep copy copies every level. Spread and Object.assign are shallow, structuredClone is deep.
const original = { name: 'Ada', skills: ['js'], joined: new Date(0) };
const shallow = { ...original };
const deep = structuredClone(original);
shallow.skills.push('sql');
console.log(original.skills.join(','));
console.log(deep.skills.join(','), deep.joined instanceof Date);
console.log(typeof JSON.parse(JSON.stringify(original)).joined);The old JSON.parse(JSON.stringify(x)) trick turns dates into strings and throws on cycles; structuredClone handles both but throws on functions.
JavaScript coding interview questions
Problems you write live, plus the utilities (Promise.all, bind, curry, deep clone) senior rounds ask you to implement.
How do you reverse a string in JavaScript?
Strings have no reverse, so spread into an array, reverse and join: [...s].reverse().join('').
const reverse = s => [...s].reverse().join('');
console.log(reverse('coddy'));
function reverseLoop(s) {
let out = '';
for (let i = s.length - 1; i >= 0; i--) out += s[i];
return out;
}
console.log(reverseLoop('hello'));Spread splits by code point, so most emoji survive where split('') and the index loop break them; emoji made of several code points need Intl.Segmenter.
How do you check whether a string is a palindrome?
Lowercase the string, keep only letters and digits, then compare characters from both ends with two pointers moving inward, stopping at the first mismatch.
function isPalindrome(s) {
const clean = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let i = 0;
let j = clean.length - 1;
while (i < j) {
if (clean[i++] !== clean[j--]) return false;
}
return true;
}
console.log(isPalindrome('A man, a plan, a canal: Panama'), isPalindrome('coddy'));Two pointers use O(1) extra space, unlike comparing with a reversed copy. Ask about case and punctuation first.
Write FizzBuzz in JavaScript.
For 1 to n, print Fizz for multiples of 3, Buzz for multiples of 5, FizzBuzz for both, otherwise the number. Test 15 first, or the 3 branch catches it.
const out = [];
for (let i = 1; i <= 15; i++) {
if (i % 15 === 0) out.push('FizzBuzz');
else if (i % 3 === 0) out.push('Fizz');
else if (i % 5 === 0) out.push('Buzz');
else out.push(i);
}
console.log(out.join(' '));Building the word, (i % 3 ? '' : 'Fizz') + (i % 5 ? '' : 'Buzz') || i, makes a third rule easy to add.
How do you count how many times each character appears in a string?
Loop once and count each character in an object or Map; first-unique, most-common and anagram questions then read from that table.
function charCount(s) {
const counts = {};
for (const ch of s) counts[ch] = (counts[ch] || 0) + 1;
return counts;
}
const word = 'loveleetcode';
const counts = charCount(word);
console.log(JSON.stringify(counts));
console.log([...word].findIndex(ch => counts[ch] === 1));The last line prints 2, the index of v, in O(n) instead of a nested loop's O(n^2).
Solve Two Sum in JavaScript: find two indices whose values add up to a target.
Walk the array once, storing each value's index in a Map, and check whether target - number is already there. O(n) time and space instead of O(n^2) nested loops.
function twoSum(nums, target) {
const seen = new Map();
for (let i = 0; i < nums.length; i++) {
const need = target - nums[i];
if (seen.has(need)) return [seen.get(need), i];
seen.set(nums[i], i);
}
return [];
}
console.log(twoSum([2, 7, 11, 15], 9).join(','), twoSum([3, 2, 4], 6).join(','));Checking before inserting stops a number from pairing with itself. On sorted input, two pointers need only O(1) space.
How do you flatten a nested array without flat()?
Built in: arr.flat(Infinity). By hand, recurse: append each item, or its flattened contents when it is an array.
const nested = [1, [2, [3, [4]], 5]];
console.log(nested.flat(Infinity).join(','));
function flatten(arr) {
const out = [];
for (const x of arr) {
if (Array.isArray(x)) out.push(...flatten(x));
else out.push(x);
}
return out;
}
console.log(flatten(nested).join(','));Both print 1,2,3,4,5. Follow-up: how do you survive very deep input? Use an explicit stack instead of recursion. See the recursion visualization.
How do you check whether brackets in a string are balanced?
Use a stack: push opening brackets, and on a closing bracket pop and check it matches. The string is valid if every pop matched and the stack ends empty.
function isValid(s) {
const pairs = { ')': '(', ']': '[', '}': '{' };
const stack = [];
for (const ch of s) {
if (ch in pairs) {
if (stack.pop() !== pairs[ch]) return false;
} else {
stack.push(ch);
}
}
return stack.length === 0;
}
console.log(isValid('({[]})'), isValid('(]'), isValid('(('));'((' is the case people miss: nothing mismatched, but brackets left open. See the stack visualization.
How do you group anagrams together?
Anagrams have the same sorted letters, so use the sorted word as a Map key and push each word into its group.
function groupAnagrams(words) {
const groups = new Map();
for (const w of words) {
const key = [...w].sort().join('');
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(w);
}
return [...groups.values()];
}
console.log(JSON.stringify(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])));Sorting costs O(k log k) per word of length k; for lowercase letters, a key of 26 counts makes it O(k).
Find the maximum sum of a contiguous subarray (Kadane's algorithm).
Kadane's algorithm: keep the best sum of a subarray ending at the current index, either extending the previous one or starting fresh, and track the best seen. O(n) time, O(1) space.
function maxSubArray(nums) {
let best = nums[0];
let cur = nums[0];
for (let i = 1; i < nums.length; i++) {
cur = Math.max(nums[i], cur + nums[i]);
best = Math.max(best, cur);
}
return best;
}
console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]));
console.log(maxSubArray([-3, -1, -2]));Starting best at nums[0], not 0, keeps an all-negative array correct.
Implement Promise.all from scratch.
Return a new promise that stores each result at its input index, resolves when the pending count reaches zero, and rejects on the first rejection. Wrap inputs in Promise.resolve so plain values work.
function promiseAll(items) {
return new Promise((resolve, reject) => {
const results = [];
let pending = items.length;
if (pending === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then(value => {
results[i] = value;
if (--pending === 0) resolve(results);
}, reject);
});
});
}
const delay = (v, ms) => new Promise(r => setTimeout(() => r(v), ms));
promiseAll([delay('a', 30), 'b', delay('c', 10)]).then(r => console.log(r.join(',')));
promiseAll([delay('a', 10), Promise.reject(new Error('nope'))]).catch(e => console.log('rejected:', e.message));Results keep input order even though c finished first, and an empty array must resolve at once or the promise hangs.
Write a polyfill for Function.prototype.bind.
Return a function that calls the original with the fixed this and the preset arguments followed by the new ones. A closure keeps all three.
Function.prototype.myBind = function (ctx, ...preset) {
const fn = this;
return function (...args) {
return fn.apply(ctx, [...preset, ...args]);
};
};
function greet(greeting, mark) {
return `${greeting} ${this.name}${mark}`;
}
const hi = greet.myBind({ name: 'Ada' }, 'Hi');
console.log(hi('.'));Follow-up: what about new? The real bind ignores the bound this under new, so check new.target and call new fn(...) in that case.
Write a deep clone function that handles nested objects and circular references.
Recurse into objects and arrays, return primitives as they are, and keep a WeakMap from each original to its copy; returning the stored copy when an object comes back handles cycles.
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== 'object') return value;
if (seen.has(value)) return seen.get(value);
if (value instanceof Date) return new Date(value);
const copy = Array.isArray(value) ? [] : {};
seen.set(value, copy);
for (const key of Object.keys(value)) copy[key] = deepClone(value[key], seen);
return copy;
}
const a = { n: 1, list: [1, { x: 2 }], when: new Date(0) };
a.self = a;
const b = deepClone(a);
b.list[1].x = 99;
console.log(a.list[1].x, b.self === b, b.when instanceof Date);It skips maps, sets, prototypes and symbol keys; in real code structuredClone covers most of that.
Implement a curry function.
Currying turns f(a, b, c) into f(a)(b)(c). Collect arguments in a closure until there are fn.length of them, then call fn.
const curry = fn =>
function curried(...args) {
return args.length >= fn.length ? fn(...args) : (...more) => curried(...args, ...more);
};
const volume = (l, w, h) => l * w * h;
const cv = curry(volume);
console.log(cv(2)(3)(4), cv(2, 3)(4), cv(2)(3, 4));Name the limit: fn.length stops at the first parameter with a default and never counts a rest parameter, so for those functions this curry calls too early or never collects.
Output-based JavaScript interview questions
Predict the output of short snippets, then pick an answer to see why.
What does this print: 0.1 + 0.2 compared with 0.3?
console.log(0.1 + 0.2, 0.1 + 0.2 === 0.3);Predict the output
0.1 and 0.2 have no exact binary floating point form, so their sum lands slightly above 0.3. Compare with a tolerance sized for your values (Number.EPSILON suits only numbers near 1) or use integers, such as cents.
What does this print: typeof on null and undefined?
console.log(typeof null, typeof undefined);Predict the output
typeof null is 'object', a bug from the first version of JavaScript kept for compatibility. Test value === null for null, and value == null for null or undefined.
What does this print: + and - between a string and a number?
console.log('5' + 3, '5' - 3);Predict the output
+ with a string on either side concatenates, so '5' + 3 is '53'. - only works on numbers, so '5' is converted and the result is 2.
What does this print: an empty array plus an empty object?
console.log([] + {});Predict the output
+ converts both to primitives: [] becomes '' and {} becomes '[object Object]', which are concatenated. {} + [] at the start of a statement differs: {} parses as a block, leaving +[], which is 0.
What does this print: map(parseInt) on an array of strings?
console.log(['1', '2', '3'].map(parseInt).join(' '));Predict the output
map passes (value, index), and parseInt reads the index as a radix: radix 0 means the default (10 here), radix 1 is invalid, and 3 is not a binary digit. Use .map(Number).
What does this print: typeof on two functions before their lines?
console.log(typeof foo, typeof bar);
function foo() {}
var bar = function () {};Predict the output
A function declaration is hoisted with its body, so foo is already a function. bar is a var, hoisted as undefined until the assignment runs; calling it on the first line would throw a TypeError.
What does this print: sort() on numbers with no comparator?
console.log([10, 9, 1, 2].sort().join(','));Predict the output
Without a comparator, sort compares items as strings in UTF-16 order, so '10' comes before '2'. Pass (a, b) => a - b for numbers. sort changes the array in place; toSorted (ES2023) returns a copy.
What does this print: Object.keys on mixed integer and string keys?
const o = { b: 1, 2: 1, a: 1, 1: 1 };
console.log(Object.keys(o).join(','));Predict the output
Array-index keys such as '1' come first in ascending order, then other string keys in insertion order (the spec's OrdinaryOwnPropertyKeys), so 1,2 lead and b stays before a. Use a Map to keep insertion order for every key.
What does this print: reading a name an inner block declares later with let?
let x = 1;
{
try {
console.log(x);
} catch (e) {
console.log(e.name);
}
let x = 2;
}Predict the output
The inner let x is hoisted to the top of its block, so x there means the inner variable, which is still in the temporal dead zone. It does not fall back to the outer x.
What does this print: chained comparisons 1 < 2 < 3 and 3 > 2 > 1?
console.log(1 < 2 < 3, 3 > 2 > 1);Predict the output
Comparisons run left to right and return booleans. true < 3 becomes 1 < 3, which is true; true > 1 becomes 1 > 1, which is false. Write ranges as a < b && b < c.
What does this print: four ways to compare NaN?
console.log(NaN === NaN, Object.is(NaN, NaN), [NaN].includes(NaN), [NaN].indexOf(NaN));Predict the output
=== never treats NaN as equal, and indexOf uses ===, so it returns -1. Object.is (SameValue) and includes (SameValueZero) both match NaN.
What does this print: two counters made by the same factory?
function counter() {
let c = 0;
return () => ++c;
}
const a = counter();
const b = counter();
a();
a();
console.log(a(), b());Predict the output
Each counter() call creates its own c. a was called twice, so its third call returns 3; b returns 1 on its first call.
What does this print: changing a nested field after a spread copy?
const a = { n: { v: 1 }, m: 1 };
const b = { ...a };
b.n.v = 2;
b.m = 2;
console.log(a.n.v, a.m);Predict the output
Spread is shallow: b.m is a separate number, but b.n is the same object as a.n, so the change to b.n.v shows in both. Use structuredClone for independent nested data.
What does this print: the result of calling an async function that returns 42?
async function getValue() {
return 42;
}
const result = getValue();
console.log(result instanceof Promise, typeof result);Predict the output
An async function always returns a promise, here wrapping 42, and typeof a promise is 'object'. Get the number with await getValue() or .then.
What does this print: delete on an array, then a write past its end?
const arr = [1, 2, 3];
delete arr[1];
arr[5] = 6;
console.log(arr.length, arr.filter(() => true).length);Predict the output
delete leaves a hole and keeps the length at 3; writing index 5 makes it 6 with more holes. filter skips holes, so it sees three values. Remove items with splice, never delete.
Preparing for the interview
How do I prepare for a JavaScript interview?
this, prototypes and the event loop, each explained in two sentences with a short example. Then implement debounce, Promise.all, deep clone and bind, and solve array and string problems out loud on a timer.What JavaScript topics are asked to freshers?
var, let and const, data types, == versus ===, truthy and falsy values, array methods, arrow functions, closures and basic promises, plus a small coding task such as FizzBuzz and a few predict-the-output snippets.What is asked in a JavaScript interview for 3 to 5 years of experience?
this, prototypes, memory leaks, modules), plus implementing something like a promise utility or a cache and explaining its trade-offs.