[Go to site: main page, start]

0% found this document useful (0 votes)
3 views108 pages

JavaScript Interview Handbook

The document is a comprehensive JavaScript interview handbook containing over 500 questions and answers, structured from beginner to advanced levels. It covers various topics including JavaScript fundamentals, ES6+ features, asynchronous JavaScript, and interview tips, along with coding challenges and a 30-day revision plan. The content is designed for students, freshers, self-taught developers, and experienced engineers preparing for JavaScript interviews.
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)
3 views108 pages

JavaScript Interview Handbook

The document is a comprehensive JavaScript interview handbook containing over 500 questions and answers, structured from beginner to advanced levels. It covers various topics including JavaScript fundamentals, ES6+ features, asynchronous JavaScript, and interview tips, along with coding challenges and a 30-day revision plan. The content is designed for students, freshers, self-taught developers, and experienced engineers preparing for JavaScript interviews.
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

JAVA S C R I P T · I N T E RV I E W P R E P

The Complete
JavaScript
Interview Handbook
Beginner to Advanced
500+ JavaScript Interview Questions with Answers

For students, freshers, self-taught developers & experienced engineers


Table of Contents

1. JavaScript Fundamentals (Beginner) 3

2. Intermediate JavaScript 14

3. Advanced JavaScript 33

4. Coding Challenges 47

5. JavaScript Output Questions 57

6. ES6+ Features 66

7. Browser & DOM Questions 73

8. Asynchronous JavaScript 80

9. React-Related JavaScript Questions 88

10. Interview Tips 96

11. Memory Tricks & Cheat Sheets 99

12. 30-Day Revision Plan 103

Final Cheat Sheet 106

2
01
JavaScript Fundamentals
Beginner Questions

Core building blocks every JavaScript developer must know: variables, data types, operators, functions,
scope, hoisting, arrays, objects, loops, conditionals, type conversion, and string/array methods. This
section presents the most frequently asked beginner-level interview questions with clear explanations,
example code, and interview-ready answers.

Q1: What is the difference between var, let, and const?

ANSWER

var is function-scoped (or globally scoped) and can be re-declared and updated. It is also hoisted and initialized with
undefined. let is block-scoped, can be updated but not re-declared in the same scope, and is hoisted but not
initialized (it sits in the "temporal dead zone"). const is also block-scoped like let, but its binding cannot be
reassigned after declaration — though objects/arrays assigned to a const can still have their contents mutated.

EXAMPLE CODE

var a = 1;
if (true) {
var a = 2; // same variable, function/global scoped
let b = 3; // block scoped
const c = 4; // block scoped, cannot reassign
}
[Link](a); // 2
// b and c are not accessible here

Interview-ready answer: I'd say var is outdated and rarely used in modern code because of its function-scoping and
hoisting quirks. I default to const for values that won't be reassigned, and use let only when I know the variable's
value needs to change.

3
Q2: What are the different data types in JavaScript?

ANSWER

JavaScript has two categories of data types: primitives and objects (reference types). The primitive types are String,
Number, Boolean, null, undefined, Symbol, and BigInt. Everything else — objects, arrays, functions, and
dates — is of type object (functions report as "function" from typeof but are technically objects).

EXAMPLE CODE

typeof "hello"; // "string"


typeof 42; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (a well-known quirk)
typeof Symbol(); // "symbol"
typeof 10n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof function(){}; // "function"

Q3: What is the difference between == and ===?

ANSWER

== is the loose equality operator. It performs type coercion before comparing two values, converting them to a
common type. === is the strict equality operator; it compares both value and type without any coercion. Using === is
generally recommended to avoid unexpected bugs caused by implicit type conversion.

EXAMPLE CODE

"5" == 5; // true (string coerced to number)


"5" === 5; // false (different types)
null == undefined; // true
null === undefined; // false
0 == false; // true
0 === false; // false

Interview-ready answer: I always default to === for predictable comparisons, and only rely on == in rare, well-
understood cases like checking for both null and undefined in one go.

4
Q4: What is hoisting in JavaScript?

ANSWER

Hoisting is JavaScript's default behavior of moving declarations to the top of their containing scope during the
compile phase, before code execution. Function declarations are hoisted completely (including their body), so they
can be called before they appear in the code. var declarations are hoisted and initialized to undefined. let and
const are hoisted but remain uninitialized in the "temporal dead zone" until their declaration line is executed.

EXAMPLE CODE

[Link](a); // undefined (hoisted, not yet assigned)


var a = 5;

[Link](b); // ReferenceError: Cannot access 'b' before initialization


let b = 10;

greet(); // works fine


function greet() { [Link]("hi"); }

Q5: What is the difference between function declarations and function


expressions?

ANSWER

A function declaration defines a named function using the function keyword as a statement, and it is fully hoisted,
meaning it can be called before its definition appears in the code. A function expression assigns a function (named or
anonymous) to a variable; only the variable declaration is hoisted (if using var), not the function definition, so it
cannot be called before the assignment.

EXAMPLE CODE

// Declaration - hoisted
function add(a, b) { return a + b; }

// Expression - not hoisted


const subtract = function (a, b) { return a - b; };

// Arrow function expression


const multiply = (a, b) => a * b;

5
Q6: What is scope in JavaScript and what are its types?

ANSWER

Scope determines the accessibility/visibility of variables. JavaScript has three main types of scope: Global scope —
variables declared outside any function or block, accessible everywhere. Function scope — variables declared with
var inside a function are accessible only within that function. Block scope — variables declared with let or const
inside {} (e.g. an if block or loop) are accessible only within that block.

EXAMPLE CODE

function demo() {
var x = 1; // function scoped
if (true) {
let y = 2; // block scoped
var z = 3; // function scoped (leaks out of the if block)
}
[Link](x, z); // 1, 3
// [Link](y); ReferenceError
}

Q7: What is the difference between null and undefined?

ANSWER

undefined means a variable has been declared but has not yet been assigned a value — it is the default value
JavaScript assigns automatically. null is an assignment value that represents the intentional absence of any object
value; a developer explicitly sets a variable to null to indicate "no value". Both are falsy, and typeof
undefined is "undefined" while typeof null is "object" (a long-standing language quirk).

EXAMPLE CODE

let a;
[Link](a); // undefined

let b = null;
[Link](b); // null

[Link](a == b); // true


[Link](a === b); // false

6
Q8: What are template literals and how are they different from regular strings?

ANSWER

