Menu
CoddyTech

JavaScript Interview Questions and Answers

JavaScript questions from var versus let to closures, this, promises and the event loop. Runnable examples were run before publishing; keep the JavaScript cheat sheet open for syntax.

89 questions20 output quizzesRunnable code checked on JavaScript (V8, ES2024)By Kevin Spektor, Co-founder & CTO

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?

Fresherbasics

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?

Freshervariablesscope

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.

JavaScript
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?

Fresherdata types

There are eight: seven primitives (number, string, boolean, undefined, null, bigint, symbol) and object. Arrays, functions and dates are objects.

JavaScript
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?

Fresheroperatorscoercion

=== compares value and type with no conversion; == converts the operands to a common type first.

JavaScript
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?

Fresherdata types

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.

JavaScript
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?

Fresherdata typescoercion

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.

JavaScript
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?

Fresherfunctionsthis

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.

JavaScript
'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?

Freshersyntax

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.

JavaScript
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?

Freshersyntax

Destructuring unpacks array items or object properties into variables in one statement, with renaming, defaults and nesting.

JavaScript
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?

Fresherarrays

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.

JavaScript
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?

Fresherfunctions

A higher-order function takes a function as an argument, returns one, or both.

JavaScript
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?

Fresherloopsarrays

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.

JavaScript
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?

Fresherarrays

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.

JavaScript
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?

Freshernumbers

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.

JavaScript
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?

Experiencedcollections

Use a Map when keys are not known in advance, are not strings, or change often; use a plain object for fixed records.

JavaScript
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?

Freshercollectionsarrays

Use [...new Set(arr)]: a Set keeps unique values in first-appearance order, in O(n).

JavaScript
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 ||?

Fresheroperators

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.

JavaScript
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?

Fresherdata types
JavaScript
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?

Fresherdom

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?

Experiencedbrowser

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?

Experiencedbrowser

All three store strings in the browser, but only cookies go to the server with every request. localStorage lasts until cleared and sessionStorage until the tab closes, each about 5 MB per origin; cookies last until they expire (or the browser session ends), hold about 4 KB each, and HttpOnly hides them from JavaScript.

Keep session tokens in an HttpOnly, Secure, SameSite cookie, not in localStorage, where injected scripts can read them.

What is strict mode in JavaScript?

Experiencedbasics

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
  • this in a plain function call is undefined, 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?

Fresherclosures

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.

JavaScript
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?

Fresherhoisting

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.

JavaScript
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?

Experiencedhoistingscope

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.

JavaScript
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?

Fresherscope

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?

Experiencedclosuresscope
JavaScript
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?

Fresherfunctionsscope

An IIFE (immediately invoked function expression) is defined and called in one expression, giving its variables a private scope.

JavaScript
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?

Experiencedclosuresperformance

Memoization caches results by argument, so a repeated call returns the stored answer. A closure holds the cache.

JavaScript
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?

Experiencedclosuresperformance

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.

JavaScript
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?

Experiencedthis

this is decided by how a function is called, not where it is defined. By priority:

  1. new Fn(): the new object.
  2. call, apply or bind: the object passed.
  3. obj.fn(): obj.
  4. a plain fn(): undefined in strict mode, the global object otherwise.

Arrow functions skip all four and use the surrounding this.

JavaScript
'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?

Experiencedthisfunctions

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.

JavaScript
'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?

Experiencedthis
JavaScript
'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?

Experiencedprototypes

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.

JavaScript
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?

Experiencedprototypes

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.

JavaScript
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?

Experiencedclassesprototypes

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.

JavaScript
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?

Seniorprototypesclasses

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.

JavaScript
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?

Experiencedclasses

Use private fields, written with #. Only code inside the class body can reach them; outside access is a syntax error.

JavaScript
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?

Fresherobjectsvariables

const stops the variable from being reassigned; Object.freeze makes the object itself read-only, one level deep.

JavaScript
'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?

Fresherasync

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?

Fresherasyncpromises

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.

JavaScript
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?

Fresherasyncpromises

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.

JavaScript
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.

Experiencedevent loop

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.

JavaScript
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?

Experiencedevent loop
JavaScript
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?

Seniorpromises

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.

JavaScript
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?

Seniorasyncperformance

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.

JavaScript
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?

Experiencedpromisesasync

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.

JavaScript
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?

Seniorpromiseserrors

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?

Seniorevent loopperformance

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.

JavaScript
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?

Seniorasyncevent loop
JavaScript
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?

Seniorasync

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?

Seniormemoryperformance

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?

Seniormemory

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?

Seniormemorycollections

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.

JavaScript
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 collected

Use 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?

Senioriterators

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.

JavaScript
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?

Seniormodules

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?

Seniorobjects

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.

JavaScript
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?

Experiencedcoercion

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.

JavaScript
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?

Experiencednumbers

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.

JavaScript
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?

Seniordata types

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.

JavaScript
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?

Experiencedobjects

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.

JavaScript
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?

Fresherstringscoding

Strings have no reverse, so spread into an array, reverse and join: [...s].reverse().join('').

JavaScript
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?

Fresherstringscoding

Lowercase the string, keep only letters and digits, then compare characters from both ends with two pointers moving inward, stopping at the first mismatch.

JavaScript
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.

Freshercoding

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.

JavaScript
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?

Fresherstringscoding

Loop once and count each character in an object or Map; first-unique, most-common and anagram questions then read from that table.

JavaScript
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.

Experiencedcodingcollections

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.

JavaScript
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()?

Experiencedarrayscoding

Built in: arr.flat(Infinity). By hand, recurse: append each item, or its flattened contents when it is an array.

JavaScript
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?

Experiencedcodingstacks

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.

JavaScript
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?

Experiencedcodingcollections

Anagrams have the same sorted letters, so use the sorted word as a Map key and push each word into its group.

JavaScript
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).

Experiencedcodingarrays

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.

JavaScript
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.

Seniorpromisescoding

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.

JavaScript
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.

Seniorthiscoding

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.

JavaScript
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.

Seniorobjectscoding

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.

JavaScript
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.

Seniorfunctionscoding

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.

JavaScript
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?

Freshernumbers
JavaScript
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?

Fresherdata types
JavaScript
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?

Freshercoercion
JavaScript
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?

Experiencedcoercion
JavaScript
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?

Experiencedarraysfunctions
JavaScript
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?

Fresherhoisting
JavaScript
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?

Fresherarrays
JavaScript
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?

Seniorobjects
JavaScript
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?

Seniorhoistingscope
JavaScript
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?

Freshercoercionoperators
JavaScript
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?

Experiencednumbers
JavaScript
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?

Fresherclosures
JavaScript
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?

Experiencedobjects
JavaScript
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?

Experiencedasyncpromises
JavaScript
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?

Seniorarrays
JavaScript
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?
Learn what every round checks: coercion, closures, 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?
The same topics one level deeper (microtask ordering, this, prototypes, memory leaks, modules), plus implementing something like a promise utility or a cache and explaining its trade-offs.
Do I need DSA for a JavaScript developer interview?
For most front end and Node.js roles you need arrays, strings, hash maps, stacks, recursion and Big O. Product companies and campus placements often add a medium problem, so practise two pointers, sliding window and hash maps in JavaScript.
How long does it take to prepare for a JavaScript interview?
Two to three weeks of revision if you write JavaScript every day; two to three months from the basics, with the last weeks on interview questions and timed coding.
Coddy programming languages illustration

Learn to code with Coddy

GET STARTED