[Go to site: main page, start]

0% found this document useful (0 votes)
4 views26 pages

JavaScript Interview QnA

This document is a comprehensive guide for JavaScript interview preparation, covering over 40 questions across 7 sections that range from easy to hard. It includes model answers, follow-up questions, and detailed explanations on topics such as JavaScript fundamentals, DOM, events, asynchronous programming, ES6+ features, and object-oriented programming in JavaScript. The guide is designed for entry-level web developers aiming for positions in frontend or full-stack development, with a focus on practical knowledge and coding skills.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views26 pages

JavaScript Interview QnA

This document is a comprehensive guide for JavaScript interview preparation, covering over 40 questions across 7 sections that range from easy to hard. It includes model answers, follow-up questions, and detailed explanations on topics such as JavaScript fundamentals, DOM, events, asynchronous programming, ES6+ features, and object-oriented programming in JavaScript. The guide is designed for entry-level web developers aiming for positions in frontend or full-stack development, with a focus on practical knowledge and coding skills.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

JavaScript

Interview Q&A
Web Development · DOM · Events · Async · ES6+ · OOP in JS
5–6 LPA · Entry Level · Frontend / Full-Stack Web Dev Prep

40+ Questions 7 Sections Easy → Hard

Core JS · DOM · Events · Async · OOP · APIs · Browser StorageModel Answers Follow-ups Answered

How to use: Questions go Easy → Medium → Hard. Every question has a full Model Answer. Blue boxes = follow-up
questions with full answers. Green boxes = deeper probes. This guide covers JavaScript from the perspective of web
development — DOM manipulation, events, async patterns, ES6+, and browser APIs. OOP theory is covered in the
Python guide; here we focus on JS-specific OOP (prototypes, classes).
■ Section 1: JavaScript Fundamentals — Types, Scope & Hoisting

Q1. What is JavaScript? How does it run in a browser?


■ Topic: JS Overview
● Easy — must answer cold

■ Model Answer:
JavaScript is a dynamically typed, single-threaded, interpreted scripting language. It is the only language natively
supported by browsers, making it essential for web interactivity. In the browser: the JS engine (V8 in Chrome,
SpiderMonkey in Firefox) parses and executes JS. JS runs in a single thread — one task at a time — but handles
concurrency via the Event Loop (callbacks, Promises, async/await). Beyond browsers: [Link] runs JS on the server
using V8.

// Event loop order demonstration


[Link]("1 - sync");

setTimeout(() => [Link]("2 - setTimeout"), 0); // macro task

[Link]().then(() => [Link]("3 - Promise")); // micro task

[Link]("4 - sync");

// Output order: 1, 4, 3, 2
// Sync first, then microtasks (Promise), then macro tasks (setTimeout)

■ Follow-up: What is the difference between JavaScript and ECMAScript?


ECMAScript (ES) is the language specification — the standard written by TC39. JavaScript is the implementation —
what browsers and [Link] actually run. ES6 (2015) was a landmark version: classes, arrow functions, const/let,
Promises, template literals, destructuring, modules. Since ES2016, a new version is released every year (ES2017,
ES2018...). When someone says "ES6+" they mean modern JavaScript with all features from ES6 onward.

■ Follow-up: What is the JavaScript event loop?


JS is single-threaded — one call stack. The event loop enables non-blocking concurrency. Call Stack: executes
synchronous code — functions pushed on entry, popped on return. Web APIs: browser-provided async APIs
(setTimeout, fetch, DOM events) run outside the JS thread. Callback Queue (Task Queue): completed async callbacks
wait here. Microtask Queue: Promise .then() callbacks — higher priority than callback queue. Event loop: when the call
stack is EMPTY, it first drains the microtask queue, then picks one task from the callback queue. This is why Promise
callbacks run before setTimeout(fn, 0) callbacks.

Q2. Explain var, let, and const. What is hoisting?


■ Topic: Scope & Hoisting
● Easy — must answer cold

■ Model Answer:
var: function-scoped, hoisted and INITIALISED to undefined at the top of its function. Can be re-declared. Avoid in
modern JS. let: block-scoped ({}), hoisted but NOT initialised — accessing before declaration throws ReferenceError
(Temporal Dead Zone). Cannot be re-declared. const: block-scoped, same as let but MUST be assigned at declaration
and cannot be reassigned. The binding is const, not the value — object properties can still be mutated. Hoisting: JS
moves declarations to the top of their scope at compile time. var declarations hoist with undefined; function declarations
hoist fully.
// var — function scoped, hoisted
function example() {
[Link](x); // undefined (hoisted)
var x = 10;
[Link](x); // 10
}

// let — block scoped, TDZ


{
// [Link](y); // ReferenceError: TDZ
let y = 20;
[Link](y); // 20
}
// [Link](y); // ReferenceError: y is not defined

// const — binding is fixed, value can mutate


const arr = [1, 2, 3];
[Link](4); // OK — mutating the array
// arr = [1, 2, 3, 4]; // TypeError — reassigning the binding

// Function hoisting — fully hoisted


greet("Alice"); // Works! Function declaration is fully hoisted
function greet(name) { return `Hello ${name}`; }

■ Follow-up: What is the Temporal Dead Zone (TDZ)?


The TDZ is the period between the start of a block and the let/const declaration within it. The variable IS hoisted (exists
in memory) but is not initialised — accessing it throws ReferenceError, not undefined. Example: { [Link](x); let x =
5; } — ReferenceError. With var: { [Link](x); var x = 5; } — logs undefined (hoisted and initialised). TDZ exists to
prevent bugs from using variables before they're defined, making code more predictable.

■ Follow-up: When should you use const vs let?


Default to const — it signals "this binding will not change" to readers and prevents accidental reassignment. Use let
only when you genuinely need to reassign the variable (loop counters, accumulating values, conditional assignment).
const with objects/arrays: the variable always points to the same object, but the object's contents are mutable. If you
need a truly immutable object: [Link](obj) — prevents adding/modifying/deleting properties (shallow freeze
only). Rule: never use var in new code. Prefer const, use let when needed.

Q3. What is the difference between == and ===? Explain type coercion.
■ Topic: Type Coercion
● Easy — must answer cold

■ Model Answer:
=== (strict equality): checks VALUE and TYPE — no coercion. Always use this. == (loose equality): checks value after
TYPE COERCION — JS converts one or both operands to a common type using complex rules. Type coercion: JS
automatically converts types in certain operations. "5" + 3 = "53" (string concatenation). "5" - 3 = 2 (numeric). Falsy
values: false, 0, "", null, undefined, NaN — all coerce to false in boolean context. Everything else is truthy.
// === vs == — always use ===
5 === "5" // false — different types
5 == "5" // true — "5" coerced to number
null == undefined // true — special case
null === undefined // false

// Falsy values
const falsy = [false, 0, "", null, undefined, NaN];
[Link](v => !v); // true — all falsy

// Type coercion gotchas


"5" + 3 // "53" — + triggers string concat
"5" - 3 // 2 — - triggers numeric
"5" * "3" // 15 — numeric

