JavaScript Interview Questions and Answers
The JavaScript questions asked in fresher and junior developer interviews, from closures and this to promises and the event loop. Each answer explains the idea simply, shows code with its real output, and gives you a short answer to say out loud.
Basics: types, scope and hoisting
What are the data types in JavaScript?
JavaScript has seven primitive types and one non-primitive type:
- Primitives:
string,number,bigint,boolean,undefined,nullandsymbol - Non-primitive:
object, which includes arrays, functions, dates and everything else
Primitives are immutable and compared by value. Objects are compared by reference: two objects with the same contents are still different objects.
You can check a type with typeof. Watch for two famous surprises: typeof null is "object", a bug from the first version of JavaScript that was never fixed, and arrays also report "object".
console.log(typeof 42);
console.log(typeof "hi");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null);
console.log(typeof 10n);
console.log(typeof Symbol("id"));
console.log(typeof []);
console.log(typeof function () {});number
string
boolean
undefined
object
bigint
symbol
object
functionTo check for an array, use Array.isArray(value). To check for null, use value === null.
JavaScript has seven primitives (string, number, bigint, boolean, undefined, null and symbol) plus objects. Primitives are compared by value and objects by reference. typeof null returns "object", which is a historical bug, so I check null with === null and arrays with Array.isArray.
Likely follow-up: How would you reliably check if a value is a plain object?
What is the difference between var, let and const?
All three declare variables, but they differ in scope, hoisting and reassignment:
varis function-scoped. It ignores blocks likeifandfor, and it can be redeclared.letis block-scoped and can be reassigned.constis block-scoped and can't be reassigned. It must be given a value straight away.
const makes the binding constant, not the value. You can still change the contents of a const object or array.
function scopes() {
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a);
console.log(typeof b);
console.log(typeof c);
}
scopes();
const user = { name: "Priya" };
user.name = "Rahul";
console.log(user.name);1
undefined
undefined
Rahula leaks out of the if block because var only respects function scope. b and c don't exist outside the block.
var is function-scoped and can be redeclared; let and const are block-scoped. const stops reassignment, not mutation. I use const by default, let when the value must change, and avoid var in modern code.
Likely follow-up: Why does const allow you to push into an array?
Functions, closures and this
What is a closure?
A closure is a function together with the variables from the scope where it was created. The function keeps access to those variables even after the outer function has finished running.
Closures are how JavaScript gives you private state: the variable can only be reached through the functions that closed over it.
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
get() {
return count;
},
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.get());
console.log(counter.count);2
undefinedcount lives on after createCounter returns, but nothing outside can read or change it directly. That's why counter.count is undefined.
Closures are everywhere: event handlers, setTimeout callbacks, React hooks, and helpers like debounce and once.
A closure is a function that remembers the variables from where it was defined, even after that outer function returns. I use closures for private state, like a counter or a cache, and they're behind callbacks, debounce and React hooks.
Likely follow-up: Can closures cause memory leaks?
What does this print: closures inside a loop with var and let?
This is one of the most asked output questions. Each setTimeout callback runs after the loop has finished.
- With
var, there is oneifor the whole function. When the callbacks run, it's already 3. - With
let, each loop iteration gets its own new binding ofj, so each callback remembers its own value.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 0);
}var: 3
var: 3
var: 3
let: 0
let: 1
let: 2Before let existed, the fix was an IIFE that captured the value: (function (i) { setTimeout(() => console.log(i)); })(i).
With var, all three callbacks share the same i, which is 3 by the time they run. let creates a new binding for every iteration, so each callback captures 0, 1 and 2. The old fix was wrapping the body in an IIFE.
Likely follow-up: How would you fix the var version without using let?
Objects, prototypes and classes
What is prototypal inheritance?
Every JavaScript object has a hidden link to another object, its prototype. When you read a property that the object doesn't have, JavaScript looks it up on the prototype, then that object's prototype, and so on. This is the prototype chain, and it ends at null.
That's how all arrays get map and filter: they live once on Array.prototype, not on every array.
const animal = {
eats: true,
describe() {
return `${this.name} eats: ${this.eats}`;
},
};
const dog = Object.create(animal);
dog.name = "Bruno";
console.log(dog.describe());
console.log(Object.getPrototypeOf(dog) === animal);
console.log(dog.hasOwnProperty("eats"));
console.log("eats" in dog);Bruno eats: true
true
false
truehasOwnProperty only checks the object itself, while in also searches the prototype chain.
Objects inherit from other objects through a prototype link. When a property isn't found on an object, JavaScript walks up the prototype chain until it finds it or reaches null. Classes and built-in methods like Array.prototype.map all work this way.
Likely follow-up: What is the difference between __proto__ and prototype?
How do classes work in JavaScript?
Classes are cleaner syntax over prototypes, not a new inheritance system. class gives you:
- a
constructorfor setting up new objects - methods that are stored on the prototype and shared by all instances
extendsandsuperfor inheritance- getters and setters,
staticmembers, and truly private fields with#
class Employee {
#salary;
constructor(name, salary) {
this.name = name;
this.#salary = salary;
}
describe() {
return `${this.name} works here`;
}
get annualSalary() {
return this.#salary * 12;
}
}
class Manager extends Employee {
constructor(name, salary, teamSize) {
super(name, salary);
this.teamSize = teamSize;
}
describe() {
return `${super.describe()} and leads ${this.teamSize} people`;
}
}
const manager = new Manager("Neha", 100000, 6);
console.log(manager.describe());
console.log(manager.annualSalary);
console.log(typeof Manager);
console.log(Object.getPrototypeOf(Manager.prototype) === Employee.prototype);Neha works here and leads 6 people
1200000
function
truetypeof Manager is "function", which shows a class is still a constructor function underneath. In a subclass, you must call super() before using this.
Classes are syntax over prototypes. Methods live on the prototype, extends sets up the prototype chain, and super calls the parent. Fields starting with # are truly private. A class is still a function underneath.
Likely follow-up: What happens if you use this before calling super() in a subclass?
Promises, async/await and the event loop
What is the event loop?
JavaScript runs on a single thread, so it can only do one thing at a time. The event loop is how it still handles timers, network requests and clicks without freezing:
- The call stack runs your synchronous code.
- Slow work (timers, fetch, file reads) is handed to the browser or Node.js, which runs it in the background.
- When that work finishes, its callback waits in a queue.
- Whenever the call stack is empty, the event loop moves the next callback onto the stack.
There are two queues. Microtasks (promise callbacks, queueMicrotask) run as soon as the current code finishes, before anything else. Macrotasks (setTimeout, I/O, events) run one per loop turn.
console.log("1: start");
setTimeout(() => console.log("4: timeout"), 0);
Promise.resolve().then(() => console.log("3: promise"));
console.log("2: end");1: start
2: end
3: promise
4: timeoutEven with a delay of 0, the timeout runs last, because promise callbacks are microtasks and always go first.
JavaScript is single-threaded. Synchronous code runs on the call stack, async work is handed off, and its callbacks wait in queues. When the stack is empty, the event loop runs all microtasks, like promise callbacks, before the next macrotask, like setTimeout.
Likely follow-up: What happens to the page if a single task runs for five seconds?
What is a Promise?
A Promise is an object representing a value that will be available later. It's always in one of three states:
- pending: still waiting
- fulfilled: finished successfully, with a value
- rejected: failed, with a reason (usually an
Error)
Once a promise is fulfilled or rejected it is settled, and it can never change again. You react to it with .then() for success, .catch() for errors and .finally() for code that runs either way.
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) resolve({ id, name: "Asha" });
else reject(new Error("Invalid id"));
}, 100);
});
}
fetchUser(1)
.then((user) => console.log("Got", user.name))
.catch((error) => console.log("Failed:", error.message));
fetchUser(-1)
.then((user) => console.log("Got", user.name))
.catch((error) => console.log("Failed:", error.message))
.finally(() => console.log("Done"));Got Asha
Failed: Invalid id
DonePromises replaced deeply nested callbacks ("callback hell"): .then() returns a new promise, so steps can be chained in a flat line.
A promise represents a future result and is pending, fulfilled or rejected; once settled it never changes. I handle success with then, errors with catch and clean-up with finally, and because then returns a new promise, steps chain without nesting.
Likely follow-up: What happens if you forget to return a promise inside .then()?
ES6+ features interviewers ask about
What is the difference between spread and rest?
Both use ..., but they do opposite jobs:
- Spread expands an array or object into individual elements: in function calls, array literals and object literals.
- Rest collects several elements into one array or object: in function parameters and destructuring.
A simple way to tell them apart: spread is on the right side, giving values out; rest is on the left, gathering values in.
function total(...prices) {
return prices.reduce((sum, price) => sum + price, 0);
}
const cart = [299, 149];
console.log(total(...cart, 199));
const [first, ...others] = ["js", "react", "sql"];
console.log(first, others.length);
const defaults = { theme: "dark", lang: "en" };
console.log(JSON.stringify({ ...defaults, lang: "hi" }));647
js 2
{"theme":"dark","lang":"hi"}With object spread, later properties win, which makes it perfect for "defaults plus overrides".
Spread expands an iterable or object into separate values, for copying or passing arguments. Rest gathers the remaining values into an array or object, in function parameters or destructuring. Same syntax, opposite direction.
Likely follow-up: Does object spread make a deep copy?
How does destructuring work?
Destructuring pulls values out of arrays or objects into variables in one step. It supports:
- renaming:
{ city: location } - default values:
{ error = "none" }, used when the value isundefined - nesting: reaching into objects inside objects
- swapping variables without a temporary one
const response = { data: { user: { name: "Asha", city: "Pune" } }, status: 200 };
const {
data: {
user: { name, city: location },
},
status,
error = "none",
} = response;
console.log(name, location, status, error);
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);
function greet({ name = "Guest", greeting = "Hello" } = {}) {
return `${greeting}, ${name}`;
}
console.log(greet());
console.log(greet({ name: "Ravi" }));Asha Pune 200 none
2 1
Hello, Guest
Hello, RaviThe = {} in greet matters: without it, calling greet() with no argument would throw, because you can't destructure undefined.
Destructuring unpacks values from arrays or objects into variables, with support for renaming, defaults and nesting. I use it for function parameters with defaults, for React props, and for swapping values with [a, b] = [b, a].
Likely follow-up: Do default values apply when a property is null?
Coding problems with solutions
Reverse a string.
The shortest answer converts the string to an array, reverses it and joins it back. Interviewers often then ask you to do it without built-in helpers, so know the loop version too.
const reverse = (text) => [...text].reverse().join("");
function reverseWithLoop(text) {
let result = "";
for (let i = text.length - 1; i >= 0; i--) {
result += text[i];
}
return result;
}
console.log(reverse("CareerKit"));
console.log(reverseWithLoop("hello"));
console.log(reverse("नमस्ते").length === "नमस्ते".length);tiKreeraC
olleh
trueUsing spread ([...text]) instead of text.split("") splits by Unicode characters, so emoji aren't broken in half. Time complexity is O(n) for both versions.
I spread the string into an array of characters, reverse it and join it, which is O(n). Without built-ins, I loop from the last index to the first and build a new string. I use spread rather than split("") so emoji and other multi-unit characters stay intact.
Likely follow-up: How would you reverse the order of words in a sentence instead?
Check whether a string is a palindrome.
A palindrome reads the same backwards. A robust answer first normalises the input (lowercase, keep only letters and digits), then compares characters from both ends with two pointers, which avoids creating a reversed copy.
function isPalindrome(text) {
const clean = text.toLowerCase().replace(/[^a-z0-9]/g, "");
let left = 0;
let right = clean.length - 1;
while (left < right) {
if (clean[left] !== clean[right]) return false;
left++;
right--;
}
return true;
}
console.log(isPalindrome("madam"));
console.log(isPalindrome("A man, a plan, a canal: Panama"));
console.log(isPalindrome("CareerKit"));
console.log(isPalindrome(""));true
true
false
trueTime O(n), extra space O(n) for the cleaned string (O(1) if you skip invalid characters with the pointers instead).
I normalise the string to lowercase letters and digits, then move two pointers inward from both ends, returning false at the first mismatch. It's O(n) time and avoids building a reversed copy.
Likely follow-up: How would you check if a number is a palindrome without converting it to a string?
All 150 questions in the ebook
Basics: types, scope and hoisting
- What are the data types in JavaScript?
- What is the difference between var, let and const?
- What is hoisting?
- What is the difference between == and ===?
- What is the difference between null and undefined?
- What are truthy and falsy values?
- How does type coercion work in JavaScript?
- What is scope in JavaScript?
- What is lexical scope and the scope chain?
- What is the difference between primitive and reference values?
- What is NaN and how do you check for it?
- Why is 0.1 + 0.2 not equal to 0.3?
- What is the difference between typeof and instanceof?
- What is strict mode?
- What are template literals?
- What do && and || return?
- What is variable shadowing?
- What is the global object?
- What is the difference between slice, substring and splice?
- How do you convert a string to a number?
- How do you reliably check the type of a value?
- Are strings mutable in JavaScript?
- What is an IIFE?
- Is JavaScript pass by value or pass by reference?
- What is the difference between undefined and "not defined"?
Functions, closures and this
- What is a closure?
- What does this print: closures inside a loop with var and let?
- How does the this keyword work?
- What is the difference between arrow functions and regular functions?
- What are call, apply and bind?
- What is a higher-order function?
- How do you implement debounce?
- What is currying?
- What is the difference between a function declaration and a function expression?
- What does "functions are first-class citizens" mean?
- What is a callback function?
- What is a pure function?
- How do default parameters work?
- What is the difference between the arguments object and rest parameters?
- What is recursion?
- What is memoization? Implement it.
- How do you implement throttle?
- How do you implement a once() function?
- What is function composition?
- What are generator functions?
- Why does this get lost in callbacks, and how do you fix it?
- What does the new keyword do?
- Write a polyfill for Array.prototype.map.
- Write a polyfill for Function.prototype.bind.
- Why does this arrow function return undefined instead of an object?
Objects, prototypes and classes
- What is prototypal inheritance?
- How do classes work in JavaScript?
- What is the difference between a shallow copy and a deep copy?
- What is the difference between Object.freeze and const?
- How do you loop over an object's keys and values?
- What are the different ways to create an object?
- What is the difference between __proto__ and prototype?
- Why put methods on the prototype instead of inside the constructor?
- What are getters and setters?
- What are property descriptors and Object.defineProperty?
- What are shorthand properties and computed property names?
- What is the difference between Object.assign and spread for merging objects?
- What are static methods and properties?
- How does instanceof work? Implement it.
- What is the difference between for...in and Object.keys?
- How do you compare two objects for deep equality?
- What is the difference between Object.preventExtensions, seal and freeze?
- What are Symbols, and why use them as object keys?
- What are the pitfalls of JSON.stringify?
- How does method chaining work?
- What are mixins?
- Why is Object.hasOwn safer than hasOwnProperty?
- Composition or inheritance: which do you prefer?
- How do you make an object iterable?
- What is a WeakMap and when would you use it?
Promises, async/await and the event loop
- What is the event loop?
- What is a Promise?
- How does async/await work?
- What is the difference between Promise.all, allSettled, race and any?
- What does this print: microtasks versus macrotasks?
- What is callback hell, and how do promises fix it?
- How does promise chaining pass values along?
- How do errors flow through a promise chain?
- What goes wrong if you forget to return a promise inside .then()?
- What is the difference between sequential and parallel awaits?
- Why doesn't await work inside forEach?
- What is for await...of?
- How do you convert a callback function to return a promise?
- Implement Promise.all.
- How would you retry a failing request with exponential backoff?
- How do you add a timeout to a promise?
- What is the order of process.nextTick, promises and setTimeout in Node.js?
- What is an unhandled promise rejection?
- What does an async function return?
- Can a promise be resolved or rejected more than once?
- Which parts of asynchronous JavaScript run outside the JavaScript engine?
- How do you cancel an async operation with AbortController?
- Implement a simple event emitter.
- What is the difference between setInterval and a recursive setTimeout?
- How do you run async tasks with a concurrency limit?
ES6+ features interviewers ask about
- What is the difference between spread and rest?
- How does destructuring work?
- What are optional chaining (?.) and nullish coalescing (??)?
- What is the difference between map() and forEach()?
- What are Set and Map, and when would you use them?
- What is the difference between ES modules and CommonJS?
- What is dynamic import()?
- What are tagged templates?
- What do Array.from and Array.of do?
- What do flat() and flatMap() do?
- What does Object.fromEntries do?
- What is the difference between includes() and indexOf()?
- What do padStart, padEnd, repeat and replaceAll do?
- What are optional catch binding and Error cause?
- What is BigInt?
- What are numeric separators and the exponentiation operator?
- What are the logical assignment operators?
- What does Array.prototype.at() do?
- What does Object.groupBy do?
- What are Proxy and Reflect?
- What are class fields and static initialisation blocks?
- What are private methods, and what does #field in obj check?
- What are toSorted, toReversed, toSpliced and with?
- What do findLast and findLastIndex do?
- What are the new Set methods like union and intersection?
Coding problems with solutions
- Reverse a string.
- Check whether a string is a palindrome.
- Find the second largest number in an array.
- Remove duplicates from an array.
- Count how often each character appears in a string.
- Write FizzBuzz.
- Flatten a nested array without using flat().
- Split an array into chunks of a given size.
- Check whether two strings are anagrams.
- Capitalise the first letter of every word.
- Find the missing number in an array of 1 to n.
- Two Sum: find two numbers that add up to a target.
- Implement a deep clone function.
- Group an array of objects by a property.
- Write a polyfill for Array.prototype.reduce.
- Run async tasks one after another with a delay between them.
- Flatten a nested object into dot-notation keys.
- Find the first non-repeating character in a string.
- Merge two sorted arrays into one sorted array.
- Rotate an array to the right by k steps.
- Count the vowels in a string.
- Print the first n Fibonacci numbers.
- Check whether brackets in a string are balanced.
- Implement an LRU cache.
- Format a number in the Indian system (lakhs and crores).