JavaScript Interview Questions and Answers

These are the JavaScript questions you'll encounter in technical interviews, each with a detailed written answer. Master the fundamentals, then work through intermediate and advanced challenges.

40 questions with answersLast updated: Sep 21, 2026
Start a mock interview

What is JavaScript?

JavaScript is a versatile, dynamically typed language that powers interactive web experiences. Originally created for browsers, it now runs on servers via Node.js and has become the backbone of modern web development.

JavaScript interviews test your understanding of core concepts like closures, the event loop, prototypes, and asynchronous programming. You'll be asked about promises, async/await, and how the JavaScript engine works under the hood.

Master these fundamentals and you'll be ready for frontend, backend, and full-stack roles. JavaScript proficiency is essential for any modern web developer.

Reading answers is not the same as giving them. Sit a full mock interview and get scored on how you actually explain them.

Try a mock interview

JavaScript Interview Questions and Answers

1Multiple choice

How would you describe JavaScript and where does it run?

Answer

JavaScript is a lightweight, dynamically-typed language originally for browsers, now running on servers via Node.js, making it versatile for full-stack development.

2Multiple choice

Explain the Document Object Model and its role in web interaction

Answer

The DOM represents the HTML document as a tree. JavaScript accesses it via document object to read/modify elements, attributes, and content dynamically.

3Multiple choice

What is a closure and how does variable scope create them?

Answer

When a function is defined inside another, it captures the outer scope's variables. Those variables remain accessible even after the outer function completes.

4Multiple choice

Explain JavaScript hoisting and how it affects function and variable declarations

Answer

Hoisting moves declarations to the top during compilation. var and function declarations are fully hoisted; let/const are hoisted but not initialized (temporal dead zone).

5Multiple choice

How do let and const differ from var in scope and reassignment?

Answer

var has function scope. let/const have block scope (if, loop). const prevents reassignment but allows mutation of objects/arrays.

6Multiple choice

What is callback hell and how do Promises and async/await solve it?

Answer

Deeply nested callbacks become hard to read. Promises chain with .then(); async/await makes asynchronous code look synchronous, improving readability.

7Multiple choice

Explain how the event loop manages asynchronous operations in JavaScript

Answer

The event loop checks if the call stack is empty, then processes microtasks (Promises) before macrotasks (setTimeout), enabling asynchronous I/O without threads.

8Multiple choice

How does prototypal inheritance differ from classical inheritance?

Answer

Objects inherit from other objects via the prototype chain. ES6 classes are syntactic sugar over prototypes, making inheritance feel more familiar to classical programmers.

9Multiple choice

What does this refer to and how does its binding work in different contexts?

Answer

In methods, this is the calling object. In functions, it's the global object (or undefined in strict mode). Arrow functions inherit this from enclosing scope. .call, .apply, .bind can override it.

10Multiple choice

How does destructuring simplify extracting values from objects and arrays?

Answer

Destructuring unpacks values: const {name, age} = person extracts properties. Array destructuring: const [first, ...rest] = array. Makes code more readable.

11Written answer

Describe JavaScript's characteristics, execution model, and dynamic typing

Answer

JavaScript is dynamically-typed, meaning types are determined at runtime. Variables can hold any type and change types. It's interpreted, executing line-by-line in the browser or Node.js. JavaScript is single-threaded but handles asynchronous I/O through callbacks, Promises, and async/await, making it responsive without multi-threading. It's prototype-based, allowing flexible object creation and inheritance. Modern JavaScript (ES6+) adds classes, modules, arrow functions, and const/let for better code organization. Its flexibility enables rapid development but requires discipline to avoid type-related bugs.

12Written answer

Explain how the DOM represents HTML as a tree and how JavaScript manipulates it

Answer

The browser parses HTML into a tree where each element is a node. The document object provides access to this tree. JavaScript can query elements: document.getElementById, querySelector. It reads/writes properties: element.textContent, element.style.color. It modifies the tree: appendChild, removeChild, innerHTML. Event listeners attach to elements: element.addEventListener('click', handler). Changes trigger reflows (recalculating layout) and repaints (redrawing). DOM manipulation is reactive: changing properties immediately affects the rendered page. Performance-conscious code batches DOM changes to minimize reflows.

13Written answer

Explain how functions capture variables from outer scopes and retain them

Answer

When a function is created, it captures variables from its enclosing scope. Even after the outer function returns, the inner function retains access through the closure. Example: function outer() { const x = 5; return function inner() { return x; } } The returned function remembers x. This enables data privacy: x is inaccessible except through inner(). Closures power callbacks and event handlers. The captured variables persist in memory as long as the closure exists, potentially causing memory leaks if not cleaned up.

14Written answer

Describe how hoisting works differently for var, let, const, functions, and classes

Answer

During compilation, declarations are moved to the top of their scope. var declarations are hoisted and initialized as undefined, so accessing before the line where it's declared returns undefined (not an error). Function declarations are fully hoisted; they're callable before the declaration. let/const are hoisted but not initialized, creating a temporal dead zone: accessing before declaration throws ReferenceError. Arrow functions are treated like variables (hoisted if const, temporal dead zone). Classes are hoisted but behave like let/const regarding the temporal dead zone. Understanding hoisting prevents unexpected undefined values.