Template literals are string literals enclosed by backticks (`) instead of single or double quotes. They support
embedded expressions using ${...} syntax (string interpolation), allow multi-line strings without escape characters,
and can be used with "tagged templates" for advanced string processing.

EXAMPLE CODE

const name = "Alice";


const age = 25;

// Old way
const msg1 = "Hello, " + name + "! You are " + age + " years old.";

// Template literal
const msg2 = `Hello, ${name}! You are ${age} years old.`;

// Multi-line
const html = `<div>
<p>${name}</p>
</div>`;

Q9: Explain the concept of truthy and falsy values in JavaScript.

ANSWER

In JavaScript, every value is inherently either "truthy" or "falsy" when evaluated in a Boolean context (like an if
statement). There are exactly six falsy values: false, 0, "" (empty string), null, undefined, and NaN.
Everything else — including "0", "false", empty arrays [], and empty objects {} — is truthy.

EXAMPLE CODE

if (0) [Link]("a"); // skipped


if ("") [Link]("b"); // skipped
if ([]) [Link]("c"); // runs! empty array is truthy
if ({}) [Link]("d"); // runs! empty object is truthy
if ("0") [Link]("e"); // runs! non-empty string is truthy

7
Q10: What is the difference between slice(), splice(), and split() methods?

ANSWER

slice(start, end) returns a shallow copy of a portion of an array or string without modifying the original.
splice(start, deleteCount, ...items) changes the contents of an array by removing or replacing
existing elements and/or adding new elements in place (it mutates the original array). split(separator) is a
string method that divides a string into an array of substrings based on a separator.

EXAMPLE CODE

const arr = [1, 2, 3, 4, 5];

[Link]([Link](1, 3)); // [2, 3] - arr unchanged


[Link](arr); // [1, 2, 3, 4, 5]

[Link]([Link](1, 2)); // [2, 3] - removed items


[Link](arr); // [1, 4, 5] - arr mutated!

const str = "a,b,c";


[Link]([Link](",")); // ["a", "b", "c"]

Q11: How does the for...in loop differ from the for...of loop?

ANSWER

for...in iterates over the enumerable property keys of an object (including arrays, where keys are indices as
strings). It is best suited for plain objects. for...of iterates over the values of an iterable (arrays, strings, Maps,
Sets, etc.) and is generally preferred for arrays and other iterables because it gives direct access to the values.

EXAMPLE CODE

const arr = ["a", "b", "c"];

for (let i in arr) {


[Link](i); // "0", "1", "2" (keys/indices as strings)
}

for (let v of arr) {


[Link](v); // "a", "b", "c" (values)
}

8
Q12: What is type coercion? Give examples of implicit coercion.

ANSWER

Type coercion is the automatic or implicit conversion of values from one data type to another, such as converting a
string to a number. JavaScript performs implicit coercion in operations like comparisons (==), arithmetic with mixed
types (especially the + operator), and Boolean contexts. Explicit coercion is done manually using functions like
Number(), String(), or Boolean().

EXAMPLE CODE

"5" + 1; // "51" (number coerced to string, concatenation)


"5" - 1; // 4 (string coerced to number, subtraction)
"5" * "2"; // 10 (both coerced to numbers)
true + 1; // 2 (true coerced to 1)
[] + []; // "" (both arrays coerced to empty strings)
[] + {}; // "[object Object]"

Q13: What are arrow functions and how do they differ from regular functions?

ANSWER

Arrow functions, introduced in ES6, provide a shorter syntax for writing function expressions using =>. The most
important difference is that arrow functions do not have their own this binding — they inherit this from the
enclosing lexical scope. They also cannot be used as constructors (no new), do not have their own arguments
object, and cannot use yield (no generator arrow functions).

EXAMPLE CODE

const obj = {
name: "Alice",
regularFn: function () {
[Link]([Link]); // "Alice"
},
arrowFn: () => {
[Link]([Link]); // undefined (this = outer scope, e.g. window)
}
};
[Link]();
[Link]();

9
Q14: How do you create and access object properties in JavaScript?

ANSWER

Objects can be created using object literals {}, the new Object() constructor, or [Link]().
Properties can be accessed via dot notation ([Link]) or bracket notation (obj["key"]). Bracket notation is
required when the property name is dynamic, contains special characters, or is stored in a variable.

EXAMPLE CODE

const person = {
name: "Bob",
"favorite color": "blue"
};

[Link]([Link]); // "Bob"
[Link](person["favorite color"]); // "blue"

const key = "name";


[Link](person[key]); // "Bob" (dynamic access)

[Link] = 30; // adding a new property

Q15: What is the difference between map(), forEach(), and filter()?

ANSWER

forEach() executes a callback for each array element and always returns undefined — it is used purely for side
effects. map() executes a callback for each element and returns a new array of the same length containing the
results of the callback. filter() executes a callback and returns a new array containing only the elements for
which the callback returned a truthy value.

EXAMPLE CODE

const nums = [1, 2, 3, 4, 5];

[Link](n => [Link](n * 2)); // logs 2,4,6,8,10, returns undefined

const doubled = [Link](n => n * 2);


[Link](doubled); // [2, 4, 6, 8, 10]

const evens = [Link](n => n % 2 === 0);


[Link](evens); // [2, 4]

10
Q16: Explain the concept of immediately invoked function expressions (IIFE).

ANSWER

An IIFE is a function that is defined and executed immediately after its creation. It is commonly used to create a
private scope, avoiding polluting the global namespace, and was a common pattern for module-like encapsulation
before ES6 modules and block-scoped variables existed.

EXAMPLE CODE

(function () {
const privateVar = "I'm private";
[Link](privateVar);
})();

// Arrow function IIFE


(() => {
[Link]("Executed immediately");
})();

Q17: What is NaN and how do you check if a value is NaN?

ANSWER

NaN stands for "Not a Number" and represents a value that is not a legal number, typically the result of an invalid or
undefined mathematical operation (e.g. 0/0 or parseInt("abc")). A tricky property of NaN is that it is the only
value in JavaScript that is not equal to itself. To reliably check for NaN, use [Link]() rather than the
global isNaN(), since the global version coerces its argument first.

EXAMPLE CODE

[Link](NaN === NaN); // false


[Link](0 / 0); // NaN
[Link](isNaN("hello")); // true (coerces "hello" to NaN first)
[Link]([Link]("hello")); // false (no coercion, "hello" is not NaN type)
[Link]([Link](NaN)); // true

11
Q18: What are default parameters in functions?

ANSWER

Default parameters allow function parameters to be initialized with default values if no value or undefined is
passed when the function is called. This avoids the older pattern of manually checking for undefined inside the
function body.

EXAMPLE CODE

function greet(name = "Guest", greeting = "Hello") {


return `${greeting}, ${name}!`;
}

[Link](greet()); // "Hello, Guest!"


[Link](greet("Alice")); // "Hello, Alice!"
[Link](greet("Bob", "Welcome")); // "Welcome, Bob!"
[Link](greet(undefined, "Hi")); // "Hi, Guest!"

Q19: What is the difference between a shallow copy and creating a reference?

ANSWER

When you assign an object or array to another variable directly (const b = a), both variables point to the same
object in memory — mutating one affects the other. A shallow copy creates a new object/array at the top level (using
[Link](), the spread operator {...a}, or [Link]()), but nested objects/arrays
inside it are still shared references with the original.

EXAMPLE CODE

const original = { name: "Alice", address: { city: "NYC" } };

const ref = original; // same reference


[Link] = "Bob";
[Link]([Link]); // "Bob" - affected!

const shallow = { ...original }; // shallow copy


[Link] = "Carol";
[Link]([Link]); // "Bob" - not affected

[Link] = "LA";
[Link]([Link]); // "LA" - nested object still shared!

12
Q20: What is the purpose of the 'use strict' directive?

ANSWER

'use strict' enables strict mode, which is a way to opt in to a restricted variant of JavaScript that catches
common coding mistakes and "unsafe" actions. In strict mode: assigning to an undeclared variable throws an error,
duplicate parameter names are disallowed, this is undefined (instead of the global object) in standalone function
calls, and certain reserved words cannot be used as variable names. Code inside ES6 modules and classes is
automatically in strict mode.

EXAMPLE CODE

"use strict";

x = 10; // ReferenceError: x is not defined (would silently create a global otherwise)

function test() {
[Link](this); // undefined in strict mode (vs. window in non-strict)
}
test();

13
02
Intermediate JavaScript
Closures, Async Basics, OOP & Modern Syntax

This section moves into the concepts that separate beginners from confident intermediate developers:
closures, callbacks, promises, async/await, the event loop, prototypes, the this keyword, classes,
inheritance, modules, error handling, destructuring, and the spread/rest syntax. Each answer includes real-
world context and likely follow-up questions.

14
Q21: What is a closure? Provide a real-world example.

ANSWER

A closure is the combination of a function bundled together with references to its surrounding state (the lexical
environment). In other words, a closure gives a function access to its outer function's scope even after the outer
function has returned. Closures are created every time a function is created, and they are commonly used to create
private variables, implement memoization, and build function factories.

EXAMPLE CODE

function createCounter() {
let count = 0; // private variable
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}

const counter = createCounter();


[Link]();
[Link]();
[Link]([Link]()); // 2
// 'count' cannot be accessed directly from outside

Interview-ready answer: I'd describe a closure as a function that "remembers" the variables from the scope it was
created in, even after that scope has finished executing. I use them constantly for data privacy and for creating
configurable functions like event handlers with pre-set parameters.

Possible follow-up: Can you implement a function that returns a unique ID generator using a closure?

15
Q22: What is the difference between a callback function and a Promise?

ANSWER

A callback is simply a function passed as an argument to another function, to be executed after some operation
completes. While useful, deeply nested callbacks lead to "callback hell" — hard-to-read, hard-to-maintain code. A
Promise is an object representing the eventual completion (or failure) of an asynchronous operation, with built-in
states (pending, fulfilled, rejected) and chaining via .then()/.catch(), which produces flatter, more
readable async code and better error handling.

EXAMPLE CODE

// Callback style
function getData(callback) {
setTimeout(() => callback(null, "data"), 1000);
}
getData((err, data) => {
if (err) return [Link](err);
[Link](data);
});

// Promise style
function getDataPromise() {
return new Promise((resolve) => {
setTimeout(() => resolve("data"), 1000);
});
}
getDataPromise().then(data => [Link](data)).catch([Link]);

16
Q23: Explain async/await and how it relates to Promises.

ANSWER

async/await is syntactic sugar built on top of Promises that allows asynchronous code to be written in a
synchronous-looking style. A function marked async always returns a Promise. Inside it, the await keyword
pauses execution of the function (without blocking the main thread) until the awaited Promise settles, then returns the
resolved value or throws the rejection reason — which can be caught with a regular try...catch block.

EXAMPLE CODE

function fetchUser(id) {
return new Promise(resolve =>
setTimeout(() => resolve({ id, name: "Alice" }), 500)
);
}

async function getUser() {


try {
const user = await fetchUser(1);
[Link](user); // { id: 1, name: "Alice" }
} catch (err) {
[Link]("Failed:", err);
}
}
getUser();

Possible follow-up: How would you run two async operations in parallel using async/await?

17
Q24: What is the event loop and how does JavaScript handle asynchronous code?

ANSWER

JavaScript is single-threaded, meaning it can execute only one piece of code at a time using the call stack. The event
loop is the mechanism that allows JavaScript to perform non-blocking operations: when an async operation (timer,
network request, etc.) completes, its callback is placed in a queue (the macrotask or microtask queue). The event loop
continuously checks if the call stack is empty, and if so, it pushes the next queued callback onto the stack to be
executed. Microtasks (Promise callbacks) are processed before macrotasks (setTimeout, setInterval).

EXAMPLE CODE

[Link]("1");

setTimeout(() => [Link]("2"), 0); // macrotask

[Link]().then(() => [Link]("3")); // microtask

[Link]("4");

// Output order: 1, 4, 3, 2
// Synchronous code runs first, then microtasks, then macrotasks

18
Q25: What is prototypal inheritance in JavaScript?

ANSWER

Every JavaScript object has an internal link to another object called its prototype. When you try to access a property
on an object and it doesn't exist on the object itself, JavaScript looks up the prototype chain until it finds the property
or reaches null. This is the mechanism behind inheritance in JavaScript — objects can inherit properties and
methods from other objects via this chain, rather than through classical class-based inheritance.

EXAMPLE CODE

const animal = {
eats: true,
walk() { [Link]("Animal walks"); }
};

const rabbit = [Link](animal);


[Link] = true;

[Link]([Link]); // true (inherited from animal)


[Link](); // "Animal walks" (inherited method)
[Link]([Link]('eats')); // false

19
Q26: How does the 'this' keyword behave in different contexts?

ANSWER

The value of this is determined by how a function is called, not where it's defined (except for arrow functions). In
a regular function called as a standalone function, this is undefined in strict mode (or the global object
otherwise). When a function is called as a method ([Link]()), this is the object before the dot. With call,
apply, or bind, this can be explicitly set. In an arrow function, this is inherited from the enclosing lexical
scope. In a constructor invoked with new, this refers to the newly created instance.

EXAMPLE CODE

const obj = {
name: "Alice",
regular: function () { return [Link]; },
arrow: () => this?.name
};

[Link]([Link]()); // "Alice"
[Link]([Link]()); // undefined (this = module/global scope)

const fn = [Link];
[Link](fn()); // undefined (called standalone, this is lost)

const bound = [Link](obj);


[Link](bound()); // "Alice"

20
Q27: How do you implement classes and inheritance in modern JavaScript?

ANSWER

ES6 introduced the class syntax, which is syntactic sugar over JavaScript's existing prototype-based inheritance.
Classes can have a constructor, instance methods, static methods, getters/setters, and private fields (using the #
prefix). The extends keyword sets up inheritance, and super() calls the parent constructor or methods.

EXAMPLE CODE

class Animal {
#sound; // private field
constructor(name, sound) {
[Link] = name;
this.#sound = sound;
}
speak() {
return `${[Link]} says ${this.#sound}`;
}
}

class Dog extends Animal {


constructor(name) {
super(name, "Woof");
}
fetch() {
return `${[Link]} fetches the ball`;
}
}

const dog = new Dog("Rex");


[Link]([Link]()); // "Rex says Woof"
[Link]([Link]()); // "Rex fetches the ball"

21
Q28: What are JavaScript modules and how do import/export work?

ANSWER

ES6 modules allow code to be split across multiple files, with explicit export and import statements controlling
what is shared between files. There are named exports (multiple per file, imported using their exact names or aliases)
and a single optional default export per file. Modules run in strict mode automatically, have their own scope (nothing
leaks to global by default), and are loaded asynchronously by the browser when using type="module".

EXAMPLE CODE

// [Link]
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default function multiply(a, b) { return a * b; }

// [Link]
import multiply, { PI, add } from './[Link]';
import * as MathUtils from './[Link]';

[Link](add(2, 3)); // 5
[Link](multiply(2, 3)); // 6

22
Q29: How does error handling work with try/catch/finally?

ANSWER

try wraps code that might throw an error. If an error occurs, control jumps to the catch block, which receives the
error object. The optional finally block runs regardless of whether an error occurred or not — commonly used for
cleanup. Custom errors can be thrown with throw new Error("message") or by extending the built-in
Error class.

EXAMPLE CODE

class ValidationError extends Error {


constructor(message) {
super(message);
[Link] = "ValidationError";
}
}

function validate(age) {
if (age < 0) throw new ValidationError("Age cannot be negative");
return age;
}

try {
validate(-5);
} catch (err) {
[Link](`${[Link]}: ${[Link]}`);
} finally {
[Link]("Validation attempted");
}

23
Q30: What is destructuring and how is it used with objects and arrays?

ANSWER

Destructuring is a syntax that allows unpacking values from arrays or properties from objects into distinct variables in
a concise way. It supports default values, renaming, nested destructuring, and combining with the rest operator to
collect remaining elements/properties.

EXAMPLE CODE

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

// Object destructuring with renaming and defaults


const { name: userName = "Guest", age } = { age: 25 };
[Link](userName, age); // "Guest" 25

// Nested destructuring
const { address: { city } } = { address: { city: "NYC", zip: "10001" } };
[Link](city); // "NYC"

// Function parameter destructuring


function printUser({ name, age }) {
[Link](`${name} is ${age}`);
}
printUser({ name: "Bob", age: 30 });

24
Q31: What is the difference between the spread operator and rest parameters?

ANSWER

Both use the ... syntax but serve opposite purposes. Spread "expands" an iterable (array, string, object) into
individual elements — used in function calls, array literals, and object literals to copy or merge. Rest "collects"
multiple remaining arguments or elements into a single array — used in function parameters and destructuring.

EXAMPLE CODE

// Spread - expanding
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1 };
const obj2 = { ...obj1, b: 2 }; // { a: 1, b: 2 }
[Link]([Link](...arr1)); // 3

// Rest - collecting
function sum(...numbers) { // numbers is an array
return [Link]((a, b) => a + b, 0);
}
[Link](sum(1, 2, 3, 4)); // 10

const [first, ...others] = [1, 2, 3];


[Link](others); // [2, 3]

25
Q32: What is the difference between call(), apply(), and bind()?

ANSWER

All three methods are used to explicitly set the value of this for a function. call(thisArg, arg1,
arg2, ...) invokes the function immediately with arguments passed individually. apply(thisArg,
[argsArray]) invokes the function immediately with arguments passed as an array. bind(thisArg,
arg1, ...) does not invoke the function — it returns a new function with this permanently bound, which can
be called later.

EXAMPLE CODE

const person = { name: "Alice" };

function greet(greeting, punctuation) {


return `${greeting}, ${[Link]}${punctuation}`;
}

