← Back to DevBytes

Top 50 JavaScript Interview Questions for Senior Developers

Top 50 JavaScript Interview Questions for Senior Developers

Preparing for a senior JavaScript interview requires more than memorizing syntax. Interviewers expect deep understanding of the language's internals, design patterns, performance implications, and real-world problem-solving ability. This tutorial covers the top 50 questions that frequently appear in senior-level JavaScript interviews, complete with practical code examples and best practices.

Why This Matters

Senior developers are evaluated on their ability to reason about edge cases, debug complex issues, and write maintainable code. Understanding these concepts deeply helps you architect better applications, mentor junior developers, and pass technical interviews with confidence.

Core JavaScript Concepts

1. What is the difference between let, const, and var?

var is function-scoped and hoisted with an initial value of undefined. let and const are block-scoped and hoisted but remain in the temporal dead zone until declared. const prevents reassignment but does not make objects immutable.

var x = 1;
let y = 2;
const z = 3;

const obj = { a: 1 };
obj.a = 2; // allowed
// obj = {}; // TypeError

2. What is hoisting?

Hoisting moves declarations to the top of their scope during compilation. Function declarations are fully hoisted, while var variables are hoisted with undefined. let and const are hoisted but inaccessible before declaration.

console.log(foo()); // works
function foo() { return 'hoisted'; }

console.log(bar); // undefined
var bar = 5;

console.log(baz); // ReferenceError
let baz = 5;

3. Explain the temporal dead zone (TDZ)

The TDZ is the period between entering a scope and the actual declaration of a let or const variable. Accessing the variable during this period throws a ReferenceError.

{
  console.log(x); // ReferenceError
  let x = 10;
}

4. What is the difference between == and ===?

== performs type coercion before comparison, while === checks both type and value. Always prefer === to avoid unexpected behavior.

0 == false;   // true
0 === false;  // false
null == undefined;  // true
null === undefined; // false

5. What is type coercion?

Type coercion is the automatic conversion of values from one type to another. JavaScript performs implicit coercion in operations involving different types.

console.log(1 + '2');     // '12'
console.log('5' - 2);     // 3
console.log(true + 1);    // 2
console.log([] + {});     // '[object Object]'

6. What is the difference between null and undefined?

undefined means a variable has been declared but not assigned a value. null is an intentional absence of value, typically assigned by the developer.

let a;
console.log(a);        // undefined
console.log(typeof a); // 'undefined'

let b = null;
console.log(typeof b); // 'object' (known bug)

7. What are primitive vs reference types?

Primitives (string, number, boolean, null, undefined, symbol, bigint) are copied by value. Reference types (objects, arrays, functions) are copied by reference.

let a = 5;
let b = a;
b = 10;
console.log(a); // 5

let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 2;
console.log(obj1.x); // 2

8. What is the typeof operator and its quirks?

typeof returns a string indicating the type. It has quirks: typeof null returns 'object', and typeof [] returns 'object'.

typeof null;       // 'object'
typeof [];         // 'object'
typeof function(){}; // 'function'
typeof NaN;        // 'number'

9. What is instanceof and how does it work?

instanceof checks the prototype chain of an object against a constructor's prototype.

class Animal {}
class Dog extends Animal {}

const d = new Dog();
console.log(d instanceof Dog);   // true
console.log(d instanceof Animal); // true

10. Explain truthy and falsy values

Falsy values: false, 0, '', null, undefined, NaN, 0n. Everything else is truthy, including empty objects and arrays.

if ([]) console.log('truthy');    // runs
if ({}) console.log('truthy');    // runs
if ('') console.log('falsy');     // does not run

11. What is short-circuit evaluation?

Logical operators && and || return the value of one of their operands based on truthiness, enabling concise conditional logic.

const name = user?.name || 'Guest';
const result = condition && doSomething();
const value = a ?? b; // nullish coalescing

12. What is the difference between ?? and ||?

|| returns the right operand for any falsy value. ?? only returns the right operand for null or undefined.

0 || 'default';  // 'default'
0 ?? 'default';  // 0
'' || 'default'; // 'default'
'' ?? 'default'; // ''

13. What is optional chaining?

Optional chaining (?.) safely accesses nested properties without throwing errors if intermediate values are null or undefined.

const city = user?.address?.city ?? 'Unknown';
const name = user?.getName?.();

14. What are template literals and tagged templates?

Template literals allow string interpolation and multi-line strings. Tagged templates let you process template literals with a function.

const name = 'World';
console.log(`Hello, ${name}!`);

function tag(strings, ...values) {
  return strings[0] + values[0].toUpperCase();
}
console.log(tag`Hi ${'there'}`); // 'Hi THERE'

15. What is destructuring assignment?