15Written answer

Contrast var's function scope with let/const's block scope and reassignment rules

Answer

var is function-scoped: var x = 1; inside an if block is accessible outside the block. let/const are block-scoped: let x = 1; inside {} is inaccessible outside. var can be redeclared and reassigned. let can be reassigned but not redeclared in the same scope. const cannot be reassigned or redeclared. const prevents reassignment but allows mutation: const obj = {}; obj.prop = 1 is valid. Modern code prefers const (immutability) and let (clear scope), avoiding var.

16Written answer

Explain how Promises and async/await provide better control flow than callbacks

Answer

Callbacks passed to functions execute when the operation completes. Nested callbacks (callback hell) become hard to read and error-prone. Promises represent eventual completion/failure, returning a Promise object allowing chaining: promise.then(success).catch(error). async/await builds on Promises, making code look synchronous: const result = await operation(); Code flows top-to-bottom, easier to follow. Error handling uses try-catch, not nested callbacks. async/await handles sequences naturally: result1 = await op1(); result2 = await op2(result1). Readability improves, reducing bugs.

17Written answer

Describe the event loop, call stack, task queue, and microtask queue mechanics

Answer

The call stack executes synchronous code. Asynchronous operations (setTimeout, fetch, event handlers) add tasks to the task queue (macrotasks); Promises add to the microtask queue. The event loop runs: (1) Execute all call stack code, (2) Execute all microtasks, (3) Execute one macrotask, (4) Repeat. Microtasks run before the next macrotask, so Promises resolve before setTimeout callbacks. This single-threaded model with queuing enables handling I/O without threads. Understanding the order prevents timing bugs and performance issues.

18Written answer

Explain how objects inherit from prototypes and how to set up the chain

Answer

Every object has a prototype, accessed via __proto__ or Object.getPrototypeOf(). When a property is not found on the object, JavaScript searches up the prototype chain. Setting obj.__proto__ = baseObj chains them. Function constructors: function Base() {}; function Child() {}; Child.prototype = Object.create(Base.prototype); sets up inheritance. ES6 classes: class Child extends Base {} is syntactic sugar over prototypes. The new operator creates an object with the constructor's prototype. Understanding the chain enables writing reusable hierarchies.

19Written answer

Discuss how this binding works in different contexts and how to control it

Answer

In methods, this is the calling object: obj.method() has this as obj. In functions, this is the global object (or undefined in strict mode). Arrow functions inherit this from the enclosing scope, ignoring the call context. .call(obj, args) executes a function with this as obj. .apply(obj, [args]) is like .call but takes an array. .bind(obj) returns a new function with this bound to obj. .bind is useful for event handlers and callbacks where context is lost. Understanding binding prevents the common bug of losing this in callbacks.

20Written answer

Explain array and object destructuring syntax and their practical uses

Answer

Object destructuring: const {x, y} = obj extracts x and y properties. Nested: const {outer: {inner}} = obj. Renamed: const {x: newX} = obj. Defaults: const {x = 0} = obj. Array destructuring: const [a, b, c] = arr. Skip: const [a, , c] = arr skips b. Rest: const [first, ...rest] = arr. Function parameters: function({x, y}) {} destructures the argument. Destructuring reduces boilerplate: accessing properties happens automatically. It's especially useful in function parameters and when extracting specific values from complex objects, improving code readability.

21Spoken answer

Walk through how you would fetch data from an API and handle errors using async/await

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I used async/await with try-catch: try { const response = await fetch(url); const data = await response.json(); } catch (error) { console.error(error); }. I checked response.ok before parsing. For multiple requests, I used Promise.all: const [data1, data2] = await Promise.all([fetch1(), fetch2()]); Concurrent requests were faster than sequential.

22Spoken answer

Describe a situation where you discovered a this binding issue and how you resolved it

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

A callback lost this context when passed as an event handler. Inside the callback, this was undefined instead of the object. I fixed it by using arrow functions: element.addEventListener('click', () => this.method()); or by binding: element.addEventListener('click', this.method.bind(this)); Arrow functions inherit this from the enclosing scope.

23Spoken answer

Explain how you would implement a closure to create private variables in a module

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I created a module exporting functions that shared private state: const counter = (() => { let count = 0; return { increment: () => ++count, get: () => count }; })(); count is inaccessible outside; only increment() and get() expose controlled access. This pattern protects internal state from external modification.

24Spoken answer

Talk about a time you debugged callback hell and refactored to Promises or async/await

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

Legacy code had deeply nested callbacks handling sequential API calls. Refactoring to async/await flattened the structure: const user = await fetchUser(); const posts = await fetchPosts(user.id); Code was immediately easier to follow. Error handling with try-catch replaced nested .catch() handlers.

25Spoken answer