[Link]([Link](person, "Hello", "!")); // "Hello, Alice!"


[Link]([Link](person, ["Hi", "?"])); // "Hi, Alice?"

const boundGreet = [Link](person, "Hey");


[Link](boundGreet(".")); // "Hey, Alice."

26
Q33: What are the differences between synchronous and asynchronous code
execution?

ANSWER

Synchronous code executes sequentially, line by line — each operation must finish before the next begins, blocking
further execution (and the UI in browsers) until completion. Asynchronous code allows long-running operations
(network requests, file I/O, timers) to run in the background without blocking the main thread; their results are
handled later via callbacks, Promises, or async/await, allowing the program to continue executing other code in the
meantime.

EXAMPLE CODE

// Synchronous - blocks
[Link]("Start");
for (let i = 0; i < 1e9; i++) {} // blocks everything
[Link]("End");

// Asynchronous - non-blocking
[Link]("Start");
setTimeout(() => [Link]("Async task"), 1000);
[Link]("End");
// Output: Start, End, Async task

27
Q34: What is memoization and how would you implement it?

ANSWER

Memoization is an optimization technique where the results of expensive function calls are cached, so that the next
time the same inputs occur, the cached result is returned instead of recomputing it. It's commonly implemented using
a closure that holds a cache object (often a Map) mapping serialized arguments to results.

EXAMPLE CODE

function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = [Link](args);
if ([Link](key)) {
[Link]("Cache hit");
return [Link](key);
}
const result = fn(...args);
[Link](key, result);
return result;
};
}

const slowSquare = (n) => { for(let i=0;i<1e6;i++); return n * n; };


const fastSquare = memoize(slowSquare);
fastSquare(5); // computes
fastSquare(5); // "Cache hit" - returns instantly

28
Q35: What is the difference between [Link]() and [Link]()?

ANSWER

[Link]() makes an object completely immutable: existing properties cannot be modified, added, or
removed, and the object cannot be reconfigured. [Link]() is less restrictive: it prevents new properties from
being added and existing properties from being removed/ reconfigured, but the values of existing writable properties
can still be changed.

EXAMPLE CODE

const frozen = [Link]({ a: 1 });


frozen.a = 2; // silently fails (throws in strict mode)
frozen.b = 3; // fails
[Link](frozen); // { a: 1 }

const sealed = [Link]({ a: 1 });


sealed.a = 2; // works
sealed.b = 3; // fails (cannot add)
[Link](sealed); // { a: 2 }

Q36: How do getters and setters work in JavaScript objects?

ANSWER

Getters (get) and setters (set) allow you to define object properties that are computed dynamically when accessed
or assigned, running custom logic behind the scenes while still using normal property syntax (no parentheses). They're
useful for validation, computed properties, and encapsulation.

EXAMPLE CODE