Destructuring unpacks values from arrays or properties from objects into distinct variables.

const { name, age = 25 } = person;
const [first, , third] = [1, 2, 3];

function getUser({ name }) { return name; }

Functions and Closures

16. What is a closure?

A closure is a function that retains access to its lexical scope even when executed outside that scope. Closures enable data encapsulation and factory functions.

function counter() {
  let count = 0;
  return function() {
    return ++count;
  };
}
const inc = counter();
console.log(inc()); // 1
console.log(inc()); // 2

17. What is the difference between function declarations and expressions?

Function declarations are hoisted entirely, while expressions are not. Arrow functions are always expressions.

hoisted(); // works
function hoisted() {}

notHoisted(); // TypeError
const notHoisted = function() {};

18. What are arrow functions and how do they differ from regular functions?

Arrow functions do not have their own this, arguments, super, or new.target. They cannot be used as constructors.

const obj = {
  value: 42,
  regular: function() { return this.value; },
  arrow: () => this.value
};
console.log(obj.regular()); // 42
console.log(obj.arrow());   // undefined

19. Explain the this keyword

this refers to the execution context. In regular functions, it depends on how the function is called. In arrow functions, it is inherited from the enclosing scope.

const obj = {
  name: 'Alice',
  greet() { console.log(this.name); },
  delayed() {
    setTimeout(() => console.log(this.name), 100);
  }
};

20. What are call, apply, and bind?

call and apply invoke a function with a specified this and arguments. bind returns a new function with a permanently bound this.

function greet(greeting) {
  return `${greeting}, ${this.name}`;
}
const user = { name: 'Bob' };
greet.call(user, 'Hi');
greet.apply(user, ['Hi']);
const bound = greet.bind(user);
bound('Hi');

21. What is a higher-order function?

A higher-order function takes a function as an argument or returns a function. Common examples include map, filter, reduce, and custom decorators.

const multiply = (x) => (y) => x * y;
const double = multiply(2);
console.log(double(5)); // 10

22. What is currying?

Currying transforms a function that takes multiple arguments into a sequence of functions that each take one argument.

const curry = (fn) => (a) => (b) => (c) => fn(a, b, c);
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6

23. What is the difference between arguments and rest parameters?

arguments is an array-like object available in regular functions. Rest parameters (...args) are real arrays and work in arrow functions.

function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

24. What is an IIFE and why use it?

An Immediately Invoked Function Expression runs immediately after definition. It was historically used to create private scopes before modules.

(function() {
  const private = 'hidden';
  console.log(private);
})();

25. What is memoization?

Memoization caches function results based on inputs to avoid recomputation, improving performance for expensive operations.

function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

Asynchronous JavaScript

26. What is the event loop?

The event loop processes the call stack, then the microtask queue (Promises), then the macrotask queue (setTimeout, I/O). This enables non-blocking concurrency in single-threaded JavaScript.

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2

27. What are microtasks vs macrotasks?

Microtasks (Promise callbacks, queueMicrotask) run after the current task and before rendering. Macrotasks (setTimeout, setInterval, I/O) run after microtasks are drained.

28. What is a Promise?

A Promise represents the eventual result of an asynchronous operation. It has three states: pending, fulfilled, or rejected.

const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve('done'), 100);
});
p.then(result => console.log(result))
 .catch(err => console.error(err))
 .finally(() => console.log('settled'));

29. What is async/await?

async/await is syntactic sugar over Promises, allowing asynchronous code to look synchronous and improving readability.

async function fetchData(url) {
  try {
    const res = await fetch(url);
    const data = await res.json();
    return data;
  } catch (err) {
    console.error('Fetch failed:', err);
  }
}

30. How do you handle multiple Promises in parallel?

Use Promise.all for all-or-nothing, Promise.allSettled for all results, Promise.race for the first settled, and Promise.any for the first fulfilled.

const [a, b] = await Promise.all([fetchA(), fetchB()]);
const results = await Promise.allSettled([fetchA(), fetchB()]);

31. What is Promise chaining?

Promise chaining sequences asynchronous operations by returning a new Promise from each .then() callback.

fetch(url)
  .then(res => res.json())
  .then(data => process(data))
  .then(result => save(result));

32. How do you create a custom Promise-based delay?

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
await delay(2000);
console.log('after 2 seconds');

33. What is the difference between setTimeout and setInterval?

setTimeout executes a callback once after a delay. setInterval executes repeatedly at intervals. Use clearTimeout and clearInterval to cancel them.

const id = setInterval(() => console.log('tick'), 1000);
clearInterval(id);

34. What is requestAnimationFrame?

requestAnimationFrame schedules a callback before the next repaint, optimizing visual animations and reducing wasted frames.