// Safe checks
[Link](NaN) // true
[Link]("hello") // false (no coercion)
isNaN("hello") // true (coerces!) — avoid

// Explicit conversion
Number("42") // 42
String(42) // "42"
Boolean(0) // false
Boolean("hi") // true

■ Follow-up: What are some surprising type coercion results in JS?


"" == false → true (both coerce to 0). null == undefined → true (special rule). null == 0 → false (null only equals
undefined with ==). [] == false → true ([] coerces to "" which coerces to 0). {} + [] → 0 (in some contexts). NaN === NaN
→ false (NaN is the only value not equal to itself). typeof null === "object" → true (historic JS bug). Use Number(x),
String(x), Boolean(x) for explicit, predictable conversion.

■ Follow-up: How do you check if a value is NaN?


NaN (Not a Number) is the result of invalid numeric operations: parseInt("abc"), 0/0, [Link](-1). NaN === NaN is
false — NaN is never equal to anything including itself. [Link](x): returns true ONLY if x is actually NaN —
safe, no coercion. isNaN(x): coerces x to number first — isNaN("hello") is true (misleading). Always use
[Link](). Also useful: [Link](x) — true if finite number (not NaN, Infinity, -Infinity).

Q4. Explain closures in JavaScript. Give a real-world use case.


■ Topic: Closures & Scope
● Medium — should know well

■ Model Answer:
A closure is a function that retains access to variables from its outer (enclosing) scope even after that outer function has
returned. In JS, every function creates a closure over the variables in scope when it was defined. The inner function
"closes over" the outer variables — they are kept alive in memory as long as the inner function exists. Use cases: data
privacy/encapsulation, factory functions, event handlers that remember context, memoization, module pattern.
// Closure — factory function
function makeCounter(start = 0) {
let count = start; // private to this call
return {
increment() { return ++count; },
decrement() { return --count; },
reset() { count = start; },
value() { return count; }
};
}

const c1 = makeCounter(10);
const c2 = makeCounter(0);
[Link](); // 11
[Link](); // 1 — independent closures

// Loop bug fix with let


for (let i = 0; i < 3; i++) {
setTimeout(() => [Link](i), 0); // 0, 1, 2
}

// Closure for memoization


function memoize(fn) {
const cache = {};
return function(...args) {
const key = [Link](args);
if (key in cache) return cache[key];
return cache[key] = fn(...args);
};
}

■ Follow-up: How does the closure loop bug happen in JavaScript?


Classic: for (var i=0; i<3; i++) { setTimeout(() => [Link](i), 0); } — logs 3,3,3. All three callbacks close over the
SAME "i" variable (var is function-scoped). By the time they run, the loop is done and i=3. Fix 1: use let — for (let i=0;
i<3; i++) — let is block-scoped, each iteration gets its OWN i. Logs 0,1,2. Fix 2: IIFE — (function(i){
setTimeout(()=>[Link](i),0); })(i) — captures current i as a parameter. Fix 3: .bind() or passing i as argument to
the callback factory.

■ Follow-up: What is the module pattern and how does it use closures?
The module pattern uses closures to create private state — variables that are inaccessible from outside but
remembered by the returned public API. const counter = (function() { let count = 0; return { increment() { count++; },
decrement() { count--; }, getCount() { return count; } }; })(); count is private — outside code cannot access it directly.
Only the returned methods can access it via closure. This was how JS modules worked before ES6 import/export.
Today, ES modules have file-level scope which provides similar privacy.

Q5. What is "this" in JavaScript? How does it change in different contexts?


■ Topic: this & Context
● Medium — should know well

■ Model Answer:
"this" refers to the execution context — the object that is currently calling the function. Its value depends entirely on HOW
the function is called, not where it is defined. Global context: this = window (browser) or global ([Link]). Object method:
this = the object before the dot. Constructor (new): this = the newly created object. Arrow function: NO own this — inherits
this from the enclosing lexical scope. Explicit: call(thisArg), apply(thisArg, [args]), bind(thisArg) — set this manually.
// this depends on call site, not definition
const obj = {
name: "Alice",
greet() { return `Hi, ${[Link]}`; },
greetArrow: () => `Hi, ${this?.name}`, // this = outer scope
};

[Link](); // "Hi, Alice" — this = obj


const fn = [Link];
fn(); // "Hi, undefined" — this = window/undefined

// Fix with bind


const bound = [Link](obj);
bound(); // "Hi, Alice"

// Arrow in class — correct this


class Timer {
constructor() { [Link] = 0; }
start() {
setInterval(() => { // arrow: this = Timer instance
[Link]++; // works correctly
}, 1000);
}
}

// call and apply


function introduce(city, country) {
return `${[Link]} from ${city}, ${country}`;
}
[Link]({name:"Bob"}, "Delhi", "India");
[Link]({name:"Bob"}, ["Delhi", "India"]);

■ Follow-up: Why do arrow functions not have their own "this"?


Arrow functions lexically capture this from the surrounding scope at the time they are DEFINED, not when they are
called. This is their biggest practical advantage: in class methods, event handlers, and callbacks, you often want this to
refer to the class instance, not whatever called the callback. Without arrow: setTimeout(function() { [Link]++; },
1000) — this is window inside the callback. With arrow: setTimeout(() => { [Link]++; }, 1000) — this is the outer
context (class instance). Downside: cannot be used as methods in object literals if you need dynamic this, and cannot
be used as constructors.

■ Follow-up: What is the difference between call(), apply(), and bind()?


All three explicitly set this. call(thisArg, arg1, arg2, ...): invokes the function immediately with individual args.
apply(thisArg, [arg1, arg2, ...]): invokes immediately with args as an array. Useful with [Link](null, array).
bind(thisArg, arg1, ...): returns a NEW function with this permanently bound — does NOT invoke immediately. Used for
event handlers, passing methods as callbacks. const boundFn = [Link](obj); — now boundFn() always uses
obj as this regardless of caller.

■ Section 2: ES6+ Modern JavaScript Features

Q6. Explain destructuring, spread operator, and rest parameters.


■ Topic: ES6+ Syntax
● Easy — must answer cold

■ Model Answer:
Destructuring: extract values from arrays or properties from objects into named variables — clean, concise alternative to
multiple assignment statements. Spread (...): expands an iterable (array, string) or object into individual elements. Rest
(...): collects remaining elements into an array (in function params or destructuring). These three features work together
and are fundamental to modern JS style.

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// Object destructuring with rename and default


const { name: userName = "Guest", age = 18, ...others } = user;

// Swap variables
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1

// Spread — copy and merge


const arr1 = [1,2,3]; const arr2 = [4,5,6];
const merged = [...arr1, ...arr2]; // [1,2,3,4,5,6]

const defaults = { theme:"dark", lang:"en" };


const userPrefs = { lang:"te", fontSize:16 };
const config = { ...defaults, ...userPrefs }; // userPrefs wins on conflict