const person = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${[Link]} ${[Link]}`;
},
set fullName(value) {
[[Link], [Link]] = [Link](" ");
}
};

[Link]([Link]); // "John Doe" (getter called)


[Link] = "Jane Smith"; // setter called
[Link]([Link]); // "Jane"

29
Q37: What is the difference between deep equality and reference equality for
objects?

ANSWER

Reference equality (checked by ===) compares whether two variables point to the exact same object in memory —
two separately created objects with identical contents are not equal by reference. Deep equality compares the actual
contents of two objects/arrays recursively, regardless of whether they are the same object in memory. JavaScript has
no built-in deep equality operator; it must be implemented manually or via libraries like Lodash's isEqual.

EXAMPLE CODE

const a = { x: 1, y: { z: 2 } };
const b = { x: 1, y: { z: 2 } };
const c = a;

[Link](a === b); // false (different objects in memory)


[Link](a === c); // true (same reference)

// Simple deep equality (doesn't handle all edge cases)


function deepEqual(o1, o2) {
return [Link](o1) === [Link](o2);
}
[Link](deepEqual(a, b)); // true

Q38: How does optional chaining (?.) help with error handling?

ANSWER

Optional chaining (?.) allows reading the value of a property located deep within a chain of connected objects
without having to manually check that each reference in the chain is valid. If a reference is null/undefined, the
expression short-circuits and returns undefined instead of throwing a TypeError. It also works with function
calls ([Link]?.()) and array access (arr?.[0]).

EXAMPLE CODE

const user = { profile: { name: "Alice" } };

[Link]([Link]?.name); // "Alice"
[Link]([Link]?.city); // undefined (no error!)
[Link]([Link]?.notify?.()); // undefined, doesn't throw

// Without optional chaining:


// [Link]([Link]); // TypeError: Cannot read properties of undefined

30
Q39: What are higher-order functions? Give examples.

ANSWER

A higher-order function is a function that either takes one or more functions as arguments, returns a function, or both.
They are central to functional programming in JavaScript and enable powerful patterns like composition, currying,
and decorators. Built-in array methods like map, filter, and reduce are all higher-order functions.

EXAMPLE CODE

// Takes a function as an argument


function repeat(n, action) {
for (let i = 0; i < n; i++) action(i);
}
repeat(3, [Link]); // 0, 1, 2

// Returns a function
function multiplier(factor) {
return (num) => num * factor;
}
const double = multiplier(2);
[Link](double(5)); // 10

31
Q40: What is the difference between synchronous iteration and generators?

ANSWER

Regular functions return a single value and complete fully when called. Generator functions (defined with
function*) can pause their execution using yield and resume later, producing a sequence of values over time on
demand. They return an iterator object, and each call to .next() resumes execution until the next yield, making
them useful for lazy evaluation, custom iteration, and managing asynchronous flows.

EXAMPLE CODE

function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}

const gen = idGenerator();


[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
[Link]([Link]().value); // 3
// Execution pauses between each .next() call

32
03
Advanced JavaScript
Engine Internals & Senior-Level Concepts

This section covers the topics that distinguish senior engineers: execution context, the call stack, memory
management and garbage collection, event delegation, debouncing/throttling, currying, polyfills, deep vs.
shallow cloning, functional programming, generators/iterators, web workers, design patterns, and the
microtask/macrotask distinction.

Q41: What is an execution context and what does it contain?

ANSWER

An execution context is an abstract environment where JavaScript code is evaluated and executed. Every time a
function is called, a new execution context is created and pushed onto the call stack. Each execution context has three
main components: the Variable Environment (where var/function declarations and arguments live), the Lexical
Environment (where let/const bindings live, including a reference to the parent scope for closures), and the
ThisBinding (the value of this). There is always one Global Execution Context created first.

EXAMPLE CODE

function outer() {
let a = 1;
function inner() {
let b = 2;
[Link](a + b); // inner's context can access outer's via lexical env
}
inner();
}
outer(); // 3

// Execution: Global EC -> outer EC -> inner EC (pushed/popped on call stack)

33
Q42: Explain the JavaScript call stack with an example of a stack overflow.

ANSWER

The call stack is a LIFO (Last In, First Out) data structure that tracks function calls. When a function is invoked, a
new frame is pushed onto the stack; when it returns, its frame is popped. If functions call each other (especially
recursively) without a base case or termination condition, the stack keeps growing until it exceeds its size limit,
causing a RangeError: Maximum call stack size exceeded (stack overflow).

EXAMPLE CODE

function recurse() {
return recurse(); // no base case
}
// recurse(); // RangeError: Maximum call stack size exceeded

function factorial(n) {
if (n <= 1) return 1; // base case prevents overflow
return n * factorial(n - 1);
}
[Link](factorial(5)); // 120

Q43: How does JavaScript's garbage collection work?

ANSWER

JavaScript automatically manages memory using a garbage collector, primarily through an algorithm called mark-
and-sweep. The garbage collector periodically identifies all objects that are reachable from the "root" (global object,
currently executing functions and their local variables) by traversing references. Any object that is not reachable is
considered garbage and its memory is freed. Older engines used reference counting, which had issues with circular
references, but modern engines like V8 use generational, incremental mark-and-sweep collectors.

EXAMPLE CODE

let obj = { data: "large data" };


obj = null; // original object becomes unreachable -> eligible for GC

// Common memory leak: forgotten timers/listeners keep references alive


function setup() {
const largeData = new Array(1000000).fill('x');
setInterval(() => [Link]([Link]), 1000);
// largeData can never be collected while the interval runs
}

34
Q44: What is event delegation and why is it useful?

ANSWER

Event delegation is a technique where, instead of attaching event listeners to many individual child elements, a single
event listener is attached to a common parent element. Because of event bubbling, events triggered on children
"bubble up" to the parent, where [Link] can be inspected to determine which child actually triggered the
event. This improves performance (fewer listeners) and automatically handles dynamically added child elements.

EXAMPLE CODE

// Instead of adding a listener to every <li>


[Link]("list").addEventListener("click", (e) => {
if ([Link] === "LI") {
[Link]("Clicked item:", [Link]);
}
});

// New <li> elements added later are automatically handled too


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

35
Q45: What is the difference between debouncing and throttling?

ANSWER

Both are techniques to limit how often a function executes in response to rapidly-firing events (scroll, resize,
keypress). Debouncing delays execution until after a specified time has passed since the last call — if the event fires
again before that time elapses, the timer resets (useful for search-as-you-type). Throttling ensures a function executes
at most once per specified time interval, regardless of how many times the event fires (useful for scroll/resize
handlers).

EXAMPLE CODE

function debounce(fn, delay) {


let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

function throttle(fn, limit) {


let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}

36
Q46: What is currying? Implement a curry function.

ANSWER

Currying is the technique of transforming a function that takes multiple arguments into a sequence of functions, each
taking a single argument. Instead of f(a, b, c), a curried version is called as f(a)(b)(c). It enables partial
application — pre-filling some arguments to create specialized functions for reuse.

EXAMPLE CODE

function curry(fn) {
return function curried(...args) {
if ([Link] >= [Link]) {
return fn(...args);
}
return (...more) => curried(...args, ...more);
};
}

function add3(a, b, c) { return a + b + c; }


const curriedAdd = curry(add3);

[Link](curriedAdd(1)(2)(3)); // 6
[Link](curriedAdd(1, 2)(3)); // 6
[Link](curriedAdd(1, 2, 3)); // 6

37
Q47: What is a polyfill? Write a polyfill for [Link].

ANSWER

A polyfill is code (usually JavaScript) that implements a feature on browsers that do not natively support it, by
checking if the feature exists and, if not, defining it manually so older environments behave consistently with newer
ones. Polyfills are essential for supporting older browsers while writing modern code.

EXAMPLE CODE

if (![Link]) {
[Link] = function (callback, thisArg) {
if (this == null) throw new TypeError("[Link] called on null/
undefined");
const result = [];
for (let i = 0; i < [Link]; i++) {
if (i in this) {
result[i] = [Link](thisArg, this[i], i, this);
}
}
return result;
};
}

[Link]([1, 2, 3].map(x => x * 2)); // [2, 4, 6]

38
Q48: What is the difference between a deep clone and a shallow clone? How do you
deep clone an object?

ANSWER

A shallow clone copies only the top-level properties of an object — nested objects/arrays are still shared by reference
with the original. A deep clone recursively copies every level of nested objects/arrays, so the clone is completely
independent of the original. Common ways to deep clone include structuredClone() (modern, handles most
cases including circular references and special types), [Link]([Link](obj)) (simple but loses
functions, undefined, Date objects, etc.), or a custom recursive function / library like Lodash's cloneDeep.

EXAMPLE CODE

const original = { name: "Alice", address: { city: "NYC" } };

// Shallow clone
const shallow = { ...original };
[Link] = "LA";
[Link]([Link]); // "LA" - nested ref shared!

// Deep clone (modern)


const deep = structuredClone(original);
[Link] = "SF";
[Link]([Link]); // "LA" - unaffected

// Deep clone (older approach, with limitations)


const deepJson = [Link]([Link](original));

39
Q49: What are some core principles of functional programming in JavaScript?

ANSWER

Functional programming emphasizes writing software by composing pure functions, avoiding shared state and
mutable data, and treating functions as first-class values. Key principles include: pure functions (same input always
produces same output, no side effects), immutability (never mutate data, create new copies instead), function
composition (building complex behavior by combining simple functions), and avoiding shared mutable state.

EXAMPLE CODE

// Pure function - no side effects, predictable


const add = (a, b) => a + b;

// Immutability - don't mutate, return new data


const addItem = (arr, item) => [...arr, item];

// Composition
const compose = (...fns) => (x) => [Link]((acc, fn) => fn(acc), x);
const double = x => x * 2;
const increment = x => x + 1;
const doubleThenIncrement = compose(increment, double);
[Link](doubleThenIncrement(5)); // 11 (5*2=10, 10+1=11)

40
Q50: What is the difference between generators and iterators?

ANSWER

An iterator is any object that implements the iterator protocol — it has a next() method that returns { value,
done }. A generator is a special function (function*) that automatically returns an object that is both an iterator
and an iterable, and that uses yield to produce values lazily, pausing and resuming execution. Generators provide a
much simpler way to create custom iterators compared to writing the protocol manually.

EXAMPLE CODE

// Manual iterator
function makeIterator(arr) {
let index = 0;
return {
next: () => index < [Link]
? { value: arr[index++], done: false }
: { value: undefined, done: true }
};
}

// Generator - much simpler


function* arrayGenerator(arr) {
for (const item of arr) yield item;
}

const gen = arrayGenerator([1, 2, 3]);


[Link]([...gen]); // [1, 2, 3]

41
Q51: What are Web Workers and when would you use them?

ANSWER

Web Workers allow JavaScript to run scripts in background threads, separate from the main UI thread. They are used
for CPU-intensive tasks (image processing, large data parsing, complex calculations) that would otherwise block the
main thread and freeze the UI. Workers communicate with the main thread via postMessage() and onmessage,
and they do not have direct access to the DOM, window, or shared memory by default (structured-clone message
passing).

EXAMPLE CODE

// [Link]
const worker = new Worker("[Link]");
[Link]({ numbers: [1, 2, 3, 4, 5] });
[Link] = (e) => [Link]("Result:", [Link]);

// [Link]
(e) {
const sum = [Link]((a, b) => a + b, 0);
postMessage(sum); // sends result back without blocking main thread
};

42
Q52: Explain the Module, Singleton, and Observer design patterns with examples.

ANSWER

The Module pattern uses closures (often IIFEs) to encapsulate private state and expose a public API. The Singleton
pattern ensures a class/object has only one instance, providing a single global point of access. The Observer pattern
defines a subscription mechanism where multiple "observer" objects are notified of events/changes in a "subject" —
the foundation of event emitters and reactive systems.

EXAMPLE CODE

// Module pattern
const CounterModule = (function () {
let count = 0;
return { increment: () => ++count, getCount: () => count };
})();

// Singleton pattern
class Database {
static #instance;
constructor() { if (Database.#instance) return Database.#instance; Database.#instance
= this; }
}

// Observer pattern
class EventEmitter {
#listeners = {};
on(event, cb) { (this.#listeners[event] ??= []).push(cb); }
emit(event, data) { (this.#listeners[event] || []).forEach(cb => cb(data)); }
}

43
Q53: What is the difference between microtasks and macrotasks? Give the
execution order for a mixed example.

ANSWER

Macrotasks (also called "tasks") include things like setTimeout, setInterval, I/O, and UI rendering.
Microtasks include Promise callbacks (.then, .catch, .finally) and queueMicrotask. After each
macrotask completes, the event loop processes all pending microtasks before moving on to render the UI or run the
next macrotask — meaning microtasks always run before the next macrotask, even if the macrotask was scheduled
first.

EXAMPLE CODE

[Link]("script start");

setTimeout(() => [Link]("setTimeout"), 0);

[Link]()
.then(() => [Link]("promise 1"))
.then(() => [Link]("promise 2"));

[Link]("script end");

// Output:
// script start
// script end
// promise 1
// promise 2
// setTimeout

44
Q54: What is the difference between == coercion rules and the Abstract Equality
Comparison Algorithm?

ANSWER

The Abstract Equality Comparison Algorithm (used by ==) defines precise rules for comparing values of different
types. Key rules: if types match, behave like ===. null == undefined is true (and only equal to each other
and themselves under ==). If comparing a number and a string, the string is converted to a number. If comparing a
boolean with anything, the boolean is converted to a number (true → 1, false → 0). If comparing an object with a
primitive, the object is converted via ToPrimitive (calling valueOf()/toString()).

EXAMPLE CODE

[Link](null == undefined); // true


[Link](null == 0); // false (special case, no coercion)
[Link]("" == 0); // true ("" -> 0)
[Link]("0" == false); // true ("0" -> 0, false -> 0)
[Link]([] == false); // true ([] -> "" -> 0, false -> 0)
[Link]([1] == 1); // true ([1] -> "1" -> 1)
[Link](NaN == NaN); // false

45
Q55: What is tail call optimization and does JavaScript support it?

ANSWER

Tail call optimization (TCO) is an engine optimization where, if a function's last action is to return the result of
calling another function (a "tail call"), the engine can reuse the current stack frame instead of allocating a new one —
preventing stack growth in recursive functions. While TCO was specified as part of ES6 ("Proper Tail Calls"), it is not
implemented in most major JavaScript engines (including V8/Chrome and [Link]) as of today, so deeply recursive
functions can still cause stack overflows even when written in tail-call form.

EXAMPLE CODE

// Tail-call form - last operation is the recursive call


function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // tail call
}
// In an engine WITH TCO, this would run in constant stack space.
// In V8 (Node/Chrome), this can still overflow for very large n.

// Trampoline pattern - manual workaround for engines without TCO


function trampoline(fn) {
return (...args) => {
let result = fn(...args);
while (typeof result === 'function') result = result();
return result;
};
}

46
04
Coding Challenges
Hands-On Problems with Complexity Analysis

Classic coding interview problems with clean implementations, and time/space complexity analysis for
each. These are the problems most likely to appear in live-coding or take-home rounds.

Challenge 1: Reverse a String

PROBLEM STATEMENT

Write a function that reverses a given string without using the built-in reverse() array method directly on the
string in one line (show the underlying logic).

SOLUTION

function reverseString(str) {
let reversed = "";
for (let i = [Link] - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}

[Link](reverseString("hello")); // "olleh"

// One-liner using built-ins


const reverse2 = str => [Link]("").reverse().join("");

Time Complexity O(n)

Space Complexity O(n)

47
Challenge 2: Check if a String is a Palindrome

PROBLEM STATEMENT

Write a function that determines whether a given string reads the same forwards and backwards, ignoring case and
non-alphanumeric characters.

SOLUTION

function isPalindrome(str) {
const cleaned = [Link]().replace(/[^a-z0-9]/g, "");
let left = 0, right = [Link] - 1;

while (left < right) {


if (cleaned[left] !== cleaned[right]) return false;
left++;
right--;
}
return true;
}

[Link](isPalindrome("A man, a plan, a canal: Panama")); // true


[Link](isPalindrome("hello")); // false

Time Complexity O(n)

Space Complexity O(n) (for the cleaned string)

48
Challenge 3: Generate Fibonacci Sequence

PROBLEM STATEMENT

Write a function that returns an array containing the first n numbers of the Fibonacci sequence, using an efficient
iterative approach.

SOLUTION

function fibonacci(n) {
const result = [0, 1];
for (let i = 2; i < n; i++) {
[Link](result[i - 1] + result[i - 2]);
}
return [Link](0, n);
}

[Link](fibonacci(8)); // [0, 1, 1, 2, 3, 5, 8, 13]

// Recursive with memoization (alternative)


function fibMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
return memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
}

Time Complexity O(n) iterative / O(2^n) naive recursive / O(n) memoized

Space Complexity O(n) for storing results

49
Challenge 4: Flatten a Nested Array

PROBLEM STATEMENT

Write a function that flattens a deeply nested array into a single-level array, both with a built-in method and a manual
recursive approach.

SOLUTION

// Using built-in (ES2019+)


const flatBuiltin = arr => [Link](Infinity);

// Manual recursive approach


function flatten(arr) {
return [Link]((acc, item) => {
return [Link](item)
? [Link](flatten(item))
: [Link](item);
}, []);
}

[Link](flatten([1, [2, [3, [4, 5]], 6]])); // [1, 2, 3, 4, 5, 6]

Time Complexity O(n) where n is total number of elements

Space Complexity O(n) for the output array plus recursion stack

50
Challenge 5: Remove Duplicates from an Array

PROBLEM STATEMENT

Write a function that removes duplicate values from an array while preserving the original order of first occurrence.

SOLUTION

// Using Set (most concise)


const removeDuplicates = arr => [...new Set(arr)];

[Link](removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]

// Manual approach (works for objects with custom equality too)


function removeDuplicatesManual(arr) {
const seen = new Set();
const result = [];
for (const item of arr) {
if (![Link](item)) {
[Link](item);
[Link](item);
}
}
return result;
}

Time Complexity O(n)

Space Complexity O(n) for the Set and result array

51
Challenge 6: Deep Clone an Object (without structuredClone)

PROBLEM STATEMENT

Implement a function that creates a deep copy of an object or array, correctly handling nested objects, arrays, and
circular references.

SOLUTION

function deepClone(obj, map = new WeakMap()) {


if (obj === null || typeof obj !== "object") return obj;
if ([Link](obj)) return [Link](obj); // handle circular refs

const clone = [Link](obj) ? [] : {};


[Link](obj, clone);

for (const key in obj) {


if ([Link](obj, key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}

const original = { a: 1, b: { c: 2 } };
[Link] = original; // circular reference
const cloned = deepClone(original);
[Link](cloned.b.c); // 2
[Link]([Link] === cloned); // true (cycle preserved)
[Link](cloned !== original); // true

Time Complexity O(n) where n is total number of properties

Space Complexity O(n) for the clone plus the WeakMap

52
Challenge 7: Implement a debounce() Function

PROBLEM STATEMENT

Implement a generic debounce higher-order function that delays invoking a function until after a specified wait
time has elapsed since the last time it was invoked, and supports an immediate-invocation option.

SOLUTION

function debounce(fn, wait, immediate = false) {


let timeout;
return function (...args) {
const context = this;
const later = () => {
timeout = null;
if (!immediate) [Link](context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) [Link](context, args);
};
}

const log = debounce(() => [Link]("Searching..."), 300);


// Rapid calls; only the last one (after 300ms of inactivity) fires
[Link]("input", log);

Time Complexity O(1) per call

Space Complexity O(1)

53
Challenge 8: Implement a Custom [Link]

PROBLEM STATEMENT

Implement your own version of [Link] as [Link], supporting the index


and array arguments in the callback, and a thisArg.

SOLUTION

[Link] = function (callback, thisArg) {


if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const result = [];
for (let i = 0; i < [Link]; i++) {
if (i in this) {
result[i] = [Link](thisArg, this[i], i, this);
}
}
return result;
};

[Link]([1, 2, 3].myMap(x => x * 10)); // [10, 20, 30]


[Link]([1, 2, 3].myMap(function (x, i, arr) {
return `${x} at index ${i} of ${[Link]}`;
}));

Time Complexity O(n)

Space Complexity O(n) for the result array

54
Challenge 9: Implement a Simplified Custom Promise

PROBLEM STATEMENT

Implement a minimal version of the Promise class (MyPromise) that supports resolve, reject, and chained .then()
calls.

SOLUTION

class MyPromise {
constructor(executor) {
[Link] = "pending";
[Link] = undefined;
[Link] = [];

const resolve = (value) => {


if ([Link] !== "pending") return;
[Link] = "fulfilled";
[Link] = value;
[Link](cb => [Link](value));
};
const reject = (reason) => {
if ([Link] !== "pending") return;
[Link] = "rejected";
[Link] = reason;
[Link](cb => [Link](reason));
};

try { executor(resolve, reject); } catch (e) { reject(e); }


}

then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = () => {
try {
if ([Link] === "fulfilled") resolve(onFulfilled ?
onFulfilled([Link]) : [Link]);
else if ([Link] === "rejected") {
if (onRejected) resolve(onRejected([Link]));
else reject([Link]);
}
} catch (e) { reject(e); }
};
if ([Link] === "pending") [Link]({ onFulfilled: handle,
onRejected: handle });
else queueMicrotask(handle);
});
}
}

55
new MyPromise((resolve) => setTimeout(() => resolve(42), 100))
.then(v => [Link]("Got:", v)); // "Got: 42"

Time Complexity O(1) per .then registration

Space Complexity O(n) callbacks stored while pending

Challenge 10: Find the First Non-Repeating Character

PROBLEM STATEMENT

Write a function that returns the first character in a string that does not repeat anywhere else in the string, or null if
every character repeats.

SOLUTION

function firstNonRepeatingChar(str) {
const counts = {};
for (const char of str) {
counts[char] = (counts[char] || 0) + 1;
}
for (const char of str) {
if (counts[char] === 1) return char;
}
return null;
}

[Link](firstNonRepeatingChar("swiss")); // "w"
[Link](firstNonRepeatingChar("aabbcc")); // null
[Link](firstNonRepeatingChar("teeter")); // "r"

Time Complexity O(n)

Space Complexity O(k) where k is number of distinct characters

56
05
JavaScript Output Questions
Tricky "What is the output?" Challenges

These questions test your understanding of type coercion, scope, hoisting, closures, and the event loop by
asking you to predict the exact console output of a code snippet. They are extremely common in technical
screening rounds.

Q56: What is the output?

[Link]([] + []);

ANSWER

""

EXPLANATION

The + operator on two objects (arrays are objects) triggers ToPrimitive conversion. Both arrays are converted to
strings via toString(), which produces "" for an empty array. Concatenating "" + "" results in an empty
string.

57
Q57: What is the output?

[Link]([] + {});
[Link]({} + []);

ANSWER

"[object Object]" and "[object Object]" (or 0 in some console/script contexts


for the second)

EXPLANATION

[] + {}: the array becomes "" and the object becomes "[object Object]", so the result is "[object
Object]". {} + [] at the top level of a script can be parsed as an empty block statement followed by +[], which
evaluates to 0 — but when used in an expression context (like inside [Link]()), it is treated as addition and
produces "[object Object]" as well.

Q58: What is the output?

[Link](typeof NaN);
[Link](typeof null);
[Link](typeof undefined);
[Link](typeof []);
[Link](typeof function(){});

ANSWER

"number", "object", "undefined", "object", "function"

EXPLANATION

NaN is technically of type number (it represents an invalid number, but is still numeric). typeof null returns
"object" due to a historical bug in JavaScript that was never fixed for backwards compatibility. Arrays report as
"object" since they are a specialized object type. Functions are the one exception that get their own typeof
result: "function".

58
Q59: What is the output?

var x = 1;
function test() {
[Link](x);
var x = 2;
}
test();

ANSWER

undefined

EXPLANATION

Due to hoisting, the var x = 2 declaration inside test() is hoisted to the top of the function (as var x;),
creating a local variable x that shadows the outer x. At the point of [Link](x), the local x exists but has not
yet been assigned, so it is undefined — not 1.

Q60: What is the output?

for (var i = 0; i < 3; i++) {


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

ANSWER

3, 3, 3, 0, 1, 2

EXPLANATION

With var, there is only one shared i across all iterations (function-scoped). By the time the timeouts fire (after the
loop completes), i is 3, so all three callbacks log 3. With let, each iteration creates a new binding of j (block-
scoped), so each callback captures its own value: 0, 1, 2.

59
Q61: What is the output?

[Link](0.1 + 0.2 === 0.3);


[Link](0.1 + 0.2);

ANSWER

false and 0.30000000000000004

EXPLANATION

JavaScript represents numbers using the IEEE 754 double-precision floating-point format, which cannot represent
some decimal fractions exactly. The sum of 0.1 + 0.2 results in a tiny rounding error, producing
0.30000000000000004 instead of exactly 0.3, so the strict equality check returns false. To compare floats
safely, check if the difference is below a small epsilon value.

Q62: What is the output?

function foo() {
[Link](this);
}
foo();

const obj = {
method: foo
};
[Link]();

ANSWER

undefined (in strict mode / modules) or the global object (non-strict) -- then
the obj object

EXPLANATION

When foo() is called as a standalone function, this is undefined in strict mode (modules are always strict) or
the global object (window/globalThis) in non-strict sloppy mode. When called as [Link](), this
refers to obj because the function is invoked as a method of that object — the call-site determines this, not where
the function was defined.

60
Q63: What is the output?

[Link](1 + "2" + 3);


[Link](1 + 2 + "3");
[Link]("1" - 1);
[Link]("5" + 3 - 2);

ANSWER

"123" , "33" , 0 , "53" - 2 = 51

EXPLANATION

JavaScript evaluates left to right. 1 + "2" becomes "12" (number coerced to string for concatenation), then "12"
+ 3 becomes "123". 1 + 2 is 3 (both numbers), then 3 + "3" becomes "33". "1" - 1: - always coerces to
numbers, so 1 - 1 = 0. "5" + 3 becomes "53" (string concatenation), then "53" - 2 coerces "53" to 53
and subtracts, giving 51.

Q64: What is the output?

let a = { val: 1 };
let b = a;
[Link] = 2;
[Link]([Link]);

let c = 1;
let d = c;
d = 2;
[Link](c);

ANSWER

2 and 1

EXPLANATION

Objects are assigned and passed by reference — b and a point to the same object in memory, so mutating [Link]
also affects [Link], resulting in 2. Primitives like numbers are assigned by value — reassigning d creates an
independent copy, leaving c unchanged at 1.

61
Q65: What is the output?

[Link]([1, 2, 3] == [1, 2, 3]);


[Link]([1, 2, 3] === [1, 2, 3]);

const arr = [1, 2, 3];


[Link](arr === arr);

ANSWER

false, false, true

EXPLANATION

Both == and === compare objects (including arrays) by reference, not by content. Two separately created arrays,
even with identical contents, are different objects in memory, so both comparisons are false. Comparing arr ===
arr is true because it's the exact same reference.

Q66: What is the output?

const obj = {
a: 10,
getA: function() {
return this.a;
}
};

const getAArrow = [Link];


[Link](getAArrow());

const { getA } = obj;


[Link](getA());

ANSWER

TypeError or undefined (depends on strict mode) for both calls

EXPLANATION

When getA is extracted from obj (either via direct assignment or destructuring) and called standalone, it loses its
connection to obj. this becomes undefined (strict mode) or the global object (non-strict). In strict mode,
this.a throws a TypeError because this is undefined. In non-strict mode, this is the global object, which
has no a property, so the result is undefined rather than 10.

62
Q67: What is the output?

[Link](typeof typeof 1);

let x;
[Link](x ?? "default");
[Link](x || "default");

x = 0;
[Link](x ?? "default");
[Link](x || "default");

ANSWER

"string", "default", "default", 0, "default"

EXPLANATION

typeof 1 returns the string "number", and typeof "number" returns "string". The nullish coalescing
operator ?? only falls back when the left side is null or undefined — so for x = undefined both ?? and ||
return "default". But once x = 0, ?? returns 0 (0 is not nullish) while || still returns "default" because 0 is
falsy.

63
Q68: What is the output?

class Animal {
constructor() {
[Link] = "Animal";
}
static create() {
return new this();
}
}

class Dog extends Animal {


constructor() {
super();
[Link] = "Dog";
}
}

[Link]([Link]().type);
[Link]([Link]().type);

ANSWER

"Dog" and "Animal"

EXPLANATION

In a static method, this refers to the class itself (not an instance). new this() therefore constructs an instance of
whichever class the static method was called on — this is how static factory methods correctly support subclassing.
[Link]() creates a Dog instance (whose constructor sets type = "Dog" after calling super()), while
[Link]() creates a plain Animal instance.

64
Q69: What is the output?

async function getData() {


return "data";
}

const result = getData();


[Link](result);

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

ANSWER

Promise { "data" } (a pending/fulfilled Promise object) then "data"

EXPLANATION

An async function always returns a Promise, even if the function body uses a plain return with a non-Promise
value. Logging result immediately shows a Promise object (JavaScript engines often display it as fulfilled with
the value, e.g. Promise {''}: "data"). The actual string "data" is only accessible by calling .then() on
the returned promise, or by awaiting it inside another async function.

Q70: What is the output?

const arr = [1, 2, 3];


[Link] = 1;
[Link](arr);

const arr2 = [1, 2, 3];


arr2[10] = 100;
[Link]([Link]);
[Link](arr2);

ANSWER

[1] and 11 and [1, 2, 3, <7 empty items>, 100]

EXPLANATION

The length property of an array is writable: setting it to a value smaller than the current length truncates the array,
removing elements beyond the new length, so arr becomes [1]. Conversely, assigning to an index beyond the
current length automatically extends length to index + 1 (here, 11), and the intervening indices become "empty
slots" (sparse array) rather than undefined values, though they read as undefined.

65
06
ES6+ Features
Modern Syntax You Must Know

A focused tour of the modern JavaScript features introduced from ES6 (2015) onward that show up
constantly in interviews: arrow functions, template literals, modules, optional chaining, nullish coalescing,
Sets, Maps, WeakMap/WeakSet, Symbol, and BigInt.

Q71: What new collection types were introduced and how do Map and Set differ
from Object and Array?

ANSWER

ES6 introduced Map and Set. A Map is a collection of key-value pairs where keys can be any type (including objects
and functions), unlike plain objects whose keys are coerced to strings. Maps maintain insertion order and have a
.size property. A Set is a collection of unique values of any type, useful for deduplication and membership testing
with O(1) lookups via .has().

EXAMPLE CODE

const map = new Map();


const objKey = {};
[Link](objKey, "value for object key");
[Link]("str", "value for string key");
[Link]([Link](objKey)); // "value for object key"
[Link]([Link]); // 2

const set = new Set([1, 2, 2, 3]);


[Link]([Link](2)); // true
[Link]([...set]); // [1, 2, 3]

66
Q72: What are WeakMap and WeakSet, and why are they 'weak'?

ANSWER

WeakMap and WeakSet are similar to Map and Set, but they hold weak references to their keys (WeakMap) or
values (WeakSet) — meaning if the only reference to an object is inside a WeakMap/WeakSet, the garbage collector is
still free to reclaim that object's memory. This makes them ideal for associating metadata with objects without causing
memory leaks. Keys/values must be objects (not primitives), and they are not iterable (no .size, no for...of)
precisely because their contents can disappear at any time.

EXAMPLE CODE

let user = { name: "Alice" };


const cache = new WeakMap();
[Link](user, "cached data");

[Link]([Link](user)); // "cached data"

user = null; // the object becomes eligible for garbage collection


// the WeakMap entry is automatically removed too - no memory leak

67
Q73: What is Symbol used for in JavaScript?

ANSWER

Symbol is a primitive data type introduced in ES6 that creates unique, immutable identifiers, often used as object
property keys to avoid name collisions (especially in libraries) or to define "hidden" properties that don't show up in
normal enumeration (for...in, [Link]). Well-known symbols like [Link] are used to
customize built-in behaviors, such as making an object iterable.

EXAMPLE CODE

const id = Symbol("id");
const user = {
name: "Alice",
[id]: 12345 // symbol-keyed property
};

[Link](user[id]); // 12345
[Link]([Link](user)); // ["name"] - symbol key is hidden

// Custom iterable using [Link]


const range = {
from: 1, to: 3,
[[Link]]() {
let current = [Link], last = [Link];
return { next: () => current <= last ? { value: current++, done: false } : { done:
true } };
}
};
[Link]([...range]); // [1, 2, 3]

68
Q74: What is BigInt and when would you use it?

ANSWER

BigInt is a primitive type that can represent integers with arbitrary precision, beyond the safe integer limit of
Number (2^53 - 1). It's created by appending n to an integer literal or calling BigInt(). BigInt is used for
precise large-number calculations such as cryptography, high-precision timestamps, or financial calculations where
rounding errors are unacceptable. BigInt and Number cannot be mixed in arithmetic operations without explicit
conversion.

EXAMPLE CODE

[Link](Number.MAX_SAFE_INTEGER); // 9007199254740991
[Link](Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true
(precision lost!)

const big = 9007199254740993n;


[Link](big + 1n); // 9007199254740994n (exact)

// [Link](1n + 1); // TypeError: Cannot mix BigInt and other types

Q75: Explain nullish coalescing assignment (??=) and logical assignment


operators.

ANSWER

ES2021 introduced logical assignment operators that combine a logical operation with assignment: ??= assigns the
right-hand value only if the left-hand variable is null or undefined; ||= assigns only if the left-hand value is
falsy; &&= assigns only if the left-hand value is truthy. These provide concise shorthand for common conditional-
assignment patterns.

EXAMPLE CODE

let config = { theme: "", retries: 0 };

[Link] ??= "light"; // theme is "" (not nullish) -> unchanged


[Link] ??= 3; // retries is 0 (not nullish) -> unchanged
[Link](config); // { theme: "", retries: 0 }

[Link] ||= "light"; // "" is falsy -> assigned


[Link]([Link]); // "light"

69
Q76: What are tagged template literals?

ANSWER

Tagged templates allow a function (the "tag") to parse a template literal's content. The tag function receives the literal
string segments as its first argument (an array) and the interpolated values as subsequent arguments, enabling custom
string processing such as sanitization, internationalization, or syntax highlighting.

EXAMPLE CODE

function highlight(strings, ...values) {


return [Link]((acc, str, i) =>
acc + str + (values[i] !== undefined ? `**${values[i]}**` : ''), '');
}

const name = "Alice";


const age = 25;
[Link](highlight`Name: ${name}, Age: ${age}`);
// "Name: **Alice**, Age: **25**"

Q77: What is the difference between [Link](), [Link](), and


[Link]()?

ANSWER

All three methods return arrays derived from an object's own enumerable properties. [Link](obj) returns
an array of property names (strings). [Link](obj) returns an array of property values.
[Link](obj) returns an array of [key, value] pairs, which is especially useful for iterating with
for...of or converting an object to a Map.

EXAMPLE CODE

const user = { name: "Alice", age: 25 };

[Link]([Link](user)); // ["name", "age"]


[Link]([Link](user)); // ["Alice", 25]
[Link]([Link](user)); // [["name","Alice"], ["age",25]]

for (const [key, value] of [Link](user)) {


[Link](`${key}: ${value}`);
}

const map = new Map([Link](user));

70
Q78: How do dynamic imports (import()) differ from static imports?

ANSWER

Static import statements are hoisted, must be at the top level of a module, and are resolved at compile/parse time.
Dynamic import() is a function-like expression that returns a Promise, can be called conditionally or anywhere in
code (including inside functions/if blocks), and loads the module asynchronously at runtime. It's commonly used
for code-splitting and lazy-loading parts of an application.

EXAMPLE CODE

// Static - always loaded


import { add } from './[Link]';

// Dynamic - loaded on demand


[Link]("click", async () => {
const { default: Chart } = await import('./[Link]');
new Chart(); // only loaded when the button is clicked
});

Q79: What are [Link](), flatMap(), and [Link]()?

ANSWER

flat(depth) flattens nested arrays up to the given depth (default 1). flatMap(callback) first maps each
element using the callback, then flattens the result by one level — equivalent to map().flat() but more efficient.
[Link](iterable, mapFn) creates a new array from any iterable or array-like object (strings, Sets,
NodeLists, objects with length), optionally applying a mapping function.

EXAMPLE CODE

[Link]([1, [2, 3], [4, [5]]].flat()); // [1, 2, 3, 4, [5]]


[Link]([1, [2, 3], [4, [5]]].flat(Infinity)); // [1, 2, 3, 4, 5]

[Link]([1, 2, 3].flatMap(x => [x, x * 2])); // [1, 2, 2, 4, 3, 6]

[Link]([Link]("abc")); // ["a", "b", "c"]


[Link]([Link]({ length: 3 }, (_, i) => i * 2)); // [0, 2, 4]

71
Q80: What are [Link](), [Link](), [Link](), and
[Link]()?

ANSWER

[Link](promises) resolves when all promises resolve (returning an array of results), or rejects
immediately if any promise rejects. [Link](promises) settles as soon as the first promise settles
(whether fulfilled or rejected). [Link](promises) waits for all promises to settle and returns
an array of {status, value/reason} objects, never rejecting itself. [Link](promises) resolves as
soon as any promise fulfills, rejecting only if all of them reject.

EXAMPLE CODE

const p1 = [Link](1);
const p2 = new Promise((_, reject) => setTimeout(() => reject("err"), 100));
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 50));

[Link]([p1, p3]).then([Link]); // [1, 3]


[Link]([p1, p2, p3]).then([Link]); // 1 (settles first)
[Link]([p1, p2]).then([Link]);
// [{status:"fulfilled", value:1}, {status:"rejected", reason:"err"}]

72
07
Browser & DOM Questions
DOM Manipulation, Events & Storage

Practical browser-environment questions covering DOM manipulation, event bubbling/capturing/


delegation, storage mechanisms (localStorage, sessionStorage, cookies), and the browser rendering
pipeline.

Q81: What are the main ways to select and manipulate DOM elements?

ANSWER

Elements are selected via [Link](), [Link]() (returns the first


match for a CSS selector), and [Link]() (returns a static NodeList of all matches).
Once selected, elements can be manipulated via properties like .textContent, .innerHTML, .classList
(add/remove/toggle classes), .style, and methods like .appendChild(), .removeChild(), and
.setAttribute().

EXAMPLE CODE

const title = [Link]("h1");


[Link] = "New Title";
[Link]("highlight");

const items = [Link](".item");


[Link](item => [Link] = "blue");

const newDiv = [Link]("div");


[Link] = "Hello";
[Link](newDiv);

73
Q82: What is the difference between event bubbling and event capturing?

ANSWER

These are the two phases of event propagation in the DOM. In capturing (trickling down), the event travels from the
document root down to the target element. In bubbling (the default), the event travels from the target element back
up to the root. addEventListener(type, handler, useCapture) — passing true as the third
argument (or {capture: true}) registers the handler for the capturing phase; the default (false) registers it for
the bubbling phase.

EXAMPLE CODE

[Link]("outer").addEventListener("click", () => {
[Link]("Outer (bubble)");
}); // bubbling - default

[Link]("outer").addEventListener("click", () => {
[Link]("Outer (capture)");
}, true); // capturing

[Link]("inner").addEventListener("click", () => {
[Link]("Inner");
});

// Clicking #inner logs: "Outer (capture)", "Inner", "Outer (bubble)"

74
Q83: How do you stop event propagation and prevent default behavior?

ANSWER

[Link]() stops the event from continuing to bubble (or capture) to other elements, but does
not prevent the element's own default action. [Link]() stops the browser's default behavior
for that event (e.g. a link navigating, a form submitting) but does not stop the event from propagating.
[Link]() stops propagation and prevents any other listeners on the same
element for the same event from running.

EXAMPLE CODE

[Link]("a").addEventListener("click", (e) => {


[Link](); // link won't navigate
});

[Link](".child").addEventListener("click", (e) => {


[Link](); // click won't bubble to parent
});

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


[Link](); // prevent page reload
// handle form data via JS/fetch instead
});

75
Q84: What is the difference between localStorage, sessionStorage, and cookies?

ANSWER

localStorage persists data with no expiration date, even after the browser is closed (typically ~5-10MB per
origin), and is accessible only via JavaScript on the client. sessionStorage is similar but data is cleared when the
page session ends (tab closed). Cookies are much smaller (~4KB), are sent to the server with every HTTP request
(impacting performance), can have an expiration date, and can be marked HttpOnly (inaccessible to JavaScript,
improving security against XSS).

EXAMPLE CODE

// localStorage - persists indefinitely


[Link]("theme", "dark");
[Link]([Link]("theme")); // "dark"

// sessionStorage - cleared when tab closes


[Link]("formDraft", [Link]({ name: "Alice" }));

// Cookies - sent with every request


[Link] = "userId=123; max-age=3600; path=/";

Q85: What happens during the browser's rendering process (critical rendering
path)?

ANSWER

The browser: (1) parses HTML into the DOM tree; (2) parses CSS into the CSSOM; (3) combines them into the
render tree (only visible elements); (4) performs layout (reflow) — calculating the exact position and size of each
element; (5) paint — filling in pixels for text, colors, images, borders; (6) composite — combining painted layers in
the correct order on screen. JavaScript execution can block this pipeline, which is why scripts are often loaded with
async or defer.

EXAMPLE CODE

<!-- defer: downloads in parallel, executes after HTML parsing completes, in order -->
<script src="[Link]" defer></script>

<!-- async: downloads in parallel, executes immediately when ready (may interrupt
parsing) -->
<script src="[Link]" async></script>

<!-- Changing layout-affecting properties (width, position) triggers reflow;


changing only color/background triggers repaint only (cheaper) -->

76
Q86: What is the difference between == DOM == 'innerHTML', 'innerText', and
'textContent'?

ANSWER

innerHTML gets/sets the HTML markup inside an element, parsing any tags — useful for inserting structured
content but risky for user-provided data (XSS). textContent gets/sets the raw text content of an element and all its
descendants, including hidden elements, without parsing HTML (safer, faster). innerText is similar to
textContent but is aware of CSS styling — it won't return text from hidden elements, and triggers a reflow to
compute visibility, making it slower.

EXAMPLE CODE

const div = [Link]("#box");


[Link] = "<strong>Bold</strong> text"; // renders as bold
[Link] = "<strong>Bold</strong> text"; // shows literal tags as text
[Link]([Link]); // includes text of hidden children
[Link]([Link]); // excludes text of hidden children (e.g. display:none)

77
Q87: How do you make an HTTP request using the Fetch API, and how do you
handle errors?

ANSWER

fetch(url, options) returns a Promise that resolves to a Response object once the HTTP response headers
are received — importantly, fetch does not reject on HTTP error status codes (404, 500); it only rejects on network
failures. You must check [Link] (or [Link]) manually and throw an error if needed, then
parse the body with .json(), .text(), etc.

EXAMPLE CODE

async function getUser(id) {


try {
const response = await fetch(`/api/users/${id}`);
if (![Link]) {
throw new Error(`HTTP error: ${[Link]}`);
}
const data = await [Link]();
return data;
} catch (err) {
[Link]("Fetch failed:", [Link]);
throw err;
}
}

78
Q88: What is the difference between defer and async script attributes, and what
about inline scripts without either?

ANSWER

A regular <script> (no attribute) blocks HTML parsing while it downloads and executes — synchronously, in
order. async scripts download in parallel with parsing and execute as soon as they're ready (possibly interrupting
parsing), without guaranteed order relative to other scripts. defer scripts also download in parallel but are
guaranteed to execute in order, only after HTML parsing is complete (but before DOMContentLoaded). defer is
generally preferred for scripts that need the DOM to be ready.

EXAMPLE CODE

<!-- Blocks parsing until downloaded AND executed -->


<script src="[Link]"></script>

<!-- Downloads in parallel, executes ASAP (order not guaranteed) -->


<script src="[Link]" async></script>

<!-- Downloads in parallel, executes in order after parsing -->


<script src="[Link]" defer></script>
<script src="[Link]" defer></script>

79
08
Asynchronous JavaScript
Callbacks, Promises, Async/Await & the Event Loop

A deep dive into how JavaScript manages asynchronous operations: callbacks, Promise chaining, async/
await, the event loop, and real-world API handling with the Fetch API.

EVENT LOOP FLOW

Web APIs
Callback / Task
Call Stack → (timers, fetch, DOM → Queue
→ Event Loop
events)

Microtask queue (Promises) is checked and fully drained by the Event Loop before each macrotask (setTimeout, etc.) and before
rendering.

80
Q89: Walk through how a chain of .then() calls executes, including error
propagation.

ANSWER

Each .then(onFulfilled, onRejected) call returns a new Promise, allowing chaining. The return value of
a .then() handler becomes the resolved value of the next promise in the chain (if it returns a Promise, the chain
"unwraps" and waits for it). If a handler throws or a promise rejects, the error skips any subsequent .then()
handlers (which only have onFulfilled) until it reaches a .catch() or a .then() with an onRejected
handler.

EXAMPLE CODE

fetchUser(1)
.then(user => fetchPosts([Link])) // returns a promise, chain waits for it
.then(posts => [Link])
.then(count => { throw new Error("Oops"); }) // throws
.then(x => [Link]("never runs:", x)) // skipped
.catch(err => [Link]("Caught:", [Link])) // handles the error
.finally(() => [Link]("Done")); // always runs

81
Q90: How would you run multiple async operations in parallel vs sequentially using
async/await?

ANSWER

To run operations sequentially, await each one in turn inside a loop or one after another — each waits for the
previous to finish. To run them in parallel, start all the async operations first (without awaiting immediately), collect
the resulting promises into an array, and then use [Link]() (or await them individually after starting all of
them) to wait for all to complete concurrently.

EXAMPLE CODE

// Sequential - slow (each waits for the previous)


async function sequential(ids) {
const results = [];
for (const id of ids) {
[Link](await fetchUser(id)); // waits each time
}
return results;
}

// Parallel - fast (all start immediately)


async function parallel(ids) {
const promises = [Link](id => fetchUser(id)); // all started
return [Link](promises); // wait for all together
}

82
Q91: What is 'callback hell' and how do Promises and async/await solve it?

ANSWER

"Callback hell" (or the "pyramid of doom") refers to deeply nested callbacks required when multiple asynchronous
operations depend on each other, resulting in code that grows horizontally and becomes hard to read, debug, and
maintain — especially error handling, which must be duplicated at every level. Promises flatten this into a linear
chain of .then() calls with centralized error handling via .catch(). async/await goes further, allowing the
chain to be written as flat, synchronous-looking code with standard try/catch.

EXAMPLE CODE

// Callback hell
getUser(id, (user) => {
getPosts([Link], (posts) => {
getComments(posts[0].id, (comments) => {
[Link](comments); // 3 levels deep, error handling at each level
}, handleError);
}, handleError);
}, handleError);

// async/await - flat and readable


async function getData(id) {
try {
const user = await getUser(id);
const posts = await getPosts([Link]);
const comments = await getComments(posts[0].id);
[Link](comments);
} catch (err) {
handleError(err);
}
}

83
Q92: How do you implement a timeout for a fetch request?

ANSWER

Since fetch has no built-in timeout, the standard approach uses AbortController: create a controller, pass its
signal to fetch, and call [Link]() after a timeout via setTimeout. If the fetch is aborted, it
rejects with an AbortError, which can be caught and handled distinctly from other errors.

EXAMPLE CODE

async function fetchWithTimeout(url, timeoutMs = 5000) {


const controller = new AbortController();
const timer = setTimeout(() => [Link](), timeoutMs);

try {
const response = await fetch(url, { signal: [Link] });
return await [Link]();
} catch (err) {
if ([Link] === "AbortError") {
throw new Error("Request timed out");
}
throw err;
} finally {
clearTimeout(timer);
}
}

84
Q93: What is the difference between setTimeout(fn, 0) and queueMicrotask(fn)?

ANSWER

Both defer execution until the current synchronous code finishes, but they go to different queues.
queueMicrotask(fn) (and Promise callbacks) are placed in the microtask queue, which is fully drained before
the event loop proceeds to the next macrotask or rendering. setTimeout(fn, 0) places fn in the macrotask
queue, which runs only after all current microtasks have completed — so microtasks always execute first, even with a
0 delay.

EXAMPLE CODE

[Link]("1");
setTimeout(() => [Link]("2: macrotask"), 0);
queueMicrotask(() => [Link]("3: microtask"));
[Link]().then(() => [Link]("4: microtask (promise)"));
[Link]("5");

// Output: 1, 5, 3, 4, 2

85
Q94: How would you implement retry logic for a failed async request?

ANSWER

A retry wrapper repeatedly attempts an async operation, catching failures and retrying up to a maximum number of
attempts, often with a delay between attempts — commonly using exponential backoff (doubling the delay each
time) to avoid overwhelming a struggling server.

EXAMPLE CODE

async function retry(fn, retries = 3, delay = 500) {


for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === retries) throw err;
[Link](`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(res => setTimeout(res, delay));
delay *= 2; // exponential backoff
}
}
}

