JavaScript Interview Handbook
JavaScript Interview Handbook
The Complete
JavaScript
Interview Handbook
Beginner to Advanced
500+ JavaScript Interview Questions with Answers
2. Intermediate JavaScript 14
3. Advanced JavaScript 33
4. Coding Challenges 47
6. ES6+ Features 66
8. Asynchronous JavaScript 80
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.
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
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
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
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; }
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
}
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
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
// 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>`;
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
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
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
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
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"
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
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);
})();
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
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
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
[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";
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
};
}
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)
);
}
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");
[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"); }
};
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)
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}`;
}
}
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
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]
// Nested destructuring
const { address: { city } } = { address: { city: "NYC", zip: "10001" } };
[Link](city); // "NYC"
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
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
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;
};
}
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
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](" ");
}
};
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;
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
[Link]([Link]?.name); // "Alice"
[Link]([Link]?.city); // undefined (no error!)
[Link]([Link]?.notify?.()); // undefined, doesn't throw
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
// 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++;
}
}
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.
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
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
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
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
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
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);
};
}
[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;
};
}
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
// Shallow clone
const shallow = { ...original };
[Link] = "LA";
[Link]([Link]); // "LA" - nested ref shared!
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
// 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 }
};
}
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");
[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
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
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.
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"
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;
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);
}
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
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
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
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
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
53
Challenge 8: Implement a Custom [Link]
PROBLEM STATEMENT
SOLUTION
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] = [];
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"
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"
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.
[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
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.
[Link](typeof NaN);
[Link](typeof null);
[Link](typeof undefined);
[Link](typeof []);
[Link](typeof function(){});
ANSWER
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.
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?
ANSWER
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.
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?
ANSWER
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.
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?
ANSWER
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.
const obj = {
a: 10,
getA: function() {
return this.a;
}
};
ANSWER
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?
let x;
[Link](x ?? "default");
[Link](x || "default");
x = 0;
[Link](x ?? "default");
[Link](x || "default");
ANSWER
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();
}
}
[Link]([Link]().type);
[Link]([Link]().type);
ANSWER
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?
ANSWER
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.
ANSWER
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
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
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
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!)
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
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
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
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
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
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));
72
07
Browser & DOM Questions
DOM Manipulation, Events & Storage
Q81: What are the main ways to select and manipulate DOM elements?
ANSWER
EXAMPLE CODE
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");
});
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
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
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>
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
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
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
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.
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
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);
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
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
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
// 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
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)
}
// 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
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
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.
96
During the Interview
97
HR Round Tips
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
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.
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
Closure Diagram
outer() scope
let count = 0
Even after outer() returns, inner() retains a live reference to count — it is never garbage collected
while inner exists.
100
Type Coercion Quick Table
forEach No undefined
101
"this" Binding Quick Reference
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 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 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 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 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.
8 Shallow vs deep clone? Shallow copies top level only; deep copies all nested levels
106
Common Output Question Patterns
var in loop + setTimeout Shared binding -> all callbacks see final value
let in loop + setTimeout New binding per iteration -> each sees its own value
Closure Diagram
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.
Task One-liner
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