// Rest in function
function sum(first, ...rest) {
return first + [Link]((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10

■ Follow-up: How do you set default values in destructuring?


const { name = "Guest", age = 18 } = user; — if name is undefined in user, "Guest" is used. const [first = 0, second = 0]
= arr; — defaults for array destructuring. In function params: function greet({ name = "Guest", role = "user" } = {}) — the
= {} default prevents crash if no argument is passed. Rename while destructuring: const { name: userName } = user; —
creates variable userName with the value of [Link].

■ Follow-up: What is the difference between spread and [Link]()?


Both shallow-copy object properties. [Link](target, source): mutates target. [Link]({}, obj) to copy
without mutation. Spread: { ...obj } — always creates a NEW object, cleaner syntax. Gotcha: both are SHALLOW
copies — nested objects are still shared by reference. Deep clone: structuredClone(obj) (modern browsers),
[Link]([Link](obj)) (simple but loses functions/dates), or [Link].

Q7. What are template literals and tagged templates?


■ Topic: ES6+ Strings
● Easy — must answer cold

■ Model Answer:
Template literals: backtick strings that allow embedded expressions ${} and multi-line strings without \n. Tagged
templates: a function before the template literal — the function receives the string parts and interpolated values
separately, enabling custom string processing. Used in: styled-components (CSS-in-JS), GraphQL query strings (gql),
SQL query builders, i18n libraries.
// Template literal — multi-line and expressions
const product = { name: "Laptop", price: 75000, discount: 10 };
const html = `

${[Link]}
Price: ■${([Link] * (1 - [Link]/100)).toFixed(2)}

`;

// Tagged template — custom processing


function highlight(strings, ...values) {
return [Link]((result, str, i) => {
const val = values[i - 1];
return result + (val !== undefined
? `${val}`
: "") + str;
});
}

const name = "Alice"; const score = 95;


highlight`Student ${name} scored ${score}%`;
// "Student Alice scored 95%"

■ Follow-up: How is a tagged template function called?


tag`Hello ${name}, you have ${count} messages` is equivalent to: tag(["Hello ", ", you have ", " messages"], name,
count). The tag function receives: (strings, ...values) — strings is an array of the literal parts, values are the
interpolated results. The function can return anything — not just a string. Real example: html`${userInput}` in
lit-element sanitizes the interpolated values to prevent XSS.

■ Follow-up: What are some practical uses of template literals in web development?
Dynamic HTML generation: const card = `${[Link]}${[Link]}`. Multi-line SQL (though use parameterized
queries, not string interpolation for user data!). CSS-in-JS with styled-components: const Button =
[Link]`background: ${props => [Link] ? "blue" : "white"}`. Logging: [Link](`User ${[Link]}
performed ${action} at ${new Date().toISOString()}`).

Q8. What are ES6 modules? Explain import and export.


■ Topic: ES6 Modules
● Medium — should know well

■ Model Answer:
ES6 modules allow splitting code into separate files with explicit imports and exports. Each module has its own scope —
variables are NOT global by default. Named export: export const PI = 3.14; — import { PI } from "./[Link]". Multiple per
file. Default export: export default class App {} — import App from "./[Link]". One per file. In HTML: use script tag with
type="module" — enables import/export in the browser. Modules are deferred by default.
// [Link] — named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Vector { constructor(x,y){this.x=x;this.y=y;} }

// [Link] — default export


export default function formatCurrency(amount, currency="INR") {
return new [Link]("en-IN",
{style:"currency", currency}).format(amount);
}

// [Link] — importing
import formatCurrency from "./[Link]"; // default
import { PI, add, Vector } from "./[Link]"; // named
import * as MathUtils from "./[Link]"; // namespace
import { add as sum } from "./[Link]"; // rename

// Dynamic import — lazy loading


const loadChart = async () => {
const { Chart } = await import("./[Link]");
return new Chart();
};

■ Follow-up: What is the difference between CommonJS (require) and ES Modules (import)?
CommonJS ([Link], older): const express = require("express"). Synchronous, loads at runtime, dynamic (can require
conditionally). ES Modules: import express from "express". Static — imports are resolved at parse time, enabling
tree-shaking (bundlers can remove unused exports). Asynchronous-capable. In [Link]: use .mjs extension or
"type":"module" in [Link] for ES modules. In browsers: only ES modules are natively supported (CommonJS
requires a bundler like webpack/vite). Dynamic import(): const module = await import("./[Link]") — lazy-load ES
modules on demand.

■ Follow-up: What is tree-shaking and why do ES modules enable it?


Tree-shaking: bundlers (webpack, rollup, vite) remove unused exports from the final bundle, reducing file size. ES
modules enable this because imports are STATIC — the bundler can analyse at build time exactly which exports are
used. import { debounce } from "lodash-es" — only debounce is included in the bundle. CommonJS: const _ =
require("lodash") — entire lodash is included because require is dynamic (bundler can't know what you'll use at
runtime). This is why lodash-es (ES module version) is preferred over lodash for frontend projects.

■ Section 3: DOM Manipulation & Browser APIs

Q9. What is the DOM? How do you select and manipulate elements?
■ Topic: DOM
● Easy — must answer cold

■ Model Answer:
The DOM (Document Object Model) is the browser's tree-like in-memory representation of the HTML document. JS
interacts with the page by reading and modifying DOM nodes. Selecting elements: [Link],
[Link] (CSS selector, returns first match), [Link] (returns NodeList of all
matches). Modifying: [Link], [Link], [Link], [Link], [Link].
// Selecting elements
const title = [Link]("title");
const btn = [Link](".submit-btn");
const cards = [Link](".product-card");

// Reading & writing content


[Link] = "New Title"; // safe — no HTML parsing
[Link] = "New Title"; // parses HTML — careful!

// Styling
[Link] = "blue";
[Link] = "color:white; padding:8px 16px;";

// classList — preferred over className


[Link]("active");
[Link]("disabled");
[Link]("hidden"); // add if absent, remove if present
[Link]("active"); // true/false

// Attributes
[Link]("data-product-id", "42");
[Link]("data-product-id"); // "42"
[Link]; // "42" (data- attributes)

// Create and append elements


const li = [Link]("li");
[Link] = "New item";
[Link]("ul").appendChild(li);

■ Follow-up: What is the difference between innerHTML and textContent?


textContent: sets/gets the raw TEXT content — no HTML parsing. Safe — user input is treated as text, not markup
(prevents XSS). innerHTML: sets/gets content INCLUDING HTML markup — the browser parses and renders it. Fast
for setting large HTML chunks. DANGER: [Link] = userInput — if userInput contains a script tag or event
handlers, it executes. Always sanitise user input before setting innerHTML (use DOMPurify or textContent). innerText:
similar to textContent but is "rendered" text — respects CSS (hidden elements return ""). Slower than textContent.

■ Follow-up: What is the difference between querySelector and getElementById?


getElementById("id"): fastest — direct hash lookup. Returns null if not found. No # prefix. querySelector("#id"): uses
CSS selector engine — slightly slower but accepts any CSS selector: querySelector(".[Link]"),
querySelector("[data-id='5']"), querySelector("ul > li:first-child"). querySelectorAll: returns a static NodeList (not live —
doesn't update when DOM changes). getElementsByClassName, getElementsByTagName: return live
HTMLCollections — update when DOM changes. Can cause infinite loops if you add elements while iterating. Modern
best practice: always use querySelector/querySelectorAll for consistency.

Q10. How do you traverse the DOM? Explain parent, child, and sibling relationships.
■ Topic: DOM Traversal
● Easy — must answer cold

■ Model Answer:
Every DOM node has properties to navigate the tree. Parent: [Link]. Children: [Link]
(HTMLCollection, element nodes only), [Link] (NodeList, includes text nodes). First/Last child:
[Link], [Link]. Siblings: [Link],
[Link]. Closest ancestor matching selector: [Link](".container") — walks up the
DOM.
const list = [Link](".product-list");

// Children
[Link]; // HTMLCollection of li elements
[Link][0]; // first li
[Link]; // first li
[Link]; // last li
[Link]; // count

// Parent
const item = [Link](".product-list li");
[Link]; // the ul
[Link]; // grandparent

// Siblings
[Link]; // next li
[Link];// previous li

// closest — walk up to find ancestor


[Link]("click", e => {
const btn = [Link]("[data-action]");
if (!btn) return;
const action = [Link]; // e.g. "delete", "edit"
handleAction(action, [Link]);
});

■ Follow-up: What is [Link]() and when is it useful?


closest(selector): starts at the element and walks UP the DOM tree, returning the first ancestor matching the selector
(or null). Extremely useful in event delegation: when a deeply nested element is clicked, use [Link](".card") to
find the card container regardless of which nested child was clicked. Example:
[Link](".product-list").addEventListener("click", e => { const card = [Link](".product-card");
if (!card) return; handleCardClick(card); }). Also useful for: finding the nearest form from an input, finding a table row
from a cell.

■ Follow-up: What is the difference between children and childNodes?


children: only ELEMENT nodes (div, p, span, etc.) — ignores text nodes and comment nodes. Returns live
HTMLCollection. childNodes: ALL nodes including text nodes (whitespace between elements), comment nodes, and
element nodes. Returns live NodeList. children is almost always what you want. childNodes is useful when working
with text content or XML. first/lastChild vs first/lastElementChild: same distinction — Element versions skip text nodes.

Q11. What is event delegation? Why is it better than attaching listeners to each
element?
■ Topic: Events & DOM
● Medium — should know well

■ Model Answer:
Event delegation: attach ONE event listener to a PARENT element instead of separate listeners on each child. Relies on
event bubbling — events bubble up from the target to the root. Check [Link] inside the handler to know which child
was clicked. Advantages: (1) Memory efficient — one listener vs hundreds. (2) Works for dynamically added elements —
no need to re-attach listeners. (3) Less code.
// WITHOUT delegation — attach to each item (bad)
[Link](".product-card .delete-btn").forEach(btn => {
[Link]("click", handleDelete); // N listeners, dynamic elements missed
});

// WITH delegation — one listener on parent (good)


[Link](".product-list").addEventListener("click", (e) => {
// Find closest delete button ancestor (or self)
const deleteBtn = [Link](".delete-btn");
if (!deleteBtn) return; // click was elsewhere

const card = [Link](".product-card");


const id = [Link];
deleteProduct(id); // works for dynamically added cards too
});

// [Link] — stop form submit


[Link]("submit", (e) => {
[Link](); // stop page reload
submitViaFetch(form); // handle with JS
});

■ Follow-up: What is event bubbling and event capturing?


When an event fires on an element, it propagates in three phases: Capture phase: event travels DOWN from
document to target. Target phase: event is at the target element. Bubble phase: event travels UP from target to
document. addEventListener(event, handler) — by default listens in the bubble phase. addEventListener(event,
handler, true) — listens in the capture phase. [Link](): prevents further bubbling (or capturing).
[Link](): prevents the default browser action (form submit, link navigation) — does NOT stop
propagation.

■ Follow-up: What is the difference between [Link] and [Link]?


[Link]: the element that TRIGGERED the event — the actual element that was clicked. [Link]: the
element the listener is ATTACHED to — always the same as "this" inside the handler (non-arrow function). In
delegation: listener is on the parent (currentTarget=parent), but user clicked a child (target=child). You use
[Link]() to find the relevant ancestor in delegation patterns.

Q12. How do you make HTTP requests in JavaScript? Explain fetch() and
async/await.
■ Topic: Browser APIs / Async
● Medium — should know well

■ Model Answer:
fetch(url, options): browser-native API for HTTP requests. Returns a Promise that resolves to a Response object.
Two-step: first await fetch() to get the response, then await [Link]() (or .text(), .blob()) to read the body. Always
check [Link] (status 200-299) — fetch only rejects on network failure, NOT on 4xx/5xx status codes.
// fetch with async/await — correct pattern
async function getProducts(page = 1) {
try {
const res = await fetch(`/api/products?page=${page}`, {
method: "GET",
headers: { "Authorization": `Bearer ${token}` }
});

if (![Link]) {
throw new Error(`Server error: ${[Link]}`);
}

const data = await [Link]();


return [Link];
} catch (err) {
[Link]("Fetch failed:", [Link]);
throw err; // let caller handle
}
}

// POST with JSON body


async function createOrder(orderData) {
const res = await fetch("/api/orders/", {
method: "POST",
headers: { "Content-Type": "application/json",
"Authorization": `Bearer ${token}` },
body: [Link](orderData)
});
if (![Link]) throw new Error(await [Link]());
return [Link]();
}

■ Follow-up: How do you handle errors with fetch?


fetch("url").catch() only catches network errors (no internet, DNS failure). A 404 or 500 response does NOT reject the
promise. Pattern: const res = await fetch(url); if (![Link]) throw new Error(`HTTP ${[Link]}: ${[Link]}`);
const data = await [Link](). Wrap in try/catch for both network errors and our manual throw. Timeout: fetch has no
built-in timeout. Use AbortController: const controller = new AbortController(); setTimeout(() => [Link](),
5000); fetch(url, {signal: [Link]}).

■ Follow-up: What is the difference between Promises and async/await?


Promises: .then()/.catch()/.finally() chaining. Can run multiple promises concurrently with [Link](), [Link](),
[Link](). async/await: syntactic sugar over Promises — same underlying mechanism, reads like
synchronous code. await pauses execution of the async function (not the whole thread) until the Promise resolves.
Error handling: .catch() on the chain vs try/catch with await — try/catch is cleaner for sequential logic. [Link]([p1,
p2, p3]): runs all concurrently, resolves when ALL resolve, rejects if ANY rejects. [Link]: resolves when all
complete, never rejects — gives status of each.

■ Section 4: OOP in JavaScript — Prototypes & Classes

Q13. How does prototypal inheritance work in JavaScript?


■ Topic: JS OOP / Prototypes
● Medium — should know well

■ Model Answer:
Every JS object has an internal link to another object called its prototype ([[Prototype]] or __proto__). When you access a
property, JS first checks the object itself, then its prototype, then the prototype's prototype — up the prototype chain until
null. All objects created with object literals inherit from [Link]. Functions have a .prototype property — when
used with "new", the created object's [[Prototype]] is set to the function's .prototype.

// Prototype chain
const animal = { breathe() { return "breathing"; } };
const dog = [Link](animal); // dog.__proto__ === animal
[Link] = function() { return "woof"; };

[Link](); // "woof" — own property


[Link](); // "breathing" — from prototype

// ES6 class (sugar over prototypes)


class Animal {
constructor(name) { [Link] = name; }
speak() { return `${[Link]} makes a sound`; }
}

class Dog extends Animal {


constructor(name, breed) {
super(name); // must call super before using this
[Link] = breed;
}
speak() { return `${[Link]} barks`; }
}

const d = new Dog("Rex", "Lab");


[Link]([Link]()); // "Rex barks"
[Link](d instanceof Dog); // true
[Link](d instanceof Animal); // true
[Link]([Link](d) === [Link]); // true

■ Follow-up: How is JS class syntax related to prototypes?


ES6 class is syntactic sugar over prototypal inheritance — under the hood, it still uses prototypes. class Animal {
speak() {...} } creates a constructor function and puts speak on [Link]. new Animal() creates an object
whose [[Prototype]] is [Link]. class Dog extends Animal uses [Link]([Link]) for the chain.
Proof: typeof Animal === "function" — classes are functions in JS. The class syntax is preferred for clarity, but
understanding prototypes helps debug prototype chain issues.

■ Follow-up: What is [Link]() and when would you use it?


[Link](proto): creates a new object with the specified object as its prototype. [Link](null): creates an
object with NO prototype — useful for pure dictionaries with no inherited properties (no toString, hasOwnProperty
conflicts). [Link]([Link]): creates an object that inherits from Animal without calling the constructor.
[Link](obj): returns the prototype of obj. hasOwnProperty(key): checks if property exists ON the object
itself (not inherited) — use [Link](obj, key) in modern JS.

Q14. What are getters, setters, and private class fields in JavaScript?
■ Topic: JS OOP / Classes
● Medium — should know well

■ Model Answer:
Getters/Setters: get and set keywords define computed properties on a class — accessed like attributes but run code.
Private fields (#): declared with # prefix — truly private to the class, NOT accessible outside (unlike _ convention which is
just a convention). Static members: belong to the class itself, not instances — class-level utility methods and constants.
class Product {
#price; // private field — truly private
#stock;

static TAX_RATE = 0.18; // static field

constructor(name, price, stock) {


[Link] = name;
this.#price = price;
this.#stock = stock;
}

get price() { return this.#price; }


set price(v) {
if (v < 0) throw new Error("Price cannot be negative");
this.#price = v;
}

get priceWithTax() { // computed — no setter


return this.#price * (1 + Product.TAX_RATE);
}

get inStock() { return this.#stock > 0; }

static fromJSON(json) { // factory method


const d = [Link](json);
return new Product([Link], [Link], [Link]);
}
}

const p = new Product("Laptop", 50000, 5);


[Link] = 55000; // setter called
[Link]([Link]); // 64900
// p.#price // SyntaxError outside class

■ Follow-up: How are private class fields (#) different from the _ convention?
_ prefix (convention): no enforcement — _privateVar is accessible by anyone. Just a signal to other developers: "don't
use this directly." # private fields (ES2022): enforced by the language engine — access from outside throws
SyntaxError. [Link](person.#name) from outside the class — SyntaxError. # fields must be declared in the class
body before use (unlike this.x which can be created in constructor). You can check if a private field exists: #name in obj
(within the class). Private methods: #doSomething() {} — same syntax.

■ Follow-up: What are static methods and static fields?


Static members belong to the CLASS, not instances. Accessed as [Link](), not [Link](). Use
for: factory methods (alternative constructors), utility functions related to the class, constants. class MathUtils { static PI
= 3.14159; static square(x) { return x*x; } } [Link](5) — no instance needed. Static methods are NOT
available on instances: new MathUtils().square(5) — TypeError. static get/set: static getters/setters on the class itself.

■ Section 5: Asynchronous JavaScript — Callbacks, Promises &


Async/Await

Q15. What are Promises? Explain the three states.


■ Topic: Async / Promises
● Easy — must answer cold
■ Model Answer:
A Promise represents the eventual completion or failure of an asynchronous operation. Three states — a Promise can
only be in ONE and transitions are irreversible: Pending: initial state — operation not yet completed. Fulfilled: operation
succeeded — .then() callbacks run. Rejected: operation failed — .catch() callbacks run. .then(onFulfilled, onRejected)
returns a new Promise — enables chaining.

// Creating a Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

// Promise chaining
fetch("/api/user/1")
.then(res => {
if (![Link]) throw new Error(`${[Link]}`);
return [Link](); // return Promise — chain waits
})
.then(user => fetch(`/api/orders?userId=${[Link]}`))
.then(res => [Link]())
.then(orders => renderOrders(orders))
.catch(err => showError([Link]))
.finally(() => hideSpinner()); // always runs

// [Link] — concurrent requests


const [user, products, cart] = await [Link]([
fetch("/api/user").then(r => [Link]()),
fetch("/api/products").then(r => [Link]()),
fetch("/api/cart").then(r => [Link]()),
]);
// All three requests fire simultaneously — much faster than sequential await

■ Follow-up: What is Promise chaining and what is the "callback hell" it solves?
Callback hell: deeply nested callbacks, hard to read and error-handle: getData(function(a){ getMoreData(a, function(b){
getEvenMore(b, function(c){...})})}). Promise chaining: flat, readable:
fetch(url).then(r=>[Link]()).then(data=>process(data)).catch(handleError). Each .then() returns a new Promise — if you
return a value, it wraps it in a resolved Promise. If you return a Promise, the chain waits for it. async/await makes this
even more readable: const data = await (await fetch(url)).json().

■ Follow-up: What is the difference between [Link] and [Link]?


[Link]([p1,p2,p3]): resolves when ALL promises resolve — value is array of results. FAILS FAST — rejects as
soon as ANY promise rejects. [Link]([p1,p2,p3]): resolves when ALL promises complete (fulfilled OR
rejected). Never rejects. Value is array of {status:"fulfilled",value:...} or {status:"rejected",reason:...}.
[Link]([p1,p2,p3]): resolves/rejects with the FIRST promise to settle. [Link]([p1,p2,p3]): resolves with first
FULFILLED promise. Rejects only if ALL reject (AggregateError). Use all() for concurrent operations that all must
succeed. allSettled() when you want results of all regardless of failure.

Q16. Explain async/await. How do you run multiple async operations concurrently?
■ Topic: Async / Await
● Medium — should know well

■ Model Answer:
async function: always returns a Promise. await expression: pauses the async function until the awaited Promise settles.
Does NOT block the thread — the event loop processes other tasks while waiting. Must be inside an async function (or
top-level module). Error handling: wrap in try/catch — cleaner than .catch() chains for sequential logic.
// WRONG — sequential (slow)
async function loadPageData() {
const user = await getUser(); // waits for user
const products = await getProducts(); // then waits for products
const cart = await getCart(); // then cart — total: t1+t2+t3
return { user, products, cart };
}

// CORRECT — concurrent (fast)


async function loadPageData() {
const [user, products, cart] = await [Link]([
getUser(), getProducts(), getCart() // all fire at once
]);
return { user, products, cart }; // total: max(t1,t2,t3)
}

// await inside loops — use for...of not forEach


async function processOrders(orderIds) {
const results = [];
for (const id of orderIds) { // for...of supports await
[Link](await processOrder(id));
}
// Or concurrent: await [Link]([Link](processOrder));
return results;
}

■ Follow-up: What is the most common async/await performance mistake?


Sequential awaits when you could run things concurrently: const user = await getUser(); const orders = await
getOrders(); — two sequential requests, total time = t1 + t2. Fix: const [user, orders] = await [Link]([getUser(),
getOrders()]); — concurrent, total time = max(t1, t2). The mistake is treating every await as blocking the others. Only
await serially when the second depends on the first. Another mistake: using await inside forEach — forEach doesn't
await the callback. Use for...of loop or [Link]([Link](async item => ...)).

■ Follow-up: How do you handle errors for multiple independent async operations?
If using [Link] and one fails, the whole thing rejects — you lose all results. Use [Link]: const results =
await [Link]([op1(), op2(), op3()]); Then filter: const succeeded = [Link](r => [Link] ===
"fulfilled").map(r => [Link]); Or wrap each individual operation: const safe = async (p) => { try { return {ok:true, value:
await p} } catch(e) { return {ok:false, error:e} } }; await [Link]([op1, op2].map(safe));

■ Section 6: Browser APIs — Storage, Forms, Web APIs

Q17. What are the browser storage options? Compare localStorage, sessionStorage,
and cookies.
■ Topic: Browser Storage
● Easy — must answer cold

■ Model Answer:
localStorage: stores key-value strings persistently — survives browser close. Same origin only. ~5MB. sessionStorage:
same API but cleared when the TAB is closed. Not shared across tabs. Cookies: server-readable, sent with every HTTP
request to the matching domain. Can set expiry, HttpOnly (no JS access), Secure (HTTPS only), SameSite. ~4KB.
IndexedDB: full database in the browser — structured data, large storage, async API.
// localStorage — persist cart across sessions
const CART_KEY = "shopping_cart";

function saveCart(cart) {
try {
[Link](CART_KEY, [Link](cart));
} catch (e) {
[Link]("Storage quota exceeded:", e);
}
}

function loadCart() {
const raw = [Link](CART_KEY);
return raw ? [Link](raw) : [];
}

// Cross-tab storage sync


[Link]("storage", (e) => {
if ([Link] === CART_KEY) {
updateCartUI([Link]([Link]));
}
});

// sessionStorage — per-tab data


[Link]("checkoutStep", "3");
const step = parseInt([Link]("checkoutStep") || "1");

■ Follow-up: When should you use localStorage vs cookies for auth tokens?
Never store sensitive auth tokens in localStorage — any XSS attack can read it: [Link] or
[Link](). Store auth tokens in HttpOnly cookies — JavaScript cannot read them, even during XSS. JWT
access token: can store in memory (JS variable) — lost on page refresh but safe. Refresh token: store in HttpOnly,
Secure, SameSite=Strict cookie — server sets it, JS never sees it. localStorage is fine for: theme preference, UI state,
shopping cart (non-sensitive), language preference.

■ Follow-up: What is the Web Storage API and how do you use it correctly?
[Link]("key", value): value MUST be a string — store objects with [Link].
[Link]("key"): returns string or null — parse with [Link]. [Link]("key"): remove
one item. [Link](): remove all items for this origin. Storage quota: ~5–10MB depending on browser.
Exceeding throws QuotaExceededError — always wrap in try/catch. Storage event: fires in other tabs/windows when
localStorage changes — enables cross-tab sync.

Q18. How do you handle forms in JavaScript? Explain validation and FormData.
■ Topic: Forms & DOM
● Medium — should know well

■ Model Answer:
Form handling: listen to the submit event, call [Link]() to stop page reload, then read values and validate, then
submit via fetch. FormData API: creates key-value pairs from a form — handles text, files, checkboxes automatically.
HTML5 validation attributes: required, minlength, maxlength, pattern, type="email". Custom validation:
[Link](message) and the invalid event.
const form = [Link]("#checkout-form");

[Link]("submit", async (e) => {


[Link]();

// Collect and validate


const errors = validateForm(form);
if ([Link]) { showErrors(errors); return; }

// FormData — handles all inputs including files


const fd = new FormData(form);
[Link]("timestamp", [Link]());

try {
const res = await fetch("/api/checkout/", {
method: "POST",
headers: { "Authorization": `Bearer ${token}` },
body: fd // NO Content-Type header — fetch sets it
});
if (![Link]) throw new Error(await [Link]());
[Link] = "/order-success";
} catch (err) {
showError("Submission failed: " + [Link]);
}
});

function validateForm(form) {
const errors = [];
const email = [Link]();
if (!email) [Link]("Email is required");
else if (!/^[^@]+@[^@]+\.[^@]+$/.test(email))
[Link]("Invalid email format");
return errors;
}

■ Follow-up: How do you validate a form before submission?


HTML5 native: [Link]() returns false if any field fails HTML5 constraints. [Link]() shows
browser-native error UI. Custom JS validation: check each field, collect errors, show them in the UI. Pattern: onSubmit,
collect all errors; if any, show them and return without submitting. Show errors inline next to fields (not just an alert).
Real-time validation: listen to the "input" or "blur" event on each field for immediate feedback. Never rely on client-side
validation alone — always validate server-side too.

■ Follow-up: How does FormData work with file uploads?


const fd = new FormData(formElement): auto-populates from all named inputs including files. [Link]("key", value):
add extra fields. [Link]("fieldName"): read a value. For file inputs: [Link]("avatar") returns a File object (extends Blob).
Send with fetch: body: formData — DO NOT set Content-Type header manually (fetch sets it with the correct multipart
boundary automatically). Track upload progress: use XMLHttpRequest with the progress event — fetch doesn't
support upload progress natively (yet).

Q19. What is the Intersection Observer API? What is it used for?


■ Topic: Browser APIs
● Medium — should know well

■ Model Answer:
Intersection Observer: efficiently detects when an element enters or exits the viewport (or another element). Replaces the
old pattern of listening to scroll events + getBoundingClientRect() — which is expensive (causes layout reflow on every
scroll). IO uses the browser's rendering thread — no layout reflow, no main thread blocking. Use cases: lazy loading
images, infinite scroll, animating elements on scroll-in, tracking ad visibility.

// Lazy load images


const lazyImages = [Link]("img[data-src]");

const imageObserver = new IntersectionObserver((entries, observer) => {


[Link](entry => {
if ([Link]) {
const img = [Link];
[Link] = [Link];
[Link]("data-src");
[Link](img); // stop watching loaded images
}
});
}, {
rootMargin: "200px", // start loading 200px before entering viewport
threshold: 0 // trigger as soon as any pixel is visible
});

[Link](img => [Link](img));

// Animate on scroll-in
const animObserver = new IntersectionObserver((entries) => {
[Link](({ target, isIntersecting }) => {
[Link]("visible", isIntersecting);
});
}, { threshold: 0.1 });

[Link](".animate-on-scroll")
.forEach(el => [Link](el));

■ Follow-up: How is Intersection Observer better than a scroll event listener?


scroll event: fires many times per second during scrolling. Each getBoundingClientRect() call forces the browser to
recalculate layout (layout thrashing). Causes jank and slow pages. Intersection Observer: asynchronous — the
browser batches and reports intersection changes efficiently on the rendering thread. No getBoundingClientRect(). No
scroll listener. Observe hundreds of elements with one observer — no performance penalty. threshold option: [0, 0.25,
0.5, 1.0] — callback fires at these percentages of element visibility.

■ Follow-up: How do you implement lazy loading images with Intersection Observer?
Store real image URLs in data-src attribute. Set src to a placeholder. const observer = new
IntersectionObserver((entries, obs) => { [Link](entry => { if ([Link]) { [Link] =
[Link]; [Link]([Link]); } }); }); [Link]("img[data-src]").forEach(img
=> [Link](img)); When the image scrolls into view, set src from data-src and stop observing it. Modern
HTML also has loading="lazy" attribute — browser-native lazy loading without JS.

■ Section 7: JS Patterns, Performance & Web Best Practices

Q20. What is debouncing and throttling? When do you use each?


■ Topic: Performance Patterns
● Medium — should know well

■ Model Answer:
Debouncing: delays execution until AFTER a pause in events. If the event fires again before the delay, the timer resets.
Use for: search-as-you-type (wait until user stops typing), window resize handler, form validation on input. Throttling:
allows execution at most ONCE per time interval — guarantees regular execution. Use for: scroll events, mouse move,
game loop, rate-limiting API calls.

// Debounce — delay until pause


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

const searchInput = [Link]("#search");


[Link]("input",
debounce(async (e) => {
const results = await searchProducts([Link]);
renderResults(results);
}, 400)
);

// Throttle — at most once per interval


function throttle(fn, interval = 200) {
let lastCall = 0;
return function (...args) {
const now = [Link]();
if (now - lastCall >= interval) {
lastCall = now;
return [Link](this, args);
}
};
}

[Link]("scroll",
throttle(() => updateScrollProgress(), 100)
);

■ Follow-up: How do you implement debounce from scratch?


function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() =>
[Link](this, args), delay); }; } const debouncedSearch = debounce(searchProducts, 300);
[Link]("input", debouncedSearch); Each keystroke clears the previous timer. The API call only fires
300ms after the user stops typing. [Link] and [Link] provide battle-tested implementations with
leading/trailing options.

■ Follow-up: What is requestAnimationFrame and when should you use it?


requestAnimationFrame(callback): schedules the callback to run before the NEXT browser repaint — typically 60 times
per second (60fps). Use for: smooth animations, canvas drawing, DOM updates that should be visually smooth. Never
use setInterval for animations — it ignores the browser's paint cycle, causing jank. Pattern: function animate() {
updateState(); render(); requestAnimationFrame(animate); } requestAnimationFrame(animate);
cancelAnimationFrame(id): stop the loop. The callback is automatically paused when the tab is hidden (battery saving).

Q21. What is the difference between synchronous and asynchronous code? What is
a callback?
■ Topic: Async Fundamentals
● Easy — must answer cold
■ Model Answer:
Synchronous: code executes line by line — each line waits for the previous to complete. Blocks the thread.
Asynchronous: operations are started and the program continues — a callback is registered to handle the result when
ready. Callback: a function passed as an argument to another function, to be called later when an async operation
completes. Callbacks are the original async mechanism in JS — later replaced by Promises, then async/await.

■ Follow-up: What is "callback hell" and how do Promises solve it?


Callback hell: deeply nested callbacks for sequential async operations — hard to read, error-handle, and maintain.
loadUser(id, (user) => { loadOrders([Link], (orders) => { loadDetails(orders[0].id, (detail) => { ... }); }); }); Problems:
pyramid of doom shape, error handling at every level, hard to break out or add logic. Promises: flatten the chain —
loadUser(id).then(user => loadOrders([Link])).then(orders => loadDetails(orders[0].id)).catch(handleAllErrors).
async/await: reads like synchronous code. One try/catch handles all errors.

■ Follow-up: What is the difference between microtasks and macrotasks?


Macrotasks (Task Queue): setTimeout, setInterval, setImmediate, I/O callbacks, DOM events — scheduled and run
one per event loop iteration. Microtasks (Microtask Queue): Promise .then()/.catch()/.finally() callbacks,
queueMicrotask(), MutationObserver — run ALL pending microtasks before the next macrotask. Order: current
synchronous code → ALL microtasks → ONE macrotask → ALL microtasks → ... Implication: chained .then() callbacks
all run before any setTimeout callback, even setTimeout(fn, 0). Infinite Promise chain (like a while loop in microtasks)
would starve the event loop.

Q22. What is CORS? How does the browser enforce it and how do you fix CORS
errors?
■ Topic: Web Security
● Medium — should know well

■ Model Answer:
CORS (Cross-Origin Resource Sharing): a browser security mechanism that restricts web pages from making requests to
a DIFFERENT origin (protocol + domain + port). The browser sends an Origin header. The server must respond with
Access-Control-Allow-Origin matching that origin (or *). Without this header: browser blocks the response — CORS error.
The request WAS sent and processed — just the response is blocked. For complex requests (non-GET, custom
headers): browser sends a preflight OPTIONS request first.
// CORS error in browser console:
// "Access to fetch at "[Link] from origin
// "[Link] has been blocked by CORS policy"

// What the server must respond with:


// Access-Control-Allow-Origin: [Link]
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: Content-Type, Authorization

// Django fix — django-cors-headers


// [Link]
// INSTALLED_APPS = [..., "corsheaders"]
// MIDDLEWARE = ["[Link]", ...]
// CORS_ALLOWED_ORIGINS = ["[Link] "[Link]

// Preflight — browser sends OPTIONS before actual request


// Only for: non-simple methods (PUT, DELETE, PATCH)
// or custom headers (Authorization, Content-Type: application/json)

// Dev workaround — proxy in vite/webpack dev server


// [Link]: server: { proxy: { "/api": "[Link] }}
// Now browser thinks API is same origin — no CORS

■ Follow-up: Why does CORS only affect browsers and not curl or Postman?
CORS is enforced by the BROWSER — it's a browser security feature protecting users. curl, Postman,
server-to-server calls have no browser — no CORS enforcement. This is why your Django API might work perfectly in
Postman but fail in the browser. It's also why CORS is not a complete security measure — it only prevents cross-origin
browser requests from accessing responses. Fix CORS in Django: install django-cors-headers. Add to
INSTALLED_APPS, MIDDLEWARE, and set CORS_ALLOWED_ORIGINS = ["[Link]

■ Follow-up: What is the Same-Origin Policy?


Same-Origin Policy (SOP): browsers prevent JavaScript from reading responses from a different origin than the page
it's running on. Origin = protocol + hostname + port. [Link] and [Link] are DIFFERENT origins
(different subdomain). SOP blocks: fetch/XMLHttpRequest to other origins, accessing cookies from other origins,
iframe content from other origins. CORS is the controlled relaxation of SOP — servers explicitly opt-in to allow specific
origins. JSONP: old hack to bypass SOP using script tags (no SOP for scripts). Insecure — never use in new code.

Q23. What is the difference between localStorage and the browser's sessionStorage
and why does it matter for security?
■ Topic: Browser Storage / Security
● Medium — should know well

■ Model Answer:
This is a summary security-focused question. Both localStorage and sessionStorage are accessible via JavaScript
(document — i.e., any script on the page). XSS (Cross-Site Scripting): if an attacker injects malicious JS into your page,
they can read EVERYTHING in localStorage and sessionStorage — including auth tokens. HttpOnly cookies: cannot be
read by JS at all — safe from XSS token theft. Best practice: store auth tokens in HttpOnly cookies (set by server), use
localStorage only for non-sensitive UI state.
■ Follow-up: How do you protect your web app from XSS?
1. Never set innerHTML with user-controlled data — use textContent or [Link](). 2. Content Security
Policy (CSP): HTTP header that whitelists script sources — blocks inline scripts and unauthorized external scripts.
Content-Security-Policy: default-src "self"; script-src "self" [Link] — even if XSS injects a script tag,
the browser refuses to execute it. 3. Use HttpOnly cookies for auth — even a full XSS exploit can't steal the token. 4.
Escape user content on the server before rendering. 5. Use a framework (React, Vue) — they escape by default.

■ Follow-up: What is CSRF and how is it different from XSS?


XSS: attacker injects malicious JS INTO your site — runs AS your site, reads your data. Prevented by: escaping
output, CSP, HttpOnly cookies. CSRF: attacker tricks a USER's browser into making a request TO your site — using
the user's session cookie (sent automatically). CSRF prevention: CSRF tokens (secret value in forms verified by
server), SameSite=Strict cookie attribute (cookie not sent for cross-site requests). JWT in Authorization header: not
vulnerable to CSRF — browsers don't auto-send Authorization headers cross-site. JWT in cookies: vulnerable to
CSRF — must add SameSite=Strict or CSRF token.

■ Rapid-Fire JavaScript Answers

Know these cold — under 30 seconds each.

Question Answer

typeof null? "object" — a historic JavaScript bug that cannot be fixed for backward
compatibility. Check for null with value === null.

undefined vs null? undefined: variable declared but not assigned. null: intentionally empty
value — explicitly set. typeof undefined = "undefined", typeof null = "object".

What is NaN? Not a Number — result of invalid numeric operations. NaN !== NaN (only
value not equal to itself). Use [Link]() to check.

== vs ===? === strict: checks value AND type, no coercion. == loose: coerces types
before comparing. Always use ===.

What is an IIFE? Immediately Invoked Function Expression: (function() { ... })(); — runs
immediately. Creates a new scope. Used to avoid polluting the global
scope.

What is event bubbling? Events propagate UP from the target element to the root. Parent listeners
receive events from children. Use stopPropagation() to stop.

What does preventDefault() do? Stops the default browser behaviour (form submit, link navigation, right-click
menu). Does NOT stop event bubbling.

What is the difference between null null: deliberate absence of value (you set it). undefined: JS sets it when
and undefined? variable is declared but not assigned, or property doesn't exist.

What is a Promise? Object representing an eventual async result. Three states: pending,
fulfilled, rejected. .then()/.catch()/.finally() for handling.
What is async/await? Syntactic sugar over Promises. async function always returns a Promise.
await pauses the function (not thread) until Promise resolves.

What is the DOM? Document Object Model — browser's tree representation of the HTML
document. JS reads/modifies it to make pages interactive.

querySelectorAll returns what? A static NodeList — like an array but not an array. Convert to array:
[Link](nodeList) or [...nodeList].

What is event delegation? Attach one listener to a parent, use [Link] to know which child was
clicked. Works for dynamically added elements too.

What is localStorage vs localStorage: persists across sessions, all tabs. sessionStorage: per-tab,
sessionStorage? cleared when tab closes.

What is [Link] / [Link]? stringify: JS object to JSON string. parse: JSON string to JS object.
Functions, undefined, and symbols are lost during stringify.

What is the spread operator? [...arr] copies array. {...obj} copies object. func(...args) expands array as
arguments. Always creates a shallow copy.

What is optional chaining ?.? user?.address?.city — returns undefined if any link in the chain is
null/undefined instead of throwing TypeError.

What is nullish coalescing ?? a ?? b — returns b only if a is null or undefined. Unlike ||, it doesn't trigger
for 0, "", or false.

What is the difference between map map: returns a NEW array with transformed values. forEach: returns
and forEach? undefined, used for side effects only.

What is CORS? Browser security: blocks responses from different origins. Server must send
Access-Control-Allow-Origin header. Only enforced in browsers.

■ 2-Day JavaScript Power Study Plan

When Study Goal

Day 1 Morning 3 Section 1: Fundamentals — var/let/const, hoisting, type Type coercion table from memory.
hrs coercion, event loop. Q1–3. Draw the event loop diagram. Know
TDZ cold.

Day 1 Afternoon 3 Sections 2 & 3: ES6+ features, closures, "this", DOM + Build a product card with JS: create,
hrs events. Q4–12. style, append, add click handler. Use
delegation.

Day 1 Evening 1.5 Rapid-fire: all 20 cold. 30s each. Focus on typeof null, These catch freshers out. Know them
hrs undefined vs null, == vs ===. instantly.

Day 2 Morning 2.5 Sections 4 & 5: JS OOP (prototypes, classes), Write a class with private fields and a
hrs Promises, async/await. Q13–16. static factory method. Fetch API with
proper error handling.
Day 2 Afternoon 2 Sections 6 & 7: Storage, Forms, CORS, debounce, Implement a search with debounce.
hrs security. Q17–23. Explain CORS in your own words.
Explain XSS vs CSRF.

Day 2 Evening 1 Mock: 10 questions, 2 min each. Write code on paper. The event loop, closures, and "this"
hr Explain event loop aloud. are the top 3 most-asked JS topics.
Nail them.

Interview Day Re-read rapid-fire only. Have 2 JS code examples ready When asked to code — think aloud.
from your project. Interviewers want to see HOW you
think.

You've got this. ■ The top 3 JS topics interviewers always ask freshers: "Explain closures," "What is the event loop," and
"How does this work." Nail those three and you've already impressed. When you code live — always talk through your
thinking: "I'm using const here because this won't be reassigned", "I'm using async/await instead of .then() chaining for
readability." That commentary is what separates a 5 LPA from a 8 LPA candidate. One last tip: practise typing JS code
without autocomplete — interviewers will ask you to write it live.

You might also like