retry(() => fetch("/api/data").then(r => [Link]()))


.then([Link])
.catch(err => [Link]("All retries failed:", err));

86
Q95: What is a Promise constructor anti-pattern, and how do you avoid it?

ANSWER

A common mistake is wrapping an already-Promise-returning function (like fetch) in a new, unnecessary new
Promise(), or mixing async/await with manual resolve/reject calls inside the executor — this adds
complexity, can swallow errors (if await throws inside the executor without a try/catch, it becomes an
unhandled rejection in some cases), and is redundant since the value is already a Promise. The fix is simply to return
or await the existing Promise directly.

EXAMPLE CODE

// Anti-pattern - unnecessary wrapping


function getData() {
return new Promise((resolve, reject) => {
fetch("/api/data")
.then(res => [Link]())
.then(resolve)
.catch(reject);
});
}

// Better - just return the promise chain


function getDataBetter() {
return fetch("/api/data").then(res => [Link]());
}

// Or with async/await
async function getDataBest() {
const res = await fetch("/api/data");
return [Link]();
}

87
09
React-Related JavaScript Questions
Where Core JS Meets React

React interview questions are often really JavaScript questions in disguise — about closures, references,
and asynchronous updates. This section covers the Virtual DOM, state vs. props, Hooks, useEffect,
closures in React, and memoization.

