Master the most frequently asked JS questions — with clear explanations and real code examples you can actually remember.
JavaScript is a lightweight, interpreted, high-level language with these key features:
var — function-scoped, hoisted and initialized as undefined, can be re-declared and reassigned.let — block-scoped, hoisted but stays in the Temporal Dead Zone until the declaration line, can be reassigned but not re-declared.const — block-scoped, same TDZ behavior, cannot be reassigned or re-declared. The value it holds (e.g., an object) can still be mutated.var x = 1;
{
var x = 10; // same variable — overwrites outer x
let y = 20; // block-scoped, invisible outside
}
console.log(x); // 10
console.log(y); // ReferenceErrorHoisting is the engine's behaviour of moving variable and function declarations to the top of their scope before code runs — only the declaration is hoisted, not the initialisation.
console.log(a); // undefined (var is declared but not yet assigned)
var a = 5;
greet(); // works — function declarations are fully hoisted
function greet() { console.log("Hello"); }
console.log(b); // ReferenceError — let stays in TDZ
let b = 10;== is loose equality and performs type coercion before comparing. === is strict equality — it compares value and type with no coercion.
0 == false // true (false is coerced to 0) 0 === false // false (different types) "5" == 5 // true "5" === 5 // false
Always prefer === to avoid subtle coercion bugs.
undefined — a variable has been declared but not yet assigned. JavaScript sets this automatically.null — an intentional absence of value. The developer explicitly sets it.let a; // undefined let b = null; // intentionally empty typeof undefined // "undefined" typeof null // "object" ← legacy quirk in JS
JavaScript has 8 data types:
string, number, bigint, boolean, undefined, null, symbolobject (arrays, functions, dates, maps, etc. are all objects)Use typeof to check a type. Note: typeof null === "object" is a known historical quirk.
Type coercion is the automatic conversion of a value from one type to another. JavaScript does this whenever you mix types in an operation.
"5" + 2 // "52" — number coerced to string
"5" - 2 // 3 — string coerced to number
true + 1 // 2 — boolean coerced to number
[] + {} // "[object Object]"
{} + [] // 0 (parsed differently by the engine)The spread operator (...) expands an iterable (array, string, object) into individual elements.
// Arrays
const a = [1, 2];
const b = [...a, 3, 4]; // [1, 2, 3, 4]
// Objects (shallow clone)
const obj = { x: 1 };
const copy = { ...obj, y: 2 }; // { x: 1, y: 2 }
// Function arguments
Math.max(...[3, 1, 4, 1, 5]); // 5Destructuring lets you unpack values from arrays or properties from objects into distinct variables.
// Array destructuring
const [a, b, ...rest] = [1, 2, 3, 4];
// a=1, b=2, rest=[3,4]
// Object destructuring
const { name, age = 18 } = { name: "Ravi" };
// name="Ravi", age=18 (default)
// In function params
function greet({ name, city = "Hyderabad" }) {
return `${name} from ${city}`;
}Template literals (backtick strings) support multi-line text, embedded expressions, and tagged templates.
const name = "CodeBegun";
const msg = `Hello, ${name}!`; // "Hello, CodeBegun!"
// Multi-line without \n
const html = `
<div>
<p>${msg}</p>
</div>
`;
// Expression interpolation
console.log(`2 + 2 = ${2 + 2}`); // "2 + 2 = 4"A closure is a function that retains access to its outer (lexical) scope even after the outer function has returned.
function counter() {
let count = 0;
return function () {
count++;
return count;
};
}
const inc = counter();
inc(); // 1
inc(); // 2 ← count persists inside the closureClosures power module patterns, memoization, and data encapsulation.
undefined), not the function body.greet(); // ✓ works
function greet() { return "Hello"; }
say(); // ✗ TypeError: say is not a function
var say = function () { return "Hi"; };Arrow functions are a concise syntax introduced in ES6. Key differences from regular functions:
this — inherits this from the surrounding (lexical) scope.arguments object (use rest params instead).new will throw).prototype property.const add = (a, b) => a + b;
const obj = {
name: "CB",
greet() {
setTimeout(() => {
console.log(this.name); // "CB" — arrow captures outer this
}, 100);
},
};An IIFE is a function that is defined and called immediately. It creates a private scope, preventing variable leakage into the global scope.
(function () {
const secret = "hidden";
console.log("runs immediately");
})();
// Arrow function IIFE
(() => {
const x = 10;
})();
console.log(typeof secret); // "undefined" — not in global scopeAll three let you set this explicitly for a function:
call(ctx, arg1, arg2) — invokes immediately; arguments passed individually.apply(ctx, [arg1, arg2]) — invokes immediately; arguments passed as an array.bind(ctx) — returns a new function with this bound; does not invoke immediately.function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: "Siva" };
greet.call(user, "Hello", "!"); // "Hello, Siva!"
greet.apply(user, ["Hi", "?"]); // "Hi, Siva?"
const hi = greet.bind(user, "Hey");
hi("."); // "Hey, Siva."Currying transforms a function with multiple arguments into a sequence of functions, each taking one argument.
// Without currying const add = (a, b) => a + b; add(2, 3); // 5 // Curried const curriedAdd = (a) => (b) => a + b; const add5 = curriedAdd(5); add5(3); // 8 add5(10); // 15
Useful for partial application and building reusable utility functions.
Generators are functions that can pause and resume execution via the yield keyword. They return an iterator object.
function* idGen() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGen();
gen.next().value; // 1
gen.next().value; // 2
gen.next().value; // 3Used for lazy sequences, infinite lists, and implementing async flows (like redux-saga).
Rest parameters (...args) collect all remaining function arguments into a real array. Unlike the old arguments object, rest params are a proper Array.
function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
// First arg named, rest captured
function first(head, ...tail) {
return { head, tail };
}
first(1, 2, 3); // { head: 1, tail: [2, 3] }The event loop is JavaScript's concurrency model. Since JS is single-threaded, it uses the event loop to handle asynchronous tasks without blocking.
Execution order: synchronous code → microtasks (Promises, queueMicrotask) → macrotasks (setTimeout, setInterval, I/O)
console.log("1");
setTimeout(() => console.log("3"), 0);
Promise.resolve().then(() => console.log("2"));
// Output: 1 2 3A Promise represents a value that may be available now, in the future, or never. It has three states: pending, fulfilled, rejected.
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve("done"), 1000);
});
p
.then(val => console.log(val)) // "done"
.catch(err => console.error(err))
.finally(() => console.log("cleanup"));async/await is syntactic sugar over Promises. An async function always returns a Promise; await pauses execution inside that function until the awaited Promise settles.
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("Not found");
return await res.json();
} catch (err) {
console.error(err);
}
}Promise.all() takes an array of Promises and returns a single Promise that resolves when all resolve — or rejects as soon as any one rejects.
const [user, posts] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
]);
// Both requests run in parallel — not sequentiallyUse it when multiple async operations are independent and you need all of their results.
Promise.race() — resolves/rejects with the first settled Promise (whichever finishes first, win or lose).Promise.allSettled() — waits for all Promises and returns an array of { status, value/reason } objects, never rejects.// race — timeout pattern
const result = await Promise.race([
fetch("/api/slow"),
new Promise((_, rej) => setTimeout(() => rej("timeout"), 3000))
]);
// allSettled — want all outcomes even if some fail
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.warn(r.reason);
});A callback is a function passed as an argument to another function and called when an async operation completes. Callback hell happens when callbacks are deeply nested, making code hard to read and maintain.
// Callback hell (pyramid of doom)
getUser(id, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
// deeply nested...
});
});
});
// Solution: Promises or async/await
const user = await getUser(id);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);Web Workers run JavaScript in a background thread, separate from the main UI thread — preventing heavy computation from blocking rendering.
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ data: [1, 2, 3, 4, 5] });
worker.onmessage = (e) => console.log("Result:", e.data);
// worker.js
self.onmessage = (e) => {
const result = e.data.data.reduce((a, b) => a + b, 0);
self.postMessage(result);
};Workers don't have access to the DOM. Communication happens via postMessage.
Objects inherit properties and methods from other objects via the prototype chain. Every object has an internal [[Prototype]] link.
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () {
return `${this.name} makes a sound.`;
};
const dog = new Animal("Rex");
dog.speak(); // "Rex makes a sound."
// dog.__proto__ === Animal.prototype → trueES6 classes are syntactic sugar over prototypal inheritance. They make OOP patterns cleaner but the underlying mechanism is still prototype-based.
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; // truenew Fn() — creates an object with Fn.prototype as its prototype and runs the constructor function.Object.create(proto) — creates a plain object with proto as its prototype; no constructor function is run.const proto = { greet() { return `Hi, ${this.name}`; } };
const obj = Object.create(proto);
obj.name = "Siva";
obj.greet(); // "Hi, Siva"
// new keyword
function Person(name) { this.name = name; }
const p = new Person("Siva");
p.greet; // undefined — no greet on Person.prototypeEncapsulation bundles data and the methods that operate on it, and restricts direct access to internal state. In JavaScript:
#field) — enforced at the engine level (ES2022+).class BankAccount {
#balance = 0;
deposit(amount) { this.#balance += amount; }
get balance() { return this.#balance; }
}
const acc = new BankAccount();
acc.deposit(500);
acc.balance; // 500
acc.#balance; // SyntaxError — truly privateMixins are a pattern for adding reusable methods to a class without using classical inheritance (avoiding the fragile base-class problem).
const Serializable = (Base) => class extends Base {
toJSON() { return JSON.stringify(this); }
toString() { return JSON.stringify(this); }
};
const Validatable = (Base) => class extends Base {
isValid() { return Object.keys(this).length > 0; }
};
class User extends Serializable(Validatable(class {})) {
constructor(name) { super(); this.name = name; }
}
new User("Siva").toJSON(); // '{"name":"Siva"}'The Document Object Model (DOM) is a tree-shaped, in-memory representation of an HTML/XML document that JavaScript can read and modify. Each HTML element becomes a node in the tree.
Key APIs: document.querySelector(), element.addEventListener(), element.innerHTML, element.setAttribute(), document.createElement().
Event delegation attaches a single listener to a parent element and uses event bubbling to handle events from multiple children — even dynamically added ones.
document.getElementById("list").addEventListener("click", (e) => {
if (e.target.matches("li")) {
console.log("Clicked:", e.target.textContent);
}
});
// Works for <li> items added after this code runsBenefits: fewer listeners, lower memory use, handles dynamic content.
innerHTML — gets/sets the HTML markup inside an element. Parses HTML tags. Risk of XSS if used with user input.textContent — gets/sets raw text only. Tags are treated as literal characters. Safe for untrusted input.const el = document.getElementById("box");
el.innerHTML = "<b>Bold</b>"; // renders bold text
el.textContent = "<b>Bold</b>"; // shows literal stringWhen an event fires, it travels through the DOM in three phases:
window down to the target.window.// Default: bubble phase (3rd arg false)
el.addEventListener("click", handler);
// Capture phase (3rd arg true)
el.addEventListener("click", handler, true);
// Stop propagation
el.addEventListener("click", (e) => {
e.stopPropagation(); // prevents further bubbling
});localStorage — persists until explicitly cleared. ~5 MB. No expiry. Same origin only.sessionStorage — persists only for the tab session (cleared on tab close). ~5 MB.HttpOnly (no JS access) and Secure flags. ~4 KB limit.Use localStorage for user preferences, sessionStorage for tab-specific state, cookies for session tokens.
offsetHeight — height including padding, border, and scrollbar (if any).clientHeight — height including padding but excluding border and scrollbar.scrollHeight — total scrollable height including hidden overflow content.const el = document.getElementById("box");
console.log(el.offsetHeight); // e.g. 220
console.log(el.clientHeight); // e.g. 200
console.log(el.scrollHeight); // e.g. 500 if content overflowsMutationObserver watches for changes to a DOM tree (added/removed nodes, attribute changes, text changes) and fires a callback asynchronously.
const observer = new MutationObserver((mutations) => {
mutations.forEach((m) => console.log(m.type, m.target));
});
observer.observe(document.body, {
childList: true, // watch child nodes
subtree: true, // and all descendants
attributes: true, // watch attribute changes
});
// Later:
observer.disconnect();The Shadow DOM is a browser API that encapsulates a DOM subtree inside a host element. Styles and scripts inside a shadow root don't leak out, and external styles don't leak in — enabling true component isolation (used by Web Components).
const host = document.getElementById("my-widget");
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>p { color: teal; }</style>
<p>Isolated content</p>
`;
// The <p> is scoped — external CSS cannot override itMemoization caches the return value of a function for given inputs so repeated calls with the same arguments skip recomputation.
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 fib = memoize((n) => (n <= 1 ? n : fib(n - 1) + fib(n - 2)));
fib(40); // fast.size, holds strong references (prevents GC)..size, holds weak references (key objects can be garbage collected).Use WeakMap to attach private data to objects or to cache values that should die when the object is no longer reachable.
// Debounce
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Throttle
function throttle(fn, limit) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= limit) { last = now; fn(...args); }
};
}A Proxy wraps an object and intercepts fundamental operations (get, set, delete, etc.) via traps. Used for validation, logging, reactivity systems (Vue 3).
const handler = {
get(target, key) {
return key in target ? target[key] : `Key "${key}" not found`;
},
set(target, key, value) {
if (typeof value !== "number") throw new TypeError("Numbers only");
target[key] = value;
return true;
},
};
const obj = new Proxy({}, handler);
obj.score = 95;
obj.score; // 95
obj.missing; // 'Key "missing" not found'Symbol creates a guaranteed-unique value — useful as object property keys that won't collide with other keys, and for implementing well-known protocols (iterators, toPrimitive).
const id = Symbol("id");
const user = { [id]: 42, name: "Siva" };
user[id]; // 42
user.id; // undefined — Symbol key is private-ish
// Well-known symbols
class MyList {
[Symbol.iterator]() {
let i = 0, data = [1, 2, 3];
return { next: () => ({ value: data[i++], done: i > data.length }) };
}
}
[...new MyList()]; // [1, 2, 3]require/module.exports) — synchronous, dynamic (can require at runtime), used by Node.js historically.import/export) — static (imports analysed at parse time), async-friendly, tree-shakeable, the standard for browsers and modern Node.// CommonJS
const fs = require("fs");
module.exports = { readFile: fs.readFileSync };
// ES Module
import { readFileSync } from "fs";
export const readFile = readFileSync;const obj = { a: 1, b: { c: 2 } };
// Shallow copy — b is still shared
const shallow = { ...obj };
shallow.b.c = 99;
obj.b.c; // 99 (mutated!)
// Deep copy
const deep = JSON.parse(JSON.stringify(obj)); // simple but lossy
const deep2 = structuredClone(obj); // modern, preferredA service worker is a script that the browser runs in a background thread, separate from the page. It intercepts network requests, enabling:
// Register
navigator.serviceWorker.register("/sw.js");
// sw.js — intercept fetch
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});Tree shaking is a dead-code elimination technique used by bundlers (Webpack, Rollup, Vite). It statically analyses ES Module import/export statements and removes exports that are never imported, reducing bundle size.
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b; // never imported
// app.js
import { add } from "./math.js";
// After tree shaking, multiply is stripped from the bundleTree shaking only works with static ES Module syntax — not require().
An iterable is any object with a [Symbol.iterator]() method that returns an iterator. An iterator is an object with a next() method returning { value, done }.
// Custom iterable
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
};
[...range]; // [1, 2, 3, 4, 5]The TDZ is the period between entering a block scope where a let or const variable is declared, and the line where it is actually declared. Accessing the variable in this period throws a ReferenceError.
{
// TDZ starts for x
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10; // TDZ ends here
console.log(x); // 10
}The TDZ exists to catch bugs that var's silent undefined hoisting would hide.
Lazy loading defers loading of a resource until it is actually needed, improving initial page performance.
loading="lazy" attribute (native HTML).import() to split JS bundles.// Dynamic import (code splitting)
const btn = document.getElementById("load");
btn.addEventListener("click", async () => {
const { Chart } = await import("./Chart.js");
new Chart(document.getElementById("canvas"));
});
// Intersection Observer lazy image
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) { e.target.src = e.target.dataset.src; }
});
});The 145-day Java Full Stack program includes mock interviews, live projects & placement support.
Apply Now →