function animate() {
  element.style.left = `${pos++}px`;
  if (pos < 200) requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

35. How do you cancel an async operation?

Use AbortController to cancel fetch requests and other async operations.

const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort(); // cancels the request

Objects, Prototypes, and Classes

36. What is the prototype chain?

Every object has a prototype. When accessing a property, JavaScript traverses the prototype chain until it finds the property or reaches null.

const animal = { eats: true };
const dog = Object.create(animal);
dog.barks = true;
console.log(dog.eats); // true (from prototype)

37. What is prototypal inheritance vs classical inheritance?

JavaScript uses prototypal inheritance, where objects inherit directly from other objects. Classical inheritance (classes) is syntactic sugar over this mechanism.

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
  speak() { return `${this.name} barks`; }
}

38. What is the difference between Object.create and new?

Object.create(proto) creates an object with a specified prototype. new invokes a constructor and sets up the prototype chain automatically.

const proto = { greet() { return 'hi'; } };
const obj = Object.create(proto);
console.log(obj.greet()); // 'hi'

39. What are static methods and properties?

Static members belong to the class itself, not instances. They are useful for utility functions and factory methods.

class MathUtil {
  static square(x) { return x * x; }
}
console.log(MathUtil.square(5)); // 25

40. What are getters and setters?

Getters and setters define object accessors, allowing controlled access to properties with validation or computed values.

class Temperature {
  #celsius = 0;
  get fahrenheit() { return this.#celsius * 9/5 + 32; }
  set fahrenheit(f) { this.#celsius = (f - 32) * 5/9; }
}

41. What are private class fields?

Private fields (prefixed with #) are truly private and cannot be accessed outside the class, unlike convention-based underscore prefixes.

class BankAccount {
  #balance = 0;
  deposit(amount) { this.#balance += amount; }
  get balance() { return this.#balance; }
}

42. What is the difference between Object.assign and spread?

Both perform shallow copies. The spread operator is more concise and is the modern preferred approach.

const merged = Object.assign({}, a, b);
const merged2 = { ...a, ...b };

Arrays, Iteration, and Data Structures

43. What is the difference between map, filter, and reduce?

map transforms each element, filter selects elements matching a condition, and reduce accumulates elements into a single value.

const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((acc, n) => acc + n, 0);

44. What is the difference between forEach and map?

forEach executes a callback for each element and returns undefined. map returns a new array with transformed values.

45. What are iterables and iterators?

Iterables implement the Symbol.iterator method, returning an iterator with a next() method. This enables for...of loops and the spread operator.

const range = {
  from: 1, to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      }
    };
  }
};
console.log([...range]); // [1, 2, 3]

46. What are generators?

Generators are functions that can pause and resume execution using yield. They return an iterator and are useful for lazy evaluation.

function* idGenerator() {
  let id = 1;
  while (true) yield id++;
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Performance, Best Practices, and Advanced Topics

47. What is debouncing and throttling?

Debouncing delays execution until a pause in events. Throttling limits execution to once per interval. Both optimize performance for frequent events.

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function throttle(fn, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

48. What is a memory leak and how do you prevent it?

Memory leaks occur when references prevent garbage collection. Common causes include forgotten timers, event listeners, closures, and detached DOM nodes.

// Bad: listener never removed
element.addEventListener('click', handler);

// Good: cleanup
element.addEventListener('click', handler);
// later...
element.removeEventListener('click', handler);

49. What are WeakMap and WeakSet?

WeakMap and WeakSet hold weak references to keys, allowing garbage collection when no other references exist. Keys must be objects.

const cache = new WeakMap();
function compute(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const result = expensiveOp(obj);
  cache.set(obj, result);
  return result;
}

50. What are JavaScript modules and what is tree shaking?

ES modules (import/export) enable static analysis, allowing bundlers to perform tree shaking—removing unused exports from the final bundle. Always use named exports for better tree-shaking support.

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// app.js
import { add } from './math.js'; // subtract is tree-shaken
console.log(add(2, 3));

Best Practices Summary

Conclusion

Mastering these 50 JavaScript interview questions equips you with the foundational and advanced knowledge expected of senior developers. The key is not just knowing the answers, but understanding the underlying mechanics—how the event loop works, why closures matter, when to use specific patterns, and how to write performant, maintainable code. Practice implementing these concepts in real projects, review the code examples until they feel natural, and be prepared to explain your reasoning during interviews. Senior roles reward depth over breadth, so focus on truly understanding each topic rather than surface-level memorization. With consistent practice and a curious mindset, you will be well-prepared to tackle even the most challenging JavaScript interviews.

— Ad —

Google AdSense will appear here after approval

← Back to all articles