Q96: What is the Virtual DOM and why does React use it?

ANSWER

The Virtual DOM is a lightweight, in-memory JavaScript representation of the actual DOM. When state changes,
React creates a new Virtual DOM tree and compares it ("diffing") with the previous one using a reconciliation
algorithm. It then computes the minimal set of changes needed and applies only those specific updates to the real
DOM. Direct DOM manipulation is expensive (triggers layout/reflow), so batching and minimizing real DOM
operations through this diffing process improves performance.

EXAMPLE CODE

// Conceptually:
// 1. State changes -> render() produces a new virtual tree (plain JS objects)
// 2. React diffs new tree vs. previous tree
// 3. React computes minimal DOM operations (the "patch")
// 4. React applies the patch to the real DOM

// You never manually call [Link] / appendChild in React -


// React's reconciler handles it behind the scenes.

88
Q97: What is the difference between state and props?

ANSWER

props ("properties") are read-only data passed from a parent component to a child — a child cannot modify its
own props. state is data managed internally by a component (via useState or [Link]), which the
component can update itself (triggering a re-render) using a setter function. Changing a parent's state can cause new
props to flow down to children, but a component never mutates its props directly.

EXAMPLE CODE

function Avatar({ url, size }) { // url and size are props - read only
return <img src={url} width={size} height={size} />;
}