Describe how you would set up event listeners on dynamically created DOM elements

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I queried parent elements, attaching event listeners there instead of on individual items: parent.addEventListener('click', (e) => { if (e.target.matches('.item')) { handleItemClick(e); } }); Event delegation handles dynamically created elements without reattaching listeners for each new item.

26Spoken answer

Explain how you would use destructuring to improve code readability in a real project

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

Instead of accessing object properties repeatedly in function parameters, I destructured: function displayUser({ name, email, avatar }) { instead of function displayUser(user) { const name = user.name; const email = user.email; ... }. Defaults reduced null checks: { name = 'Guest' } = {}.

27Spoken answer

Walk through how you would implement memoization using closures and objects

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I implemented memoization for expensive computations: const memoize = (fn) => { const cache = {}; return (arg) => { if (!(arg in cache)) cache[arg] = fn(arg); return cache[arg]; }; }. Calling memoize(expensiveFunction) returned a cached version. Repeated calls with the same argument returned cached results instantly.

28Spoken answer

Talk about understanding and preventing memory leaks from retained closures

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

A closure captured a large dataset in a loop, preventing garbage collection. Each closure retained the entire dataset. I fixed it by limiting captured variables or using weak references. I ensured event listeners and closures were cleaned up when no longer needed: element.removeEventListener or assigning null to references.

29Spoken answer

Describe how you would implement the module pattern to organize related functions

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I organized related functions in a module: const userModule = (() => { const users = []; return { add: (u) => users.push(u), getAll: () => [...users] }; })(); The module had private state (users) and public API (add, getAll). This pattern is cleaner than global functions.

30Spoken answer

Explain how you would use prototypal inheritance to create reusable component classes

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I created a Shape base: function Shape(x, y) { this.x = x; this.y = y; }. Circle inherited: function Circle(x, y, r) { Shape.call(this, x, y); this.r = r; }; Circle.prototype = Object.create(Shape.prototype); Circle.prototype.constructor = Circle;. ES6 classes were simpler: class Circle extends Shape { constructor(x, y, r) { super(x, y); this.r = r; } }.

31Coding

Write an async function that fetches JSON data from an API and displays it on the page

// Fetch and Display Data
// Implement the solution here

Answer

async function fetchAndDisplay(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error('Network response was not ok');
    const data = await response.json();
    const container = document.getElementById('data-container');
    container.innerHTML = JSON.stringify(data, null, 2);
  } catch (error) {
    console.error('Error:', error);
  }
}
32Coding

Implement event delegation to handle clicks on dynamically created list items

// Event Delegation Handler
// Implement the solution here

Answer

const list = document.getElementById('list');
list.addEventListener('click', (event) => {
  if (event.target.tagName === 'LI') {
    console.log('Clicked item:', event.target.textContent);
    event.target.classList.toggle('selected');
  }
});
33Coding

Create a counter function using closures to maintain private state

// Closure Counter
// Implement the solution here

Answer

function createCounter() {
  let count = 0;
  return {
    increment: function() { return ++count; },
    decrement: function() { return --count; },
    getCount: function() { return count; }
  };
}
const counter = createCounter();
34Coding

Chain multiple Promises to handle sequential API calls

// Promise Chain
// Implement the solution here

Answer

fetch('/user')
  .then(response => response.json())
  .then(user => fetch(`/posts/${user.id}`))
  .then(response => response.json())
  .then(posts => console.log(posts))
  .catch(error => console.error(error));
35Coding

Select DOM elements and modify their content, attributes, and styles

// DOM Query and Manipulation
// Implement the solution here

Answer

const heading = document.querySelector('h1');
heading.textContent = 'New Title';
heading.style.color = 'blue';
heading.setAttribute('data-id', '123');
const items = document.querySelectorAll('.item');
items.forEach(item => item.classList.add('active'));
36Coding

Use array methods like map, filter, reduce with callback functions

// Array Methods with Callbacks
// Implement the solution here

Answer

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

Implement a debounce function to limit how often a function executes

// Debounce Function
// Implement the solution here

Answer

function debounce(fn, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}
const debouncedSearch = debounce((query) => fetch(`/search?q=${query}`), 300);
38Coding

Use destructuring to extract and work with object properties

// Object Destructuring
// Implement the solution here

Answer

const user = { name: 'Alice', age: 30, email: 'alice@example.com' };
const { name, age, email = 'unknown' } = user;
const [first, ...rest] = [1, 2, 3, 4];
function greet({ name, age }) { console.log(`Hello ${name}, age ${age}`); }
39Coding

Implement the module pattern with private and public methods

// Module Pattern
// Implement the solution here

Answer

const userModule = (() => {
  const users = [];
  return {
    addUser: (user) => { users.push(user); },
    getUsers: () => [...users],
    getUserById: (id) => users.find(u => u.id === id)
  };
})();
userModule.addUser({ id: 1, name: 'Alice' });
40Coding

Set up prototype-based inheritance between constructor functions

// Prototype Inheritance
// Implement the solution here

Answer

function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { console.log(this.name + ' speaks'); };
function Dog(name, breed) { Animal.call(this, name); this.breed = breed; }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const dog = new Dog('Rex', 'Labrador');