Master the most frequently asked JavaScript questions in tech interviews — with clear, concise answers you can actually remember.
No questions match your search. Try a different keyword.
JavaScript is a lightweight, interpreted, high-level programming language with these key features:
var, let, and const?
var — function-scoped, hoisted to the top of its function (initialized as undefined), can be re-declared and reassigned.let — block-scoped, hoisted but NOT initialized (temporal dead zone), cannot be re-declared in the same scope, can be reassigned.const — block-scoped, hoisted but NOT initialized, cannot be re-declared or reassigned. Objects/arrays declared with const can still be mutated.var x = 1; let y = 2; const z = 3;
{
var x = 10; // same variable — overwrites outer x
let y = 20; // new block-scoped variable
}
console.log(x); // 10 (var leaks out)
console.log(y); // 2 (let stays block-scoped)
Hoisting is JavaScript's behavior of moving variable and function declarations to the top of their scope before code executes — but only the declaration, not the initialization.
console.log(a); // undefined (not ReferenceError)
var a = 5;
greet(); // works — function declarations are fully hoisted
function greet() { console.log("Hello"); }
console.log(b); // ReferenceError — let/const are hoisted
let b = 10; // but stay in the temporal dead zone
== and ===?
== is the loose equality operator — it performs type coercion before comparing. === is the strict equality operator — it compares both value and type without coercion.
0 == false // true (false coerced to 0) 0 === false // false (different types) "5" == 5 // true (string coerced to number) "5" === 5 // false
Always prefer === to avoid unexpected bugs from type coercion.
null and undefined?
undefined — a variable has been declared but not yet assigned a value. JS assigns it automatically.null — an intentional absence of any value. The developer explicitly sets it to signal "no value here".let a; // undefined — declared, not assigned let b = null; // null — intentionally empty typeof undefined // "undefined" typeof null // "object" (legacy JS quirk) null == undefined // true (loose equality) null === undefined // false (strict equality)
JavaScript has 8 data types:
Primitive (7): string, number, bigint, boolean, undefined, null, symbol
Non-primitive (1): object — includes plain objects, arrays, functions, dates, maps, sets, etc.
Primitives are immutable and stored by value. Objects are stored by reference.
The Document Object Model (DOM) is a programming interface that represents an HTML/XML document as a tree of objects. JavaScript can use the DOM API to read and manipulate page content, structure, and styles.
// Select an element
const btn = document.getElementById('myBtn');
// Modify content
btn.textContent = 'Click me';
// Add an event listener
btn.addEventListener('click', () => alert('Clicked!'));
The DOM is provided by the browser — it is not part of the JavaScript language spec itself.
A closure is a function that retains access to its outer (enclosing) scope even after the outer function has returned. The inner function "closes over" the outer variables.
function makeCounter() {
let count = 0; // outer variable
return function () { // inner function — closure
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2 — count persists!
Common uses: data encapsulation, factory functions, memoization, event handlers that remember state.
Arrow functions are a concise ES6 syntax for writing functions. Key differences:
this — arrow functions inherit this from their enclosing scope (lexical this). Regular functions get their own this depending on how they're called.new keyword.arguments object — use rest parameters instead.// Regular function
const obj = {
name: 'CB',
greet: function() { console.log(this.name); } // 'CB'
};
// Arrow function — this refers to outer scope
const obj2 = {
name: 'CB',
greet: () => console.log(this.name) // undefined
};
this keyword in JavaScript?
this refers to the object that is currently executing the function. Its value depends on how the function is called:
this is the global object (window in browser, global in Node).this is the object before the dot.new) — this is the newly created object.this from the surrounding lexical scope.call / apply / bind — explicitly set this.call, apply, and bind?
All three explicitly set this for a function:
call(thisArg, arg1, arg2, …) — invokes the function immediately, arguments passed individually.apply(thisArg, [arg1, arg2, …]) — invokes the function immediately, arguments passed as an array.bind(thisArg, arg1, …) — returns a new function with this bound permanently. Does NOT call it immediately.function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const person = { name: 'Arjun' };
greet.call(person, 'Hello'); // "Hello, Arjun"
greet.apply(person, ['Hi']); // "Hi, Arjun"
const bound = greet.bind(person, 'Hey');
bound(); // "Hey, Arjun"
A callback is a function passed as an argument to another function, to be executed later — usually after an async operation or event completes.
function fetchData(callback) {
setTimeout(() => {
callback('data received');
}, 1000);
}
fetchData(function(result) {
console.log(result); // "data received" after 1s
});
Overusing callbacks leads to "callback hell" — deeply nested, hard-to-read code. Promises and async/await solve this.
Scope determines where a variable is accessible in your code. JavaScript has three scopes:
let/const inside { }; accessible only within that block.JavaScript also follows the scope chain — when a variable isn't found locally, JS looks up the chain to the enclosing scope.
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It has three states: pending, fulfilled, and rejected.
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Done!'), 1000);
});
promise
.then(result => console.log(result)) // "Done!"
.catch(err => console.error(err))
.finally(() => console.log('Always runs'));
Promises solve callback hell by enabling chaining with .then() and .catch().
async / await.
async/await is syntactic sugar over Promises that makes async code look and behave like synchronous code.
async — marks a function as asynchronous; it always returns a Promise.await — pauses execution inside the async function until the Promise settles.async function fetchUser() {
try {
const res = await fetch('https://api.example.com/user');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('Error:', err);
}
}
await can only be used inside an async function (or at the top level of ES modules).
JavaScript is single-threaded. The event loop is the mechanism that allows JS to perform non-blocking operations by offloading work to the browser/Node APIs and processing callbacks when the call stack is empty.
console.log('A'); // 1st
setTimeout(() => console.log('B'), 0); // 3rd (task queue)
Promise.resolve().then(() => console.log('C')); // 2nd (microtask)
console.log('D'); // 2nd sync
// Output: A, D, C, B
Promise.all() vs Promise.allSettled() vs Promise.race()?
Promise.all(arr) — runs all promises in parallel; resolves when ALL resolve, rejects immediately if ANY rejects.Promise.allSettled(arr) — runs all promises; resolves when ALL settle (fulfilled or rejected), never short-circuits. Returns an array of status objects.Promise.race(arr) — resolves/rejects as soon as the FIRST promise settles.Promise.any(arr) — resolves as soon as the FIRST promise fulfills; rejects only if ALL reject.setTimeout and setInterval?
setTimeout(fn, delay) — executes fn once after delay milliseconds.setInterval(fn, delay) — executes fn repeatedly every delay milliseconds until clearInterval() is called.const id = setInterval(() => console.log('tick'), 1000);
setTimeout(() => clearInterval(id), 5000); // stop after 5s
Note: the actual delay may be slightly longer than specified due to the event loop and minimum timer resolution (4ms in browsers).
Every JavaScript object has a hidden internal link called [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf()). This points to another object called its prototype. When you access a property that doesn't exist on an object, JS looks up the prototype chain until it finds it or reaches null.
const arr = [1, 2, 3]; // arr doesn't have 'map' itself — it finds it on Array.prototype arr.map(x => x * 2); // [2, 4, 6]
JavaScript objects inherit properties and methods directly from other objects through the prototype chain — this is prototypal inheritance.
const animal = { breathes: true };
const dog = Object.create(animal);
dog.bark = function() { return 'Woof!'; };
console.log(dog.breathes); // true — inherited from animal
console.log(dog.bark()); // 'Woof!' — own property
ES6 class syntax is syntactic sugar over this prototype-based system.
ES6 classes are syntactic sugar over constructor functions and prototype-based inheritance. They make OOP more readable but don't change the underlying prototype mechanism.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound.`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks.`; }
}
const d = new Dog('Rex');
d.speak(); // "Rex barks."
d instanceof Animal; // true
Object.create() and the new keyword?
Object.create(proto) — creates a new object with proto as its prototype. No constructor function is called. Useful for pure prototypal inheritance.new Constructor() — creates a new object, sets its prototype to Constructor.prototype, runs the constructor function with this bound to the new object, and returns it.When an event fires on an element, it propagates through the DOM in two phases:
{ capture: true } fire here.addEventListener handlers fire here.// Stop propagation to prevent bubbling further
element.addEventListener('click', (e) => {
e.stopPropagation();
});
Event delegation means attaching a single event listener to a parent element instead of each child — leveraging event bubbling. This is efficient for dynamic lists or many similar elements.
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Clicked:', e.target.textContent);
}
});
Benefits: fewer listeners (better memory), works for dynamically added elements.
e.preventDefault() and e.stopPropagation()?
e.preventDefault() — cancels the default browser action for an event (e.g., stops a form from submitting, stops a link from navigating). The event still propagates.e.stopPropagation() — prevents the event from bubbling/capturing further up or down the DOM. The default action still happens.innerHTML, innerText, and textContent?
innerHTML — gets/sets the HTML markup inside an element (parses HTML tags). Risk: XSS if used with user input.innerText — gets/sets the human-readable rendered text only (respects CSS visibility, triggers reflow).textContent — gets/sets all text including hidden elements; faster than innerText, does not parse HTML.{ ...obj } and Object.assign() are shallow.// Shallow
const a = { x: 1, nested: { y: 2 } };
const b = { ...a };
b.nested.y = 99;
console.log(a.nested.y); // 99 — mutated original!
// Deep copy (simple objects, no functions/dates)
const c = JSON.parse(JSON.stringify(a));
// Or use structuredClone() in modern JS
const d = structuredClone(a);
Memoization is an optimization technique that caches the results of expensive function calls so that when the same inputs occur again, the cached result is returned instead of recomputing.
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(...args);
cache.set(key, result);
return result;
};
}
const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n - 1));
factorial(10); // computed
factorial(10); // returned from cache
Currying transforms a function with multiple arguments into a sequence of functions each taking one argument.
// Normal const add = (a, b) => a + b; add(2, 3); // 5 // Curried const curriedAdd = a => b => a + b; const add5 = curriedAdd(5); add5(3); // 8 (partial application) add5(7); // 12
Useful for creating reusable, partially-applied functions and in functional programming.
Generator functions (declared with function*) return an iterator. They can pause execution with yield and resume later, making them useful for lazy evaluation, async flow control, and infinite sequences.
function* counter() {
let i = 0;
while (true) {
yield i++;
}
}
const gen = counter();
gen.next().value; // 0
gen.next().value; // 1
gen.next().value; // 2 (infinite, no overflow)
WeakMap — a Map where keys must be objects. Entries are weakly held — if the key object is garbage-collected, the entry disappears automatically. Not iterable, no .size.WeakSet — a Set of objects only, weakly held. Useful for tracking objects without preventing their garbage collection.Use them to store metadata about objects (e.g., DOM nodes) without memory leaks.
Symbol is a primitive type introduced in ES6. Every Symbol() call returns a unique, immutable value — even if given the same description. Symbols are often used as unique object property keys that won't clash with other keys or be accidentally iterated.
const id1 = Symbol('id');
const id2 = Symbol('id');
id1 === id2; // false — always unique
const obj = {};
obj[id1] = 123;
// id1 won't appear in for...in or Object.keys()
The Temporal Dead Zone is the period between the start of a block scope and the point where a let or const variable is declared. During the TDZ, any attempt to access the variable throws a ReferenceError.
console.log(x); // ReferenceError — in TDZ let x = 5; console.log(x); // 5 — after declaration
for...of and for...in?
for...in — iterates over enumerable property keys of an object (including inherited ones). Best used for plain objects.for...of — iterates over values of any iterable (arrays, strings, Maps, Sets, generators). Does not work on plain objects.const arr = [10, 20, 30]; for (const i in arr) console.log(i); // "0", "1", "2" (keys) for (const v of arr) console.log(v); // 10, 20, 30 (values)
Destructuring — extract values from arrays/objects into variables.
const [a, b] = [1, 2]; // array
const { name, age = 25 } = { name: 'Arjun' }; // object with default
Spread (...) — expands an iterable into individual elements.
const merged = [...arr1, ...arr2];
const copy = { ...original, newKey: 1 };
Rest (...) — collects remaining elements into an array.
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }
sum(1, 2, 3, 4); // 10
Type coercion is the automatic conversion of a value from one type to another by JavaScript's engine.
1 + "2" → "12" (number to string).Number("5"), String(42), Boolean(0)."5" - 2 // 3 (string coerced to number) "5" + 2 // "52" (number coerced to string) !!"hello" // true (truthy coercion)
Proxy wraps an object and intercepts operations on it (get, set, delete, etc.) via traps. Used for validation, logging, reactive systems (Vue 3 uses it).
const handler = {
set(target, key, value) {
if (typeof value !== 'number') throw new Error('Numbers only!');
target[key] = value;
return true;
}
};
const safe = new Proxy({}, handler);
safe.score = 95; // ok
safe.score = 'A'; // Error!
Reflect provides static methods that mirror Proxy traps, making it easy to perform default operations inside traps.
A Higher-Order Function is a function that either takes one or more functions as arguments, returns a function, or both. They are a pillar of functional programming in JS.
// Takes a function as argument [1, 2, 3].map(x => x * 2); // [2, 4, 6] [1, 2, 3].filter(x => x > 1); // [2, 3] [1, 2, 3].reduce((acc, x) => acc + x, 0); // 6 // Returns a function const multiply = factor => num => num * factor; const double = multiply(2); double(5); // 10
IIFE (Immediately Invoked Function Expression)?
An IIFE is a function that is defined and immediately executed. It creates its own scope, preventing variable leakage into the global scope.
(function () {
const secret = 42;
console.log('Runs immediately!');
})();
// Arrow IIFE
(() => console.log('Also an IIFE'))();
Common before ES6 modules for encapsulation. Less common now with block scoping and modules.
Map and a plain object?
Map accepts any value (objects, functions, primitives) as keys; plain objects only accept strings/symbols.Map preserves insertion order; plain object order is mostly insertion-order for strings but not guaranteed for integer keys.Map has a .size property; plain objects need Object.keys(obj).length.Map is directly iterable with for...of.Map is faster for frequent add/remove operations.Object.freeze() vs Object.seal()?
Object.freeze(obj) — prevents adding, removing, or modifying any properties. The object becomes completely immutable (shallow).Object.seal(obj) — prevents adding or removing properties, but existing properties can still be modified (if writable).const obj = Object.freeze({ x: 1 });
obj.x = 99; // silently fails (throws in strict mode)
obj.x; // still 1
const obj2 = Object.seal({ y: 1 });
obj2.y = 99; // works — modification allowed
obj2.z = 3; // silently fails — no new properties
// Debounce
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Throttle
function throttle(fn, limit) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last >= limit) { last = now; fn(...args); }
};
}
// Sync — blocks
const data = fs.readFileSync('file.txt'); // waits here
// Async — non-blocking
fs.readFile('file.txt', (err, data) => {
console.log(data); // runs later
});
console.log('This runs immediately');
localStorage vs sessionStorage vs cookies?
localStorage — stores data with no expiry. Persists across browser sessions. ~5–10 MB. Only accessible by the same origin. Not sent to server.sessionStorage — stores data for the duration of the browser session (cleared when tab closes). Same-origin, not sent to server.HttpOnly (inaccessible to JS) and Secure.?.) and nullish coalescing (??)?
Optional chaining ?. — safely accesses nested properties. If any part of the chain is null or undefined, the expression short-circuits and returns undefined instead of throwing.
const city = user?.address?.city; // undefined if address is null
Nullish coalescing ?? — returns the right-hand side only if the left-hand side is null or undefined (not falsy like 0 or "").
const name = user.name ?? 'Anonymous'; // 'Anonymous' only if null/undefined const count = 0 ?? 10; // 0 (0 is not null/undefined) const count2 = 0 || 10; // 10 (0 is falsy — || catches all falsy)
import / export)?
ES6 modules allow splitting code into reusable files. Each module has its own scope.
// math.js — named exports
export const PI = 3.14;
export function add(a, b) { return a + b; }
// utils.js — default export
export default function log(msg) { console.log(msg); }
// main.js — importing
import { PI, add } from './math.js';
import log from './utils.js'; // default import (any name)
Modules are always in strict mode and load asynchronously. They differ from CommonJS (require/module.exports) used in Node.
strict mode in JavaScript?
Adding "use strict"; at the top of a file or function enables strict mode, which opts into a restricted variant of JS that:
with statements.this undefined (not global) in non-method functions.implements, interface, etc.).ES6 modules and classes are always in strict mode automatically.
Array.map(), filter(), and reduce()?
map(fn) — transforms each element; returns a new array of the same length.filter(fn) — keeps only elements where fn returns truthy; returns a new array (may be shorter).reduce(fn, init) — reduces all elements to a single value (sum, object, nested array, etc.).const nums = [1, 2, 3, 4, 5]; nums.map(x => x * 2); // [2, 4, 6, 8, 10] nums.filter(x => x % 2 === 0); // [2, 4] nums.reduce((acc, x) => acc + x, 0); // 15
JSON (JavaScript Object Notation) is a lightweight, text-based data-interchange format. It is a subset of JavaScript object literal syntax but supports only strings, numbers, booleans, null, objects, and arrays — no functions or undefined.
// JS object → JSON string
const json = JSON.stringify({ name: 'Arjun', age: 22 });
// '{"name":"Arjun","age":22}'
// JSON string → JS object
const obj = JSON.parse('{"name":"Arjun","age":22}');
obj.name; // 'Arjun'
throw and reject in error handling?
throw — raises an exception synchronously. Use it in synchronous code or inside async functions (where it gets converted into a rejected Promise).reject() — rejects a Promise; used inside the Promise constructor to signal async failure.// Synchronous
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
// Async — both are equivalent
async function fetchData() {
throw new Error('Network error'); // same as reject
}
new Promise((_, reject) => {
reject(new Error('Network error'));
});
In async functions, always use throw. In Promise constructors, use reject().
Join CodeBegun's Java Full Stack program — 145 days of real-world projects, mock interviews, and 1-on-1 mentorship with industry engineers.
Apply Now — Next Batch 1 June →