function Profile() {
const [name, setName] = useState("Alice"); // name is state - owned here
return (
<div>
<Avatar url="/[Link]" size={50} /> {/* passing props down */}
<button => setName("Bob")}>Change name</button>
<p>{name}</p>
</div>
);
}

89
Q98: How does the useState hook work under the hood (conceptually)?

ANSWER

useState(initialValue) returns a pair: the current state value and a setter function. React preserves this state
between re-renders by associating it with the order in which hooks are called within a given component instance
(which is why hooks cannot be called conditionally or in loops — the call order must be consistent). Calling the setter
schedules a re-render; React then calls the component function again, and useState returns the updated value on
that render (not immediately after calling the setter, which is asynchronous/batched).

EXAMPLE CODE

function Counter() {
const [count, setCount] = useState(0);

function handleClick() {
setCount(count + 1); // schedules re-render, 'count' here still 0
[Link](count); // logs 0 (stale closure - not yet updated)
}

return <button >
}

// To use the previous value safely, use the updater function form:
// setCount(prev => prev + 1);

90
Q99: What is the useEffect hook and what does its dependency array control?

ANSWER

useEffect(callback, dependencies) lets you run side effects (data fetching, subscriptions, DOM
manipulation) after a component renders. The dependency array controls when the effect re-runs: if omitted, it runs
after every render; an empty array [] means it runs only once, after the initial render (like componentDidMount);
listing variables means it re-runs whenever any of those values change. The optional function returned from the
callback is the cleanup function, run before the effect re-runs or when the component unmounts.

EXAMPLE CODE

useEffect(() => {
const subscription = subscribeToData(userId, (data) => setData(data));

return () => {
[Link](); // cleanup - prevents memory leaks
};
}, [userId]); // re-run only when userId changes

// Common bug: missing dependency causes a "stale closure" capturing old userId

91
Q100: How can closures cause stale state ('stale closure') bugs in React, and how
do you fix them?

ANSWER

Because each render of a component creates a new closure over that render's props/state, an effect, event handler, or
timeout defined in one render "remembers" the values from that render. If that callback runs later (e.g. in a
setTimeout or after an async operation), it sees the old ("stale") state, not the current one. Common fixes: include
the value in the dependency array so the effect re-creates the closure with fresh values, use the updater-function form
of setState (setCount(prev => prev + 1)), or use a useRef to always read the latest value.

EXAMPLE CODE

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // BUG: 'count' is stale, always captures initial 0
}, 1000);
return () => clearInterval(id);
}, []); // empty deps -> closure never refreshed

// FIX: use the updater function, which doesn't depend on closed-over 'count'
// setCount(prev => prev + 1);
}

92
Q101: What problem do useMemo and useCallback solve?

ANSWER

Both are memoization hooks that help avoid unnecessary recalculations or re-renders by caching values between
renders, recomputing only when listed dependencies change. useMemo(fn, deps) memoizes the result of an
expensive calculation. useCallback(fn, deps) memoizes the function reference itself — useful when
passing callbacks to memoized child components ([Link]), since a new function reference on every render
would otherwise cause those children to re-render unnecessarily.

EXAMPLE CODE

function ProductList({ products, query }) {


// Recompute only when products or query change
const filtered = useMemo(
() => [Link](p => [Link](query)),
[products, query]
);

// Stable reference passed to a memoized child


const handleSelect = useCallback((id) => {
[Link]("Selected:", id);
}, []);

return <MemoizedList items={filtered} />;


}

93
Q102: What is the difference between controlled and uncontrolled components?

ANSWER

A controlled component has its form value driven entirely by React state — the input's value comes from state,
and onChange updates that state, making React the "single source of truth". An uncontrolled component manages
its own state internally in the DOM, and React accesses its current value only when needed, typically via a ref.
Controlled components offer more predictable behavior and easier validation; uncontrolled components can be
simpler for basic forms.

EXAMPLE CODE

// Controlled
function ControlledInput() {
const [value, setValue] = useState("");
return <input value={value} => setValue([Link])} />;
}

// Uncontrolled
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => [Link]([Link]);
return <input ref={inputRef} defaultValue="initial" />;
}

94
Q103: Why does React require keys for list items, and what happens if you use the
array index as a key?

ANSWER

Keys help React identify which items in a list have changed, been added, or removed across re-renders, allowing it to
update the DOM efficiently and correctly preserve component state/identity for each item. Using the array index as a
key works fine for static lists, but if the list can be reordered, filtered, or have items inserted/removed, index-based
keys cause React to misattribute state between items (e.g. an input's focus or local state "jumping" to the wrong row),
because the key no longer reliably identifies the same logical item. A stable, unique identifier (like a database ID)
should be used instead.

EXAMPLE CODE

// Risky if list order can change


{[Link]((item, index) => <Item key={index} {...item} />)}

// Better - stable identity


{[Link]((item) => <Item key={[Link]} {...item} />)}

95
10
Interview Tips
Before, During & After the Interview

Technical knowledge is only half the battle. This section covers practical strategies for preparing,
performing, and communicating effectively during JavaScript interviews — for both technical and HR
rounds.

Before the Interview

Revision Strategy Mock Interviews Resume Preparation


• Spend the final week reviewing • Practice explaining your • List specific technologies
your own notes and flashcards thought process out loud, even (ES6+, React, [Link]) rather
rather than consuming new when alone — verbalizing than just "JavaScript" —
material. reasoning is a distinct skill from interviewers often ask directly
• Re-implement core utilities solving silently. about listed skills.
from scratch: debounce, deep • Time yourself on coding • Quantify project impact where
clone, curry, a custom Promise, problems (20-30 minutes is possible (e.g. "reduced load
polyfills for map/filter/reduce. typical for a single problem). time by 30%").
• Re-read code you've personally • Practice on a whiteboard or • Be ready to discuss every line
written for past projects — be plain text editor occasionally, on your resume in depth — only
ready to explain decisions in without autocomplete, to mirror list what you can confidently
your own work. interview conditions. defend.
• Group concepts by theme • Record yourself or do mock • Keep a one-page version
(async, scope, OOP) rather than sessions with a peer and review focused on relevant experience
randomly jumping between for filler words, pacing, and for the role.
topics. clarity.

96
During the Interview

Answer with Confidence Explaining Code Communication Tips


• It's fine to take a few seconds to • Narrate your approach before • Treat the interview as a
think before answering — writing code: "I'll iterate collaborative conversation, not
silence is better than rambling. through the array, track seen an interrogation — ask
• If unsure, say what you do values in a Set, and build a clarifying questions.
know and how you'd find the result array." • If you make a mistake,
answer (docs, experimentation) • Name variables meaningfully acknowledge it calmly and
rather than guessing confidently even under time pressure — correct it — interviewers value
and being wrong. userIndex not x. debugging skills as much as
• Avoid hedging every sentence • After writing a solution, walk first-try correctness.
with "I think" or "maybe" — through it with a sample input • Listen carefully to hints;
state what you know directly, to verify correctness aloud. interviewers often nudge toward
and flag genuine uncertainty a better approach — take the
clearly. hint rather than insisting on
your original plan.

Technical Round Tips

Think Aloud Clarify Assumptions Discuss Complexity


• Describe your mental model as • Ask about input constraints: • After a working solution, state
you work through the problem Can the array be empty? Can its time/space complexity
— it lets the interviewer follow values be negative? Are proactively.
your reasoning and offer hints. duplicates allowed? • If your first solution is brute-
• If you get stuck, articulate why • Confirm expected behavior for force, say so and suggest a more
you're stuck: "I'm not sure edge cases before writing code optimal approach — even if you
whether this should mutate the — it shows rigor and avoids don't have time to fully
input or return a new array." wasted effort. implement it.
• Restate the problem in your • Discuss trade-offs: "This is O(n)
own words to confirm time but O(n) space; a two-
understanding. pointer approach could get O(1)
space if the input is sorted."

97
HR Round Tips

"Tell Me About Yourself" Strengths & Weaknesses Salary Discussion


• Structure as: current role/skills • For strengths, pick ones relevant • Research market rates for the
→ relevant experience/projects to the role and back them with a role, location, and your
→ why you're interested in this brief example. experience level beforehand.
role. • For weaknesses, choose • Where possible, let the
• Keep it under 2 minutes and something genuine but non- employer state a range first; if
tailor it to the specific company/ disqualifying, and describe asked directly, give a researched
role. concrete steps you're taking to range rather than a single
• Lead with the most relevant improve it. number.
experience for the job, not • Avoid clichés like "I'm a • Frame salary as one factor
strictly chronological order. perfectionist" without a real among several (growth, team,
example. learning opportunities) rather
than the sole focus.

98
11
Memory Tricks & Cheat Sheets
Mnemonics & Visual Memory Maps

Quick mnemonics and visual maps to lock in the concepts that come up again and again. Use these for
last-minute revision the night before an interview.

Closure Formula

Function + Lexical Environment = Closure

A closure isn't magic — it's just a function carrying its birth environment with it. Every function in JavaScript
forms a closure over the scope it was defined in.

Event Loop Memory Trick

Call Stack → Web APIs → Callback Queue → Event Loop

Mnemonic: "Stack Asks Web, Web Calls Back, Loop Lets it In." Remember: microtasks (Promises) always
cut in line before the next macrotask (setTimeout).

99
Promise States: P-R-F

Pending → Resolved (Fulfilled) / Rejected

State Meaning Can transition to

Pending Initial state, operation not yet complete Fulfilled or Rejected

Fulfilled Operation completed successfully (final, immutable)

Rejected Operation failed (final, immutable)

var / let / const Trick: "VLC"

Letter Keyword Memory Hook

V var Variable scope (function-scoped, hoisted, re-declarable)

L let Local block scope (re-assignable, not re-declarable)

C const Constant reference (block-scoped, cannot reassign)

Closure Diagram

outer() scope

let count = 0

inner() — remembers count via closure

Even after outer() returns, inner() retains a live reference to count — it is never garbage collected
while inner exists.

100
Type Coercion Quick Table

Expression Result Why

"5" + 1 "51" + with a string concatenates

"5" - 1 4 - always coerces to numbers

true + 1 2 true coerces to 1

[] + [] "" both arrays -> ""

null == undefined true special case rule

NaN === NaN false NaN never equals itself

Array Method Cheat Sheet

Method Mutates Original? Returns

map No New array (same length)

filter No New array (subset)

forEach No undefined

reduce No Accumulated value

slice No New array (portion)

splice Yes Removed elements

sort Yes Same array, sorted

reverse Yes Same array, reversed

concat No New merged array

flat No New flattened array

101
"this" Binding Quick Reference

How Called "this" refers to

[Link]() The object before the dot (obj)

fn() (standalone) undefined (strict) / global object

Arrow function Inherited from enclosing scope (no own this)

[Link](obj) / apply obj (explicit)

[Link](obj) obj (permanently bound)

new Fn() The newly created instance

102
12
30-Day Revision Plan
A Day-by-Day Roadmap

A structured four-week roadmap that moves from fundamentals through advanced topics, ending with
mock interviews and full revision. Adjust the pace based on your starting level — freshers may want to
spend extra time in Week 1-2, while experienced developers can compress Weeks 1-2 and spend more
time on Weeks 3-4.

Week 1 — Fundamentals
Day Focus Topic Tasks

Day 1 Variables & Data Types Review var/let/const, primitive vs reference types. Solve 5 Section 1 questions.

Day 2 Operators & Type Coercion Practice == vs ===, truthy/falsy. Work through Section 5 output questions 56-61.

Day 3 Functions & Scope Function declarations vs expressions, arrow functions, scope chains.

Day 4 Hoisting Deep dive on hoisting for var/let/const/functions. Predict outputs for tricky snippets.

Day 5 Arrays & Array Methods map, filter, reduce, slice, splice. Implement custom versions (Section 4).

Day 6 Objects & Object Methods [Link]/values/entries, getters/setters, [Link]/seal.

Day 7 Review & Mini Quiz Re-attempt all Section 1 questions without looking at answers; note weak spots.

103
Week 2 — Intermediate Concepts
Day Focus Topic Tasks

Day 8 Closures Study Q21 closely. Build a counter, a memoizer, and a private-state module from scratch.

Day 9 Callbacks & Convert 3 callback-based functions into Promise-based ones.


Promises

Day 10 Async/Await Rewrite Promise-chain examples using async/await with try/catch.

Day 11 The Event Loop Draw the event loop diagram from memory. Predict output for 3 mixed micro/macrotask
snippets.

Day 12 Prototypes & 'this' Practice call/apply/bind. Predict 'this' for 5 different invocation styles.

Day 13 Classes & Inheritance Build a 3-level class hierarchy with super(), private fields, and static methods.

Day 14 Review & Mock Timed quiz: 15 Section 2 questions in 45 minutes.


Quiz

Week 3 — Advanced Topics


Day Focus Topic Tasks

Day 15 Execution Context & Call Stack Trace execution context creation for nested function calls.

Day 16 Memory Management Study garbage collection. Identify and fix 2 memory-leak code snippets.

Day 17 Debounce & Throttle Implement both from scratch without referring to notes.

Day 18 Currying & Functional Implement curry() and compose(). Refactor an imperative snippet functionally.
Programming

Day 19 Generators & Iterators Write 2 custom generators (e.g. range generator, infinite ID generator).

Day 20 ES6+ Features Cover Sets, Maps, WeakMap/WeakSet, optional chaining, nullish coalescing
(Section 6).

Day 21 Review & Mock Quiz Timed quiz: 15 Section 3 + Section 6 questions in 45 minutes.

104
Week 4 — Mock Interviews & Final Revision
Day Focus Topic Tasks

Day 22 DOM & Browser Cover event delegation, bubbling/capturing, storage APIs (Section 7).

Day 23 Asynchronous JS Deep Implement retry-with-backoff and fetch-with-timeout (Section 8).


Dive

Day 24 React-Related JS Review closures-in-React, useEffect, useMemo/useCallback (Section 9).

Day 25 Coding Challenges Practice Solve all 10 Section 4 challenges from scratch, timed.

Day 26 Mock Interview #1 Full 45-minute mock: 1 coding problem + 5 conceptual questions.

Day 27 Mock Interview #2 Repeat with different problems; focus on communication and complexity analysis.

Day 28 Weak Spot Review Revisit every topic flagged as weak across Weeks 1-3.

Day 29 Full Cheat Sheet Pass Read through the entire Final Cheat Sheet and Memory Tricks section.

Day 30 Light Review & Rest Skim notes only, no new material. Prepare resume, questions for interviewer, and rest
well.

105
+
Final Cheat Sheet
Quick Revision Reference

A condensed, scannable reference for last-minute revision: the most-asked questions, output-question
patterns, key diagrams, one-liners, and final notes.

Most Asked Questions (At a Glance)

# Question One-line Answer

1 var vs let vs const Function-scoped/hoisted vs block-scoped vs block-scoped & non-reassignable

2 == vs === Loose (coerces types) vs strict (no coercion)

3 What is a closure? A function + its remembered lexical scope

4 What is hoisting? Declarations moved to top of scope before execution

5 Event loop? Mechanism enabling async via call stack + queues

6 Promise vs async/await? async/await is syntax sugar over Promises

7 map vs forEach? map returns new array, forEach returns undefined

8 Shallow vs deep clone? Shallow copies top level only; deep copies all nested levels

9 'this' in arrow fn? Inherited from enclosing lexical scope

10 Debounce vs throttle? Debounce waits for inactivity; throttle limits rate

106
Common Output Question Patterns

Snippet Pattern Key Rule

[] + [], [] + {} ToPrimitive coercion of objects/arrays to strings

var in loop + setTimeout Shared binding -> all callbacks see final value

let in loop + setTimeout New binding per iteration -> each sees its own value

0.1 + 0.2 === 0.3 Floating point precision -> false

typeof null Returns "object" (historical bug)

[Link]().then() vs setTimeout Microtasks run before macrotasks

Destructured method called standalone Loses 'this' binding -> undefined/TypeError

Event Loop Diagram

1. Call Stack 2. Web APIs 3. Queues 4. Event Loop


runs sync code
→ timers, fetch
→ micro & macro
→ pushes back to stack

Closure Diagram

createCounter() execution context

let count = 0 (lives on after return)

returned increment/decrement/getCount functions form closures over count

107
Promise Diagram

Fulfilled
Pending → resolve(value)

Rejected
Pending → reject(reason)

Once settled (fulfilled or rejected), a Promise's state and value are immutable forever.

JavaScript One-Liners Worth Memorizing

Task One-liner

Remove duplicates [...new Set(arr)]

Flatten nested array [Link](Infinity)

Deep clone structuredClone(obj)

Swap variables [a, b] = [b, a]

Convert to number +str or Number(str)

Check array [Link](val)

Object to array of pairs [Link](obj)

Random integer 0-n [Link]([Link]() * n)

Unique random ID [Link]()

Sum array [Link]((a,b) => a+b, 0)

Final Note: Interviewers care less about memorized answers and more about how you reason through
unfamiliar problems. Use this handbook to build a strong foundation, but spend equal time practicing live
coding, explaining your thinking out loud, and asking clarifying questions. Good luck!

108

You might also like