Mastering JavaScript for Interviews
Mastering JavaScript for Interviews
JavaScript interviews are unpredictable — but your preparation doesn’t have to be.
This book is designed as a complete, structured, and practical resource for anyone aiming to master
JavaScript for real-world technical interviews. With 180+ carefully curated questions, it covers the
full spectrum of JavaScript concepts: from fundamentals like scope and closures to advanced topics
such as event loops, Web APIs, memory optimization, async patterns, and modern ES features.
Every question is answered in a deep, explanatory, interview-style format. You’ll find clarity for
beginners, depth for experienced developers, and insights that help you understand why JavaScript
behaves the way it does — not just what it does.
1
Table of Contents -1
001. Difference between var, let, and const
002. What is hoisting in JavaScript?
003. Difference between == and ===
004. What is scope (global, function, block)?
005. What are closures?
006. What is the event loop?
007. Explain call stack and execution context
008. What are data types in JavaScript?
009. Difference between primitive and reference types
010. What is type coercion?
011. Explain truthy and falsy values
012. What is NaN and how do you check for it?
013. Explain ‘this’ keyword in JavaScript
014. Difference between implicit, explicit, default, and new binding
015. What is lexical scope?
016. Explain function declarations vs function expressions
017. What is an IIFE (Immediately Invoked Function Expression)?
018. Explain pure functions and side effects
019. What is immutability and why does it matter?
020. Difference between undefined, null, and NaN
021. What is strict mode?
022. What is the Temporal Dead Zone (TDZ)?
023. What are arrow functions and how are they different from normal functions?
024. Explain default parameters, rest operator, and spread operator
025. What are template literals?
026. Difference between for, for-in, for-of, and forEach
027. What are objects and how are they stored in memory?
028. Explain pass-by-value vs pass-by-reference
029. What is object destructuring and array destructuring?
030. Explain [Link], [Link], and [Link]
031. What is prototypal inheritance?
032. How does the prototype chain work?
033. What are constructor functions?
034. Explain class syntax in ES6 and how it’s sugar over prototypes
2
Table of Contents -2
3
Table of Contents -3
070. What are Web Workers and when should you use them?
071. What are Service Workers and PWA concepts?
072. Explain [Link], [Link], and [Link]
073. What is BigInt in JavaScript?
074. Explain dynamic imports and code splitting
075. What are optional chaining and nullish coalescing operators?
076. Explain Proxy and Reflect API
077. What is destructuring / aliasing and how is it useful?
078. What is module federation in modern JS apps?
079. Explain Virtual DOM and reconciliation in React conceptually (JS related)
080. What is event-loop starvation?
081. Explain call stack overflow and recursion depth limits
082. What are tagged template literals?
083. What is lazy evaluation in JS?
084. How does JavaScript handle memory leaks?
085. Explain hoisting with function expressions vs arrow functions
086. What is the Temporal API (upcoming JS proposal)?
087. What is requestAnimationFrame and when to use it?
088. Explain IntersectionObserver and MutationObserver APIs
089. What is the difference between innerHTML and textContent?
090. What are custom events and how do you dispatch them?
091. Explain microtask queue vs nextTick in [Link]
092. What is the difference between V8 engine internals and standard JavaScript?
093. How does just-in-time (JIT) compilation work in JavaScript engines?
094. Explain hidden classes and inline caching in V8
095. What are WeakRefs and FinalizationRegistry?
096. How does debounce–throttle combo optimize performance?
097. What are ArrayBuffer and TypedArray?
098. Explain SharedArrayBuffer and Atomics API
099. What is structured concurrency (upcoming spec)?
100. How does the ECMAScript spec define execution order?
101. Explain [Link] and environment internal slots
102. What is the Realms API and why might it matter for sandboxing?
103. How does the module resolution algorithm work in ESM?
104. What are decorators and how do they extend class behavior?
4
Table of Contents -4
5
Table of Contents -5
140. What is the Notification API and how can you request user permission?
141. Explain the Battery Status and Network Information APIs
142. What is the Fetch streaming API and how can you consume a streamed response?
143. What is the Web Share API and when is it useful?
144. How does the Web Crypto API provide secure randomness and hashing?
145. What is [Link] and why is it safer than [Link]?
146. Explain WebRTC basics — data channels and peer connections
147. What is AbortController and how do you use it to cancel fetch requests?
148. What is Content Security Policy (CSP) and why is it important?
149. What is cross-site scripting (XSS) and how can JavaScript prevent it?
150. What is cross-site request forgery (CSRF) and how can JS help mitigate it?
151. Explain sandboxed iframes and the same-origin policy in browsers
152. What is the Trusted Types API and how does it defend against XSS?
153. What is the Cache Storage API and how does it relate to Service Workers?
154. How do Progressive Web Apps (PWAs) leverage Service Workers for offline access?
155. What is the difference between deep clone and structuredClone for complex objects?
156. How do custom iterators work and how can you build your own iterable object?
157. What are ArrayBuffer and TypedArray, and how are they different from Arrays?
158. What are SharedArrayBuffer and Atomics, and how do they enable thread safety?
159. How do WeakRefs and FinalizationRegistry help manage memory?
160. What are transferable objects and how do they improve performance in Workers?
161. What is structured concurrency (upcoming spec) and how might it change async patterns?
162. Explain monkey patching, why it’s discouraged, and alternatives
163. What is the Realms API and why might it matter for sandboxing?
164. How does the ECMAScript spec define execution order at the spec level?
165. What are the pitfalls of floating-point arithmetic (0.1 + 0.2 ≠ 0.3)?
166. How can you achieve precise decimal arithmetic in JavaScript?
167. How do [Link] and [Link] support localization?
168. What are pluralRules and segmenter in the Intl API?
169. How does Temporal API improve date–time management compared to Date?
170. What are the limitations of [Link] and how to get cryptographically secure
randomness?
171. What triggers a reflow vs a repaint and how to minimize them?
172. How does compositing work in modern browsers?
173. What are layout thrashing and forced synchronous layouts?
6
Table of Contents-6
174. What is IntersectionObserver and how can it be used for lazy loading?
175. How do custom events improve component communication?
176. What is the difference between innerHTML, outerHTML, and textContent?
177. What is the difference between CORS preflight and simple requests?
178. How does sandbox attribute in iframes affect script execution?
179. What are cross-origin resource policies (CORP, COEP, COOP) and why do they matter?
180. What is a content-type sniffing attack and how can JS prevent it?
181. What is the Observer pattern and how is it implemented in JavaScript?
182. What is the Publish–Subscribe pattern and how does it differ from Observer?
183. Explain functional composition in JavaScript
184. What are Higher-Order Components (HOC) and render-props patterns conceptually?
185. What is dependency injection and can it be achieved in JavaScript?
186. What are singletons and their drawbacks in JavaScript?
187. What is event-driven architecture and how can it be implemented in JavaScript?
188. Explain memoization strategies and cache invalidation techniques
189. What is reactive programming and how does it differ from imperative programming?
190. How does decorator syntax enhance class behavior?
191. What are import assertions and how do they ensure module type safety?
192. What is module federation and how does it enable micro-frontend architectures?
193. What are WeakMap-based private fields and how do they differ from native private fields ()?
194. How does lazy vs eager evaluation affect performance in iterables?
195. What are WeakKeys and WeakRefs — new memory-safe references?
196. How do structuredClone, postMessage, and transferable objects relate?
7
Difference between var, let, and const
One-line answer: `var` is function-scoped and hoisted to `undefined`; `let`/`const` are block-scoped,
hoisted but uninitialized (TDZ). `let` can be reassigned, `const` cannot (reference only).
Choosing the right declaration prevents scope leaks, redeclaration bugs, and subtle hoisting issues—
especially in loops and async callbacks.
Code examples
```js
// Hoisting difference
[Link](a); // undefined (var hoisted and initialized)
var a = 5;
```
```js
// Block scope vs function scope
for (var i = 0; i < 3; i++) {
{
setTimeout(() => [Link]("var i:", i), 0);
8
}
}
// var i: 3,3,3
```
```js
// const: immutable binding, mutable object
const user = {{ name: "Alice" }};
- Thinking `const` makes the object immutable (it only freezes the binding).
- Using `var` in loops with async callbacks causes late binding bugs.
- Assuming `let`/`const` aren't hoisted—they are, but remain uninitialized (TDZ).
- Prefer `const` by default; use `let` when you know the value will change.
- Avoid `var` in modern code unless maintaining legacy code.
9
Related topics
- Hoisting, TDZ, Scope & Closures, Binding of `this`, Function vs Block scope.
Practice questions
1. What happens when you access a `let` variable before its declaration?
2. Why can you modify a `const` object's properties but not reassign it?
3. Rewrite the loop below to log 0, 1, 2:
```js
for (var i = 0; i < 3; i++) {
{
setTimeout(() => [Link](i), 100);
}
}
```
10
What is hoisting in JavaScript?
Hoisting in JavaScript
When JavaScript executes your code, it first performs a setup step where it registers all the variables
and functions it can find. This behavior is known as hoisting. Because of hoisting, some variables and
functions seem to exist even before the line where they are defined. However, different declarations
are hoisted in different ways.
Function declarations are available early and can be used anywhere in their scope. Variables declared
using var are also hoisted but start with a value of undefined. Variables declared with let, const, or
class are hoisted too, but they are left uninitialized. If you try to access them before their declaration
line, JavaScript throws a ReferenceError. This situation is known as the Temporal Dead Zone (TDZ).
Think of JavaScript running your file in two passes inside an execution context. In the first pass, called
the creation phase, JavaScript scans your code and sets up memory for all variables and functions. It
does not execute any statements yet; it only prepares the environment. In the second pass, called
the execution phase, JavaScript runs the code line by line and assigns actual values to the variables or
executes functions.
1. Function declarations
```js
function greet() {
[Link]("hi");
}
```
The entire function is stored in memory during setup, which means you can call greet anywhere in
the same scope, even before its line in the code.
2. var variables
```js
var a = 10;
```
11
The name a is created and initialized with undefined during the setup phase. The actual assignment
(= 10) happens later in the execution phase.
```js
let x = 1;
const y = 2;
class Person {}
```
These names are created but left uninitialized. Any attempt to read them before their declaration
line results in a ReferenceError. This waiting period is known as the Temporal Dead Zone.
When the execution phase begins, JavaScript runs your code from top to bottom. When it reaches
var a = 10, it assigns 10 to the already existing a. When it reaches let x = 1 or const y = 2, it initializes
them for the first time, exiting the TDZ. Function declarations were already ready to use, so they can
be called before their declaration lines.
The TDZ exists to prevent you from using variables before they are properly defined. Without it,
many confusing bugs would occur where variables show undefined values unexpectedly.
Here are some examples that show how hoisting works in different scenarios.
```js
[Link](a); // undefined (hoisted name + default value)
var a = 5;
12
```js
sayHi(); // works (function declaration is hoisted fully)
function sayHi() {
[Link]("Hi!");
}
try {
sayHello();
} catch (e) {
[Link]([Link]);
} // TypeError or ReferenceError
var sayHello = function () {
[Link]("Hello!");
};
try {
wave();
} catch (e) {
[Link]([Link]);
} // ReferenceError (TDZ)
let wave = () => [Link]("Wave!");
```
With var, the variable sayHello exists during setup and has the value undefined until its assignment
line. Function expressions and arrow functions behave like variable assignments and are not ready
until the line is executed. With let or const, the variable exists but is uninitialized until that line is
reached.
```js
try {
new Person();
} catch (e) {
[Link]([Link]);
} // ReferenceError (TDZ)
class Person {}
```
13
To visualize how hoisting works, imagine time flowing from top to bottom in your code.
```
CREATION PHASE (Setup, before running your lines)
- function greet -> ready (you can call it)
- var a -> exists with value undefined
- let b -> exists but uninitialized (TDZ)
- const c -> exists but uninitialized (TDZ)
There are several common misconceptions about hoisting. Some people think let and const are not
hoisted. In reality, they are hoisted, but they remain uninitialized until their declaration line. Others
think hoisting moves code to the top. That is not true; the JavaScript engine only creates memory
bindings for variables and functions during setup. Another misconception is that function
expressions are hoisted like function declarations. Only function declarations are hoisted with their
body ready. Function expressions or arrow functions depend on whether the variable is declared
using var, let, or const. Finally, var is sometimes used in loops with asynchronous code, which often
causes bugs because all iterations share the same variable. It is better to use let in such cases.
There are also some subtle edge cases you may encounter.
```js
var x = 1;
var x = 2; // allowed
[Link](x); // 2
```
var allows redeclaration in the same scope, which can lead to unexpected results.
14
TDZ with default parameters referencing later bindings
```js
let y = 1;
function f(a = y) {
return a;
}
[Link](f()); // 1
function g(a = z) {
return a;
}
let z = 2;
```
In this case, you cannot read z in the default parameter of g until z has been initialized.
Modules and hoisting behave slightly differently. ES modules always run in strict mode and have their
own rules. Imports are hoisted and must appear at the top level of the file. They are ready before the
module code runs. If you use top-level await, it pauses the module's execution until the awaited
promise is resolved.
In practice, it is best to use const by default and let only when you need to change a variable's value.
Avoid using var in modern JavaScript because it can lead to confusing behavior. Use function
declarations when you need to call functions from anywhere in the scope, and use function
expressions or arrow functions when you want them to be created at a specific time in execution.
15
1. Why does [Link](a) show undefined but [Link](b) throws a ReferenceError before their
declarations if a is declared with var and b is declared with let?
2. Predict the output of the following code.
```js
say();
var say = function () {
[Link]("hi");
};
```
```js
for (var i = 0; i < 3; i++) {
setTimeout(() => [Link](i), 0);
}
```
```js
for (let i = 0; i < 3; i++) {
setTimeout(() => [Link](i), 0);
}
```
Or by capturing i:
```js
for (var i = 0; i < 3; i++) {
((iCopy) => setTimeout(() => [Link](iCopy), 0))(i);
}
```
16
Difference between == and ===
Difference between == and === in JavaScript
In JavaScript, both the double equals (==) and triple equals (===) are comparison operators used to
check if two values are equal, but they work differently. The difference lies in whether they perform
type conversion before comparing.
The double equals operator (==) checks for equality after converting both values to a common type.
This process is called type coercion. JavaScript tries to make both sides the same type before
comparing, which can sometimes produce unexpected results. The triple equals operator (===)
checks for equality without converting types. It compares both value and data type exactly as they
are.
Because of this, == is called the loose equality operator, and === is called the strict equality operator.
When you use ==, JavaScript may convert strings, numbers, booleans, or even null and undefined to
make the comparison possible. This can make your code unpredictable if you are not aware of how
coercion works.
For example:
```js
[Link](5 == "5"); // true, because "5" is converted to a number
[Link](0 == false); // true, because false is converted to 0
[Link](null == undefined); // true, special rule in JS
[Link]("0" == false); // true, because both sides are converted to 0
```
All these return true even though the data types are different. This happens because the == operator
tries to make the values the same type before comparing.
```js
[Link](5 === "5"); // false, number vs string
[Link](0 === false); // false, number vs boolean
[Link](null === undefined); // false, different types
[Link]("0" === false); // false, string vs boolean
```
17
With ===, no type conversion happens. JavaScript checks both the type and the actual value. If either
differs, the result is false.
To understand this better, you can think of === as a stricter form of comparison. It is like asking, "Are
these two values exactly the same, including their type?" On the other hand, == is like asking, "Can
these two values be considered the same after some conversion?"
When comparing objects or arrays, both operators behave the same way. They only check if both
sides refer to the exact same object in memory.
```js
let a = [1, 2];
let b = [1, 2];
[Link](a == b); // false
[Link](a === b); // false
let c = a;
[Link](a === c); // true, because both point to the same object
```
Common misconceptions
1. Some developers believe == and === are interchangeable. They are not. Using == can cause
unexpected true or false results because of type coercion.
2. null and undefined are equal with == but not with ===. This can lead to subtle bugs if you are
checking for missing values.
3. Comparing objects with == or === never compares their contents, only their references.
4. Using == for numeric comparisons with strings or booleans can create unpredictable results,
especially when data comes from user input or APIs.
Practical examples
```js
[Link](1 == true); // true, true becomes 1
[Link](1 === true); // false, number vs boolean
18
[Link]("" == false); // true, both convert to 0
[Link]("" === false); // false, string vs boolean
```
As you can see, == tries to help by converting types, but this often causes confusion. That is why
most developers use === by default.
Best practices
Always use === unless you specifically want type coercion. The strict equality operator makes your
code more predictable and easier to debug. If you really need to compare values with different types,
convert them manually using functions like Number(), String(), or Boolean() before comparing.
```js
[Link](Number("5") === 5); // true, both are numbers now
```
Practice questions
```js
[Link]("" == 0);
[Link]("" === 0);
19
```
3. Why does null == undefined return true but null === undefined return false?
4. How would you safely compare a string number like "10" with an actual number without
unexpected results?
20
What is scope (global, function, block)?
What is scope in JavaScript (global, function, block)
Scope is the area of a program where a variable or function name is visible and can be accessed.
Understanding scope helps you predict where a value can be read or changed and prevents
accidental name collisions. In JavaScript there are three primary kinds of scope you will use every
day: global scope, function scope, and block scope. There is also module scope in ES modules, which
behaves like file level scope, but this note focuses on the everyday three.
Global scope means a name is available everywhere in your program after it is defined. In a browser,
global variables become properties of the window object. In [Link], the global object is different,
and variables declared with let or const at the top level of a module are not added to the global
object. Global scope is convenient but dangerous because any part of the program can read or
change that value. Prefer limiting scope where possible.
Function scope is created each time a function is called. Variables declared with var inside a function
are visible anywhere inside that function but not outside it. Because var is function scoped, using it
inside if or for blocks does not limit its visibility to those blocks; it remains available everywhere in
the function.
Block scope is created by a pair of braces. The most common blocks are those from if, for, while, try,
and just plain braces. Variables declared with let or const are block scoped. They exist only inside
that block and are not accessible outside. This prevents accidental leaks and makes code easier to
reason about. Classes declared with class also follow block scoping rules. Variables in block scope
have a temporal dead zone before the declaration line, which means you cannot access them before
their declaration executes.
Shadowing happens when an inner scope declares a name that already exists in an outer scope.
Inside the inner scope, the new declaration hides the outer one. Shadowing can be useful for clarity
when parameter names repeat, but overusing it can reduce readability.
Lexical scope means that JavaScript decides which variables are visible based on where functions and
blocks are written in the code, not based on where they are called at runtime. A function can access
variables from the scope where it was defined. This idea is the foundation for closures.
Examples
```js
// global scope example
let siteName = "Docs";
function show() {
21
[Link](siteName); // can read global
}
show();
```
```js
// function scope with var
function demo() {
var x = 1;
if (true) {
var x = 2; // same function-scoped variable
}
[Link](x); // 2
}
demo();
```
```js
// block scope with let and const
function demo2() {
let a = 1;
if (true) {
let a = 2; // different, block-scoped variable
const b = 3;
[Link](a, b); // 2 3
}
[Link](a); // 1
// [Link](b); // ReferenceError
}
demo2();
```
```js
// shadowing and lexical scope
const value = "outer";
function make() {
const value = "inner";
return function () {
22
[Link](value); // reads "inner" because of lexical scope
};
}
make()();
```
Common misconceptions
1. Variables declared with var in a block are block scoped. In reality, var is function scoped and leaks
out of the block.
2. Global variables declared with let or const become properties on window. In browsers only var at
the top level does that; let and const at top level of a script create global bindings but not window
properties, and in modules they are module scoped.
3. A variable defined inside a function can be accessed after the function returns. This is only possible
through closures, not by direct access.
Practice questions
1. Explain the difference between function scope and block scope using a short code example.
2. Why is global scope considered risky, and how can you reduce reliance on it in a large application.
3. What is shadowing, and when could it reduce readability.
23
What are closures?
What are closures
A closure is one of the most important - and often misunderstood - ideas in JavaScript.
In simple words, a closure is a function that carries a memory of the environment in which it was
created. It remembers the variables that were around it at the time it was defined, even after that
outer code has finished executing.
To understand why this happens, you first need to recall that JavaScript uses lexical scope - meaning
variable visibility is determined by where a function is written in the code, not by where it is called
later.
Because of lexical scope, an inner function automatically "knows" about the variables defined in its
outer function.
This is a closure in action: the combination of the function and the preserved environment around it.
2. A simple example
```js
function makeCounter() {
let count = 0; // this variable lives in makeCounter's scope
return function () {
count++; // inner function can still access count
return count;
};
}
24
const counter = makeCounter();
[Link](counter()); // 1
[Link](counter()); // 2
[Link](counter()); // 3
```
Even though makeCounter no longer exists in memory as a running function, the variable count
remains because the closure is still holding onto it.
It's very important to understand that closures don't store copies of variables - they store references.
That means if a variable's value changes after the closure is created, the closure will see the updated
value.
```js
function outer() {
let message = "Hello";
return function inner() {
[Link](message);
};
}
25
That's why they're so powerful - they can reflect changes over time.
Before ES6 introduced classes with private fields, closures were the main way to create private data
in JavaScript - variables that can't be accessed from outside but are still remembered internally.
```js
function createAccount() {
let balance = 0;
return {
deposit(amount) {
balance += amount;
[Link]("Deposited:", amount);
},
getBalance() {
return balance;
},
};
}
Closures often appear naturally in asynchronous operations like setTimeout, Promise, or event
listeners.
The inner function runs later, but still remembers the variables from when it was defined.
26
```js
function greet(name) {
setTimeout(function () {
[Link]("Hello, " + name);
}, 1000);
}
greet("Alice");
```
Even though greet finishes before one second passes, the callback inside setTimeout still knows what
name was - because it carries a closure around it.
Closures aren't just theoretical - they power many common programming patterns:
Data encapsulation: hiding internal state, like private variables.
Function factories: creating multiple customized versions of a function.
Memoization: remembering results for faster future calculations.
Event handlers and callbacks: retaining access to state across time.
Modules: grouping related code with private internal data.
```js
function multiplier(factor) {
return function (n) {
return n * factor;
};
}
[Link](double(5)); // 10
[Link](triple(5)); // 15
```
27
Each inner function remembers its own factor - carried in its own backpack from when it was
created.
Closures keep their referenced variables alive in memory as long as they are reachable.
If you store many closures or forget to release them when no longer needed, those variables won't
be garbage-collected.
That's not a bug - it's just how JavaScript ensures your closures keep working.
In most normal cases, the runtime cleans up automatically once closures are no longer used.
8. Summary
Or in simpler words:
A closure is a function plus its backpack of remembered variables.
Closures make JavaScript functions powerful and flexible, letting you preserve state, hide data, and
control behavior across time - all thanks to lexical scoping and how JavaScript keeps those
"backpacks" alive even after the original context is gone.
Examples
```js
// private state counter
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const c1 = makeCounter();
[Link](c1()); // 1
[Link](c1()); // 2
```
28
```js
// function factory
function greeter(greeting) {
return function (name) {
return greeting + ", " + name;
};
}
const hello = greeter("Hello");
[Link](hello("Sam")); // Hello, Sam
```
```js
// async with closure
function delayedLog(msg) {
for (let i = 1; i <= 3; i++) {
setTimeout(() => [Link](msg, i), i * 100);
}
}
delayedLog("Step"); // Step 1, Step 2, Step 3
```
Common misconceptions
1. Closures copy values. They actually keep references to variables, so you observe updated values,
not frozen snapshots.
2. Closures always cause memory leaks. They only keep what is referenced. If nothing references the
inner function, the closure can be collected.
3. Using var in loops with callbacks works the same as let. With var, each iteration shares the same
variable, which surprises many developers.
Practice questions
1. Implement a once utility so a function runs at most one time and returns the same result on later
calls.
2. Write a memoize function that caches results by argument for a pure function.
3. Explain why var often behaves unexpectedly in loops with asynchronous callbacks and show a fix.
29
30
What is the event loop?
What is the event loop
The event loop is the coordination system that allows JavaScript to appear concurrent, even though
it runs on a single thread. It manages what code runs now, what runs next, and when the browser
can repaint the screen. Every JavaScript environment, like a browser or [Link], includes an event
loop, though their specific queues and priorities may differ slightly.
When JavaScript first starts running a script, it executes all synchronous code line by line on the call
stack. This is the main thread of execution. When asynchronous operations like timers, fetch calls, or
user interactions occur, they register callbacks to be executed later, once their operation finishes.
Those callbacks are stored in different types of queues.
If the stack is empty, the event loop picks the next appropriate task and pushes it onto the stack to
execute.
There are two main kinds of tasks the event loop manages — macrotasks and microtasks — and
understanding their order is crucial.
Macrotasks (often just called tasks) include things like setTimeout, setInterval, setImmediate (in
[Link]), I/O callbacks, and events such as clicks or network responses. Microtasks include promise
.then() callbacks, async/await continuations, and functions queued using queueMicrotask.
Microtasks always run before the event loop moves to the next macrotask.
The event loop takes one macrotask from the macrotask queue (for example, the initial script or a
setTimeout callback) and executes it fully, top to bottom.
When that macrotask completes, the event loop checks the microtask queue.
All microtasks in the queue are executed, one after another, until the microtask queue is completely
empty.
31
Once the microtasks finish, the browser gets a chance to perform rendering or painting if needed.
Then, the loop picks the next macrotask and repeats the same steps.
Because microtasks are processed right after each macrotask and before rendering, promise .then()
callbacks can run sooner than you might expect. For example, if you schedule both a promise and a
setTimeout with zero delay, the promise handler will always run first because it is a microtask, and
microtasks are drained before the next macrotask starts.
```js
[Link]("Start");
[Link]("End");
```
This happens because the main script itself is a macrotask. After it finishes, the event loop looks at
the microtask queue. The promise .then() is there, so it runs before the next macrotask (the
setTimeout callback).
If you add more microtasks while executing microtasks, those newly added ones are also run before
the event loop returns to macrotasks. This is why microtasks can "chain" indefinitely if they keep
scheduling more microtasks.
Another important detail is rendering. The browser does not repaint the screen in the middle of
microtasks. Rendering occurs only after the microtask queue is empty and before starting the next
macrotask. That's why promise-heavy operations can block the visual update even though they seem
asynchronous.
32
In [Link], the terminology differs slightly, but the logic is similar. Node has phases like timers,
pending callbacks, I/O polling, and a microtask queue that behaves like the browser's.
Run one macrotask (e.g., a piece of code, setTimeout callback, I/O event).
If more microtasks appear during this step, keep running them until none remain.
Repeat indefinitely.
This precise sequence ensures that JavaScript remains non-blocking, responsive, and predictable
despite running in a single thread.
Examples
```js
[Link]("A");
setTimeout(() => [Link]("B"), 0);
[Link]().then(() => [Link]("C"));
[Link]("D");
// Order: A, D, C, B
```
```js
// microtasks drain before the next macrotask
setTimeout(() => [Link]("timeout"), 0);
[Link]().then(() => [Link]("then-1"));
[Link]().then(() => [Link]("then-2"));
// then-1, then-2, timeout
```
33
```js
// queueMicrotask behaves like a promise microtask
queueMicrotask(() => [Link]("microtask"));
[Link]("sync");
// sync, microtask
```
Common misconceptions
1. setTimeout with zero delay runs immediately. It schedules a task that runs only after the current
call stack is empty and microtasks have run.
2. Promises are faster by themselves. It is not about speed but about queue priority. Promise
callbacks run in the microtask queue which is processed before the next task.
3. The event loop belongs to JavaScript the language. The event loop is provided by the host
environment (browsers, [Link]) which integrates timers, I/O, and rendering.
Practice questions
1. Predict the output order of logs when both setTimeout and [Link] are used together.
2. Explain why a long running while loop blocks click handlers from running.
3. Show how to yield back to the event loop to keep the UI responsive during heavy computation.
34
Explain call stack and execution context
Explain call stack and execution context
The call stack is a structure that tracks what function is currently running and which function to
return to when it finishes. Each time a function is called, the runtime creates a new frame and
pushes it onto the stack. When the function returns, its frame is popped. If the stack grows too deep
through unbounded recursion, a stack overflow error occurs.
An execution context is the environment in which a piece of JavaScript runs. It includes the scope
chain, the bindings for variables and functions, the value of this, and references to outer
environments. There is a global execution context created when the script starts. Each function call
creates a new function execution context. During function creation, JavaScript also records the lexical
environment that will be used later when the function runs; this makes closures possible.
Creation and execution happen in two phases for each context. In the creation phase, the engine
allocates memory for declarations and sets up the scope and this binding. In the execution phase, the
code runs line by line, reading and writing variables and calling other functions. When a function
calls another function, a new context is created and pushed onto the call stack above the current
one.
Examples
```js
function a() {
[Link]("in a");
b();
[Link]("back to a");
}
function b() {
[Link]("in b");
}
a();
// Stack behavior: enter a, enter b, exit b, back to a, exit a
```
```js
// two-phase model inside a function
function sum(x, y) {
// creation phase sets up x, y, and the environment
return x + y; // execution phase reads values and returns
}
[Link](sum(2, 3));
35
```
```js
// overflow example (do not run in production)
function recur() {
return recur();
}
// recur(); // RangeError: Maximum call stack size exceeded
```
Common misconceptions
1. The call stack shows asynchronous callbacks waiting. Only running frames are on the stack.
Asynchronous callbacks wait in queues until picked up.
2. The value of this is the same in every function. It depends on how the function is called, not just
where it is defined.
3. Execution context and scope are the same thing. Scope is part of the execution context, which
includes additional details like this and the outer environment.
Practice questions
1. Describe what happens on the call stack when a function calls another function that then throws
an error.
2. Explain how execution context creation and execution phases relate to hoisting.
3. Why does unbounded recursion cause a stack overflow, and how can you avoid it.
36
What are data types in JavaScript
What are data types in JavaScript
JavaScript has a small set of built in types. These types define how values behave, how they compare
to each other, and what operations are valid. There are primitive types and reference types. Primitive
types are immutable and compared by value. Reference types are objects and compared by
reference.
The primitive types are number, string, boolean, null, undefined, symbol, and bigint. Number
represents both integers and floating point values including special values like NaN and Infinity.
String is a sequence of characters. Boolean represents true or false. Null represents an intentional
empty value. Undefined means a variable has been declared but not assigned a value. Symbol
creates unique identifiers useful for object keys that should not collide. Bigint represents integers of
arbitrary size beyond the safe range of number.
Objects are collections of key value pairs and include plain objects, arrays, functions, dates, and
many other built in structures. Functions are callable objects. Arrays are ordered collections with a
length property and numeric indices. Most values you create with curly braces or constructors are
objects and are passed by reference.
Type inspection can be done with typeof, [Link], and other utilities. typeof works reliably for
primitives except that typeof null historically returns object for legacy reasons. Arrays report typeof
object, so use [Link] to detect them. For class instances, you can use instanceof to check
prototype relationships.
Examples
```js
[Link](typeof 42); // "number"
[Link](typeof "hi"); // "string"
[Link](typeof true); // "boolean"
[Link](typeof undefined); // "undefined"
[Link](typeof Symbol("s")); // "symbol"
[Link](typeof 10n); // "bigint"
[Link](typeof null); // "object" (legacy quirk)
[Link](typeof {}); // "object"
[Link]([Link]([])); // true
```
```js
// objects by reference
const a = { x: 1 };
const b = a;
37
b.x = 2;
[Link](a.x); // 2
```
```js
// numbers and string conversions
[Link](Number("10")); // 10
[Link](String(99)); // "99"
```
Common misconceptions
1. Null and undefined mean the same thing. Null is an intentional empty value; undefined means not
assigned yet.
2. Arrays are a separate typeof result. Arrays are objects; use [Link] to detect them.
3. Bigint and number can be mixed freely. You cannot mix them directly in arithmetic without explicit
conversion.
Practice questions
1. List all primitive types and describe a use case for symbol and bigint.
2. Why does typeof null return object, and how do you reliably test for null.
3. When would you prefer an array over an object and why.
38
Difference between primitive and reference types
Difference between primitive and reference types
Primitive types are number, string, boolean, null, undefined, symbol, and bigint. These values are
immutable and compared by value. When you assign a primitive to a new variable or pass it to a
function, a copy of the value is made. Changing the new variable does not affect the original.
Reference types are objects, including arrays and functions. These values are stored by reference.
When you assign an object to another variable, both variables refer to the same underlying object.
Changing a property through one reference is visible through the other. Equality comparisons for
objects check whether two references point to the same object, not whether their contents are
equal.
Stack and heap are common mental models. Engines are free to implement however they like, but
the model is useful: primitives are often small fixed size values and can be copied easily, while
objects may live in managed memory with garbage collection and are accessed by references.
Examples
```js
// primitives copy by value
let a = 5;
let b = a;
b = 7;
[Link](a, b); // 5 7
```
```js
// objects copy by reference
const p = { n: 1 };
const q = p;
q.n = 2;
[Link](p.n); // 2
```
```js
// equality
[Link]({} === {}); // false, different objects
const r = {};
const s = r;
[Link](r === s); // true, same reference
```
39
```js
// shallow copy vs deep copy
const user = { name: "A", meta: { views: 1 } };
const shallow = { ...user }; // shallow copy
[Link] = 5;
[Link]([Link]); // 5
```
Common misconceptions
1. Objects are passed by reference. JavaScript passes arguments by value; the value for objects is a
reference. Reassigning the parameter does not change the caller's variable, but mutating the object
does.
2. Spreading or [Link] always creates a deep copy. They only copy one level by default.
3. Two objects with the same properties are equal with ===. They are equal only if they are the same
reference.
Practice questions
1. Show how to deep clone a nested object without mutating the original using structuredClone or a
library.
2. Explain why a function cannot reassign an object parameter to replace the caller's variable but can
mutate its properties.
3. Give an example where a shallow copy causes an unexpected mutation in the original object.
40
What is type coercion
What is type coercion
Type coercion is when JavaScript converts a value from one type to another so that an operation can
proceed. Coercion happens in two main ways: implicit and explicit. Implicit coercion occurs when
operators or comparisons convert values automatically. Explicit coercion is when you convert values
directly using Number, String, Boolean, or other APIs.
The language defines conversion rules for many operations. For arithmetic with the plus operator, if
either operand is a string and the other is not an object with a special behavior, JavaScript converts
the other operand to a string and concatenates. For subtraction, multiplication, and division,
JavaScript converts operands to numbers. For comparisons with double equals, the runtime attempts
to convert both sides to a common type, which can lead to results that surprise people who are not
aware of the rules.
Objects convert to primitives by trying valueOf and toString in a specific order depending on the
operation. Symbols do not coerce to strings implicitly to avoid accidental leaks. Bigints do not mix
with numbers without explicit conversion.
Examples
```js
// string concatenation vs numeric addition
[Link]("10" + 5); // "15"
[Link](10 + "5"); // "15"
[Link](10 - "5"); // 5
[Link]("10" * "2"); // 20
```
```js
// explicit coercion
[Link](Number("12")); // 12
[Link](String(7)); // "7"
[Link](Boolean("")); // false
[Link](Boolean("hi")); // true
```
```js
// equality coercion
[Link](0 == false); // true
[Link]("" == 0); // true
[Link](null == undefined); // true
41
[Link]("0" == false); // true
```
```js
// object to primitive
const price = {
value: 1000,
valueOf() { return [Link]; }
};
[Link](price + 50); // 1050 (valueOf used)
```
Common misconceptions
1. Coercion is always bad. Coercion is a tool; confusion comes from not knowing the rules. Use strict
equality to avoid surprises where needed.
2. The plus operator always adds numbers. If either side is a string, plus concatenates.
3. Boolean conversion treats any non empty string as true but the string "0" is also true. Only the
empty string is false.
Practice questions
1. Show how different operators trigger coercion with examples for +, -, ==, and Boolean conversion.
2. Explain how an object with a custom valueOf can influence arithmetic results.
3. Why does 0 == false evaluate to true but 0 === false is false, and how would you make such
comparisons safer.
42
JavaScript does not permanently convert the value; it only interprets it as true or false for that
operation using an internal ToBoolean step. This is part of type coercion.
Examples
```js
if ("hello") [Link]("truthy"); // non-empty string is truthy
if (0) [Link]("never runs"); // 0 is falsy
if ([]) [Link]("runs"); // empty array is truthy
if ({}) [Link]("runs"); // empty object is truthy
if (null) [Link]("no"); // null is falsy
```
```js
[Link](Boolean("")); // false
[Link](Boolean(" ")); // true (string with a space)
[Link](Boolean(123)); // true
[Link](Boolean(0)); // false
[Link](Boolean([])); // true
[Link](Boolean({})); // true
```
Common misconceptions
1. Empty arrays and empty objects are falsy. All objects are truthy, even empty ones.
2. The string "false" is falsy. Any non-empty string is truthy.
3. NaN behaves like 0 in truthiness. NaN is falsy.
Practice questions
1. List all falsy values in JavaScript and explain why "0" is truthy but 0 is falsy.
2. Explain why [] && {} returns {} but [] || {} returns [].
3. Predict the behavior of if ("0") and if (0) and explain the difference.
43
What is NaN and how do you check for it
What is NaN and how do you check for it
NaN stands for Not-a-Number. It is a special numeric value that represents the result of an invalid or
undefined numeric operation. NaN has typeof "number" because it lives in the number type domain,
but it signals that a meaningful numeric value could not be produced.
You get NaN from operations like 0 / 0, parsing non-numeric strings as numbers, square roots of
negative numbers (in real arithmetic), or any arithmetic expression that already contains NaN. Once
NaN is produced, it contaminates further arithmetic: any operation involving NaN typically yields
NaN again.
Examples
```js
[Link](typeof NaN); // "number"
[Link](0 / 0); // NaN
[Link]([Link](-1)); // NaN
[Link](parseInt("abc")); // NaN
[Link](Number("12x")); // NaN
```
A unique property of NaN is that it is not equal to anything, including itself. Therefore NaN === NaN
is false. This design preserves the idea that different invalid results should not compare equal.
To check for NaN, prefer [Link](value). It returns true only when the value is actually the
special NaN value. The older global isNaN(value) first coerces the argument to a number, so it can
report true for non-numeric strings, which is often not what you want.
```js
[Link](NaN === NaN); // false
[Link]([Link](NaN)); // true
[Link]([Link]("hello")); // false
[Link](isNaN("hello")); // true (coerces to number → NaN)
```
Common misconceptions
1. NaN means "not a numeric type". It is a number type value meaning "invalid numeric result".
44
2. isNaN and [Link] are interchangeable. isNaN coerces; [Link] is strict and safer.
3. You can detect NaN using equality or inequality operators. NaN never equals anything, not even
itself.
Practice questions
1. Name three operations that can produce NaN and explain why.
2. Why does NaN !== NaN evaluate to true, and how do you correctly test for NaN?
3. Write a function isNumeric(val) that returns true only for finite numbers (hint: use typeof,
[Link]).
45
Explain ‘this’ keyword in JavaScript
Explain this keyword in JavaScript
In JavaScript, the this keyword is one of the most confusing topics for beginners because it behaves
differently from how people expect in other languages. But once you understand what decides the
value of this (the call site, not the definition site), it becomes consistent and logical.
Think of this as a reference to the current "owner" of the function call - the object that the function
is being executed "on."
But JavaScript does not decide this when you write the function - it decides it each time the function
is called, depending on how the function is called.
1. Global context
When you use this in the global scope (outside of any function), the value depends on the
environment and mode.
In a browser, outside of strict mode, this refers to the global object - that's window.
In [Link], it refers to an empty object in modules, not the global object.
In strict mode, this is undefined in global functions.
```js
[Link](this === window); // true (in browsers, non-strict mode)
```
If you add "use strict"; at the top, then inside a normal function that isn't attached to an object:
```js
"use strict";
function show() {
[Link](this);
}
show(); // undefined
```
```js
46
const user = {
name: "Alice",
greet() {
[Link]("Hi, I'm " + [Link]);
},
};
[Link](); // "Hi, I'm Alice"
```
If you separate the function from the object, the connection is lost:
That means whatever this was outside the arrow function will also be the value inside it.
This makes arrow functions perfect for callbacks and event handlers where you want to preserve the
outer context.
```js
const team = {
title: "Developers",
listMembers() {
setTimeout(() => {
[Link]("Team: " + [Link]); // uses team's this
}, 100);
},
};
[Link](); // "Team: Developers"
```
47
If we used a normal function instead of an arrow here, this inside the setTimeout callback would not
refer to team.
JavaScript lets you manually decide what this should be when you call a function.
call executes the function immediately, with this set to the first argument.
apply does the same, but arguments are passed as an array.
bind creates a new function with a permanently fixed this.
```js
function sayHi() {
[Link]([Link]);
}
[Link](user); // "John"
[Link](admin); // "Admin"
This is useful when you need to control context explicitly, especially when passing functions as
callbacks.
When you call a function with the new keyword, JavaScript automatically:
Creates a new empty object.
Sets this inside the function to point to that object.
Runs the function body.
Returns the new object (unless the function returns a different object).
```js
48
function Person(name) {
[Link] = name;
}
const p = new Person("Alice");
[Link]([Link]); // "Alice"
```
Here, this inside Person refers to the new instance being created.
If you forget new, this won't refer to the new object — it will follow the default rule instead, often
causing bugs.
6. Event handlers
In browser event handlers (like onclick), this automatically refers to the element that received the
event — unless you're using arrow functions, which do not have their own this.
```js
[Link]("button"). () {
[Link](this); // the button element
};
[Link]("button"). => {
[Link](this); // probably window, not the button
};
```
7. Binding priority
Example:
```js
49
function show() {
[Link]([Link]);
}
const obj = { value: "obj", show };
Here, even though we bound the function, new takes precedence and creates a new this.
Examples
```js
function show() {
[Link](this);
}
show(); // undefined in strict mode; window in sloppy mode
```
```js
const user = {
name: "Sam",
greet() {
[Link]([Link]);
},
50
};
[Link](); // "Sam"
const fn = [Link];
fn(); // undefined or window depending on mode
```
```js
const obj = {
name: "A",
printLater: function () {
setTimeout(() => [Link]([Link]), 50); // arrow keeps outer this
},
};
[Link](); // "A"
```
```js
function hi() {
[Link]([Link]);
}
[Link]({ msg: "Hello" }); // "Hello"
```
Common misconceptions
1. this points to the function itself. It points to the call-time receiver object, not the function.
2. Arrow functions take this from the caller. They capture this from where they are defined.
3. Binding once changes this forever. bind returns a new function with a fixed this; the original is
unchanged.
Practice questions
1. Explain the difference in this between arrow functions and regular functions with an example.
2. How would you preserve a method's this when passing it as a callback to an event listener?
3. What does new do to this inside a constructor function?
51
52
Difference between implicit, explicit, default, and new
binding
Difference between implicit, explicit, default, and new binding
JavaScript resolves "this" using four main rules. Default binding applies when a function is called
without a receiver object. In non-strict mode, this becomes the global object; in strict mode, this is
undefined. Implicit binding applies when you call a function as a property of an object; the object to
the left of the dot becomes this. Explicit binding uses call, apply, or bind to set this to a specific
object regardless of how the function is invoked. New binding applies when you use new; it creates a
fresh object, sets it as this inside the constructor, and returns it unless the constructor returns an
object explicitly.
When multiple rules could apply, the priority is: new binding first, then explicit binding, then implicit
binding, then default binding.
Examples
```js
function show() {
[Link]([Link]);
}
// default
show(); // undefined (strict) or global name (non-strict)
// implicit
const user = { name: "Sam", show };
[Link](); // "Sam"
// explicit
[Link]({ name: "Alex" }); // "Alex"
// new
function Person(name) {
[Link] = name;
}
const p = new Person("Ravi");
[Link]([Link]); // "Ravi"
53
```
Common misconceptions
1. Arrow functions follow these rules. Arrow functions ignore all four and capture this lexically.
2. bind mutates the original function. It returns a new function with fixed this.
3. call or apply can override new. If new is used, new binding wins.
Practice questions
54
What is lexical scope
What is lexical scope
Lexical scope means variable visibility is determined by where code is written, not by where it is
called. Functions and blocks create scopes. Inner scopes can access names from their outer scopes,
but not the reverse. This structure is fixed at parse time and provides a predictable chain for name
lookup at runtime.
When code runs, the engine looks for a variable starting in the current scope and then walks outward
through enclosing scopes until it finds a match or reaches the global scope. This model enables
closures, because a function keeps access to the scope where it was defined even after that outer
function returns.
Examples
```js
const outer = "outside";
function a() {
const inner = "inside";
[Link](outer); // accesses outer
}
a();
// [Link](inner); // ReferenceError
```
```js
function makeAdder(x) {
return function (y) {
return x + y; // x comes from lexical scope
};
}
const add5 = makeAdder(5);
[Link](add5(2)); // 7
```
Common misconceptions
55
1. Lexical scope changes based on the caller. It is determined at definition time.
2. Only functions create scope. Blocks with let/const also create scope.
3. Closures create new scopes out of thin air. They capture existing lexical environments.
Practice questions
56
Explain function declarations vs function expressions
Explain function declarations vs expressions
Function declarations and function expressions define functions but differ in when they become
available and how they are used. A function declaration appears as a statement beginning with the
function keyword. It is hoisted, which means the entire function is available before its line in the
code. A function expression defines a function inside an expression, for example by assigning it to a
variable. Expressions are not hoisted as callable functions; only the variable's binding is created early
(undefined for var, temporal dead zone for let/const), and the function value is set at runtime when
that line executes.
Function expressions may be anonymous or named. Named expressions can aid debugging and allow
self-reference for recursion.
Examples
```js
// declaration
greet();
function greet() {
[Link]("Hi");
}
```
```js
// expression
const sayHi = function () {
[Link]("Hello");
};
sayHi();
```
```js
// named expression for recursion
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
[Link](factorial(5)); // 120
57
```
Common misconceptions
1. All functions are hoisted the same way. Only declarations are fully hoisted.
2. A named function expression leaks its name to the outer scope. The name is only visible inside the
function body.
3. Declarations inside blocks behave uniformly across modes. Block scoping rules and strict mode can
affect visibility.
Practice questions
58
What is an IIFE (Immediately Invoked Function
Expression)?
What is IIFE (Immediately Invoked Function Expression)
IIFEs are useful for one-time setup, initializing modules, and keeping temporary variables private.
Arrow functions can be used as IIFEs too.
Examples
```js
(function () {
const message = "Runs now";
[Link](message);
})();
```
```js
(function (name) {
[Link]("Hello " + name);
})("Sam");
```
```js
const result = (() => {
const x = 2,
y = 3;
return x * y;
})();
[Link](result); // 6
```
Common misconceptions
59
1. IIFEs are obsolete after ES6. They still help with one-off initialization and encapsulation.
2. Only function keyword works. Arrow IIFEs work as well.
3. IIFEs create globals. They prevent globals by scoping variables locally.
Practice questions
60
018. Explain pure functions and side effects
Explain pure functions and side effects
A pure function always produces the same output for the same input and does not cause any
observable changes outside itself. It does not read or write global state, mutate its parameters,
perform I/O, or rely on time or randomness unless those are passed in as inputs. Pure functions are
predictable, easy to test, and simple to reason about.
A side effect in programming refers to any action a function performs that affects something outside
itself, or depends on something that can change outside its control. It's called a "side" effect because
it happens alongside the main purpose of the function — instead of just returning a value, the
function is also changing the world around it.
Every function has its own scope — the variables and values defined inside it.
If a function reads or modifies something beyond its scope, that's considered interacting with the
outside world or shared state.
Examples of shared state:
Global variables
Data stored in files, databases, or APIs
The browser DOM (elements on a web page)
External services (network requests)
Console logs (which affect the program's visible output)
Any variable or object that was created outside the function and then modified inside it
So, whenever a function touches these — by reading, writing, or depending on them — it is
performing a side effect.
61
A function that modifies a shared variable affects other parts of the program in ways that may not be
obvious.
This means that side effects create coupling — parts of the program become dependent on each
other's hidden behaviors.
When a function has side effects, it becomes harder to test, debug, or reason about:
You can't easily predict the result without knowing the full program state.
You can't safely reuse it in different contexts.
Bugs may appear when multiple functions modify the same data in unexpected orders.
That's why in good program design, side effects are not eliminated (since they're needed for
interaction) but isolated — kept at the boundaries of the system.
This approach makes your core logic pure (depending only on inputs and producing outputs) and
pushes unavoidable side effects to specific, controlled areas (like one module handling I/O or DOM
updates).
Imagine a web application that calculates total price and then updates the page:
The calculation part (subtotal + tax) is pure — it always gives the same result for the same inputs.
The DOM update ([Link]("#total").textContent = total) is a side effect — it changes
something visible outside the function.
If you separate these concerns — one function that purely computes, and another that updates the
DOM — you can easily test the computation logic without worrying about browser behavior.
Later, if the display code changes (say, switching to a different UI framework), your pure logic remains
safe and reusable.
Every useful program must eventually perform side effects, because that's how it communicates with
the world (displaying output, saving data, sending requests).
The key is controlling them:
Keep pure logic functions independent and predictable.
Let a small part of the code handle side effects explicitly (for example, one function responsible for
logging or rendering).
This approach keeps your codebase more modular, easier to test, and less prone to bugs that come
from unpredictable changes in shared state.
Examples
62
```js
// pure
function add(a, b) {
return a + b;
}
```
```js
// impure: reads and writes external state
let total = 0;
function addToTotal(x) {
total += x;
return total;
}
```
```js
// impure: mutates input
function pushItem(arr, v) {
[Link](v);
return arr;
}
```
```js
// pure alternative: returns a new array
function append(arr, v) {
return [Link](v);
}
```
Common misconceptions
1. Pure functions cannot use variables. They can use parameters and constants; they just cannot rely
on or mutate external changing state.
2. Logging is harmless. Logging is a side effect.
63
3. A function that happens to return the same value today is pure. Purity requires guarantees for all
time with identical inputs and no external changes.
Practice questions
64
What is immutability and why does it matter?
What is immutability and why does it matter
Immutability means that once a value is created, it is not changed. Instead of modifying data in
place, you create new values that reflect the change. JavaScript primitives (string, number, boolean,
null, undefined, symbol, bigint) are immutable. Objects and arrays are mutable by default, but you
can adopt immutable patterns to avoid accidental shared mutations.
Immutability reduces hidden coupling, makes reasoning and debugging easier, and helps avoid bugs
where two parts of a program unintentionally affect each other through shared references. It also
enables simple change detection strategies and time-travel debugging in state management libraries.
To work immutably with objects and arrays, prefer methods that return new structures such as array
map, filter, slice, spread syntax for objects and arrays, and [Link]. Use structuredClone for
deep copies when needed. [Link] can prevent modification of an object's properties, but it is
shallow and does not freeze nested objects.
Examples
```js
// primitive example
let a = "hi";
let b = a;
b += "!";
[Link](a, b); // "hi", "hi!"
```
```js
// immutable update for object
const user = { name: "Sam", meta: { visits: 1 } };
const updated = {
...user,
meta: { ...[Link], visits: [Link] + 1 },
};
[Link]([Link], [Link]); // 1, 2
```
```js
// immutable array operations
65
const arr = [1, 2, 3];
const doubled = [Link]((x) => x * 2);
const appended = [...arr, 4];
[Link](arr, doubled, appended);
```
Common misconceptions
1. const makes an object immutable. const prevents reassignment of the binding, not mutation of
the object's contents.
2. Immutability is always slower. For typical app-level data, the clarity and safety outweigh minor
copying costs; libraries optimize structural sharing.
3. [Link] or [Link] makes deep immutability. [Link] is shallow; deep
immutability needs recursive freezing or libraries.
Practice questions
In JavaScript, all three - undefined, null, and NaN - represent "absence" in some form.
But they describe different kinds of absence, and mixing them up often causes confusion.
undefined -> "The variable exists, but no one has given it a value yet."
66
NaN -> "I tried to get a number, but the result is nonsense."
undefined is the default state of things that exist but haven't been assigned any value.
You don't have to explicitly write undefined; JavaScript gives it automatically in several cases:
Declared but not initialized variable
Missing function return value
Accessing a non-existent object property
Function parameter not passed during a call
Examples:
```js
let x;
[Link](x); // undefined (declared but no value)
function doSomething() {}
[Link](doSomething()); // undefined (no return statement)
function greet(name) {
[Link]("Hello " + name);
}
greet(); // name is undefined
```
67
null represents a value that's intentionally empty.
Developers assign null themselves to mean, "This should have no value."
```js
Example:
Unlike undefined, which JavaScript assigns automatically, null is assigned by you when you want to
signal "nothing here on purpose."
```js
68
Examples:
Number("abc"); // NaN
parseInt("hello"); // NaN
0 / 0; // NaN
[Link](-1); // NaN
NaN is contagious - once a calculation involves NaN, the whole result becomes NaN.
NaN + 5; // NaN
NaN * 2; // NaN
```
This happens because JavaScript treats NaN as a special "unreliable" value that never equals
anything - even itself.
69
Examples
```js
let x;
[Link](x); // undefined
const obj = {};
[Link]([Link]); // undefined
let y = null;
[Link](y === null); // true
[Link](Number("abc")); // NaN
[Link](NaN === NaN); // false
[Link]([Link](NaN)); // true
```
Common misconceptions
1. undefined and null are interchangeable. undefined implies "not assigned yet"; null is an explicit,
intentional empty value.
2. typeof null === "null". It returns "object" due to a long-standing quirk.
3. NaN compares equal to NaN. NaN never equals anything; use [Link] to test it.
Practice questions
1. When would you deliberately use null instead of leaving a variable undefined?
2. How do you distinguish between a missing property and a property explicitly set to null?
3. Why does NaN === NaN return false and how do you correctly test for NaN?
70
What is strict mode
What is strict mode
Strict mode is an optional mode in JavaScript that makes the language behave in a safer, more
predictable way by turning silent errors into visible ones and by disallowing some problematic
features. You enable it by placing "use strict"; at the top of a script file or at the beginning of a
function body. In ES modules, strict mode is enabled by default.
Strict mode helps catch mistakes early. Assigning to an undeclared variable throws a ReferenceError
instead of creating an accidental global. Certain syntax that often leads to bugs is disallowed. The this
value in plain functions becomes undefined instead of implicitly pointing to the global object, which
prevents hidden global access. Duplicate parameter names are banned, octal escape sequences are
disallowed, attempts to delete plain variable bindings throw, and writes to non-writable properties
throw in strict mode rather than failing silently in some engines.
Examples
```js
"use strict";
x = 10; // ReferenceError: x is not defined
```
```js
function demo() {
"use strict";
y = 5; // ReferenceError
}
```
```js
function show() {
[Link](this);
}
show(); // non-strict: global object; strict: undefined
```
Common misconceptions
1. Strict mode always makes code faster. It mainly improves safety; performance is engine-
dependent.
2. Strict mode is only for modules. Modules are strict by default, but scripts and functions can opt in
with "use strict".
71
3. Strict mode breaks working code for no reason. It surfaces real mistakes like accidental globals and
unsafe patterns.
Practice questions
1. List three runtime differences you get in strict mode versus non-strict.
2. How does strict mode change the default this in plain functions.
3. Why do writes to read-only properties throw in strict mode.
The Temporal Dead Zone (TDZ) is a short period during your program's execution when a variable has
been declared in memory but is not yet ready to use. It exists only for variables declared using let,
const, or class.
When JavaScript starts running a block of code - like inside a function or { } braces - it sets aside
memory space for all variables it finds inside that block. This happens before the actual lines of code
start executing. However, for let, const, and class, JavaScript does not assign them any initial value at
this stage. The variables are marked as "uninitialized." They are known to exist, but you cannot
access them yet.
The time between when the block begins and when JavaScript reaches the actual line where you
declare the variable is called the Temporal Dead Zone. The word "temporal" refers to time, and
"dead zone" means you cannot use that variable during that time. If you try to access it before the
declaration line runs, JavaScript immediately throws a ReferenceError instead of silently giving
undefined.
Once the interpreter reaches the variable's declaration line and runs it, the variable becomes fully
initialized. From that moment onward, you can safely read or write its value.
The TDZ exists to prevent confusing bugs. In older JavaScript (before ES6), variables declared with var
were automatically set to undefined when the program started running. This led to situations where
developers accidentally used variables before they were truly ready, without realizing it. With the
TDZ, JavaScript makes it clear: you cannot use a variable before it's actually declared.
This rule makes code more predictable. It ensures that every variable is used only after it has been
properly defined and initialized, which avoids subtle errors and improves readability.
So, the TDZ is not an error itself - it's a protective mechanism that enforces good timing and
discipline in how you use your variables.
72
Examples
```js
{
// [Link](x); // ReferenceError (TDZ)
let x = 10; // TDZ ends here
[Link](x); // 10
}
```
```js
{
// [Link](y); // ReferenceError
const y = 3;
}
```
Default parameters can also hit TDZ if they reference variables declared later.
```js
let a = 1;
function f(b = a) {
return b;
} // ok
function g(b = c) {
return b;
} // ReferenceError when called
let c = 2;
```
Common misconceptions
1. let and const are not hoisted. They are hoisted but left uninitialized until the declaration runs; the
gap is the TDZ.
2. TDZ is a special exception type. It is behavior that results in ReferenceError.
3. Only blocks have TDZ. Functions and class bodies also create TDZ for their let/const/class bindings.
73
Practice questions
74
What are arrow functions and how are they different
from normal functions?
What are arrow functions and their differences from normal functions
Arrow functions are a compact way to write function expressions introduced in ES6. They change
syntax (shorter) and semantics (important differences from "normal" functions created with the
function keyword). The biggest semantic change is lexical this: an arrow function does not create its
own this; instead, it captures the this from the surrounding (enclosing) scope at the moment the
arrow is created.
- Consequence: call, apply, and bind cannot change an arrow function's this-they still pass
arguments, but the this stays whatever was captured.
- Practical upside: perfect for callbacks where you want to keep using the outer object's this (e.g.,
inside setTimeout, array methods, or promise handlers).
- Practical gotcha: arrows are a poor choice for object methods if you want this to be the receiver
object; use a normal method syntax instead.
- No arguments: if you need a parameter list, use rest parameters ((...args)), which give you a real
array.
- In classes, super used inside an arrow function refers to the super of the containing method-useful,
but subtle.
- [Link] is likewise taken from the outer scope (and arrows themselves cannot be constructors
anyway).
- Parameters
75
- Zero parameters: () => 42
- One parameter (no default/destructuring): x => x \* 2 (parentheses optional)
- Multiple/default/destructured: (x, y = 1) => x + y, ({id}) => id
- Bodies
- Use parentheses: () => ({ a: 1 }) (without them the braces are parsed as a block).
- With concise bodies there's no return keyword; with block bodies you must return explicitly.
- Names
- Arrow functions are syntactically anonymous, but engines infer a name from the assignment
target: const add = (a,b)=>a+b; [Link] is often "add".
ASYNC ARROWS
You can write async arrow functions: const fetchIt = async url => { const r = await fetch(url); return
[Link](); };
They still capture this lexically; await works the same as in async function.
- In methods: setTimeout(() => [Link](), 0) keeps the instance this without .bind(this).
- Short utilities
76
- One-liners (predicates, transforms) become very readable.
- If you write obj = { total: 0, add: () => { [Link]++ } }, this won't be obj; it'll be whatever was outer
when the arrow was defined. Use add() { [Link]++ } instead.
- Arrows will not give you the element as this. Prefer a normal function if you need this bound to
the element.
- Constructors / generator functions / places where you need arguments, [Link], or your own
this
- Arrows simply cannot do those.
- In classes, defining arrow properties like handler = () => { ... } binds this per instance (great for
callbacks) but creates a separate function per instance (slightly more memory) vs sharing a method
on the prototype. Use this pattern when convenient, but be aware of the trade-off.
- [Link](obj, x) will pass x, but the arrow's this won't become obj. If you need to rebind this, use a
normal function.
- You can still use call/apply for arguments only with an arrow; this won't change.
- Arrows behave like strict functions for disallowed patterns (e.g., no duplicate parameter names
when defaults/rest are present).
- They inherit strictness from their environment (modules are strict by default).
77
- "Arrow functions are just shorter syntax."
Shorter, yes-but also different semantics for this, arguments, super, [Link], and constructibility.
1. Do I need my own this (dynamic receiver, event element, prototype method)? -> Use normal
function.
2. Do I want to preserve outer this in a callback or tiny helper? -> Use arrow.
3. Do I need arguments, new, prototype, or yield? -> Use normal function.
4. Am I inside a class and want an instance-bound handler for convenience? -> Arrow property can
be great.
Examples
```js
const double = (x) => x * 2; // implicit return
const add = (a, b) => {
return a + b;
}; // block body
```
```js
const obj = {
n: 0,
incLater() {
setTimeout(() => {
this.n++;
}, 0); // arrow keeps outer this
},
};
78
```
```js
const sum = (...nums) => [Link]((a, b) => a + b, 0);
```
Common misconceptions
1. Arrow functions are just shorter syntax. They also change this and arguments behavior.
2. You can use new with an arrow function. Arrow functions are not constructors.
3. Arrow functions are always better. Prefer normal functions when you need your own this,
arguments, or when defining methods on prototypes.
Practice questions
79
Explain default parameters, rest operator, and spread
operator
Explain default parameters, rest, and spread operators
These three features were added in modern JavaScript (ES6) to make working with function
arguments and arrays easier and more natural. They all look similar because they use three dots (...),
but they serve different purposes depending on where they appear.
When you write a function, sometimes the caller doesn't pass a value for one of the parameters.
Before default parameters existed, that would make the value undefined, and you had to handle it
manually.
Default parameters let you specify a value that will automatically be used if no argument is provided,
or if undefined is passed.
Think of it as giving your function a safety net.
Example idea:
If you have a function that greets a user, and no name is given, you can tell JavaScript to use "Guest"
by default.
So instead of checking manually if the value exists, the function always works - it greets either the
given name or the default one.
This makes functions more reliable and self-explanatory.
In plain English:
If you say, "give me the rest of them," that's what this feature does - it takes everything that remains
and packs it together so you can use it easily inside the function.
80
This is very useful when building flexible functions - for example, when summing a list of numbers,
processing all given inputs, or combining variable-length data.
The spread operator also uses three dots (...), but instead of collecting values, it does the opposite -
it unpacks them.
If you have an array (a list of values) and want to pass each value separately, the spread operator lets
you do that easily.
You can think of it as pouring out the contents of a container.
For example:
If you have a list of numbers and want to pass them into a function that expects individual numbers,
the spread operator breaks that array into separate pieces so the function can see them individually.
"Here, take each of these items, not the box they're in."
The same idea works for objects: the spread operator can copy all key-value pairs from one object
into another, which makes merging or cloning objects very simple.
Even though rest and spread both use three dots, they do opposite things:
- Rest collects multiple values into one bundle (when used in a function definition).
- Spread unpacks one bundle into multiple values (when used in a function call or an array/object
literal).
Before these were added to JavaScript, you had to write extra code to:
81
- Merge arrays or objects with clumsy methods.
Now, with default, rest, and spread, your code becomes cleaner, more expressive, and easier to
understand.
They help you write functions that are more flexible and less error-prone, especially when working
with variable data.
Default parameters
```js
function greet(name = "there") {
return "Hi " + name;
}
greet(); // "Hi there"
greet("Sam"); // "Hi Sam"
```
Rest parameters
```js
function sum(...nums) {
return [Link]((a, b) => a + b, 0);
}
```
```js
const a = [1, 2];
const b = [3, 4];
const combined = [...a, ...b]; // [1,2,3,4]
[Link]([..."hi"]); // ['h','i']
```
```js
const user = { name: "A", meta: { v: 1 } };
82
const copy = { ...user, role: "admin" }; // shallow copy
```
Common misconceptions
1. Default expressions are evaluated at function definition time. They run at call time only if the
argument is undefined.
2. Rest gives an arguments-like object. Rest gives a true array; arguments is array-like and not in
arrow functions.
3. Object spread does a deep clone. It copies only one level; nested objects remain shared.
Practice questions
83
What are template literals?
What are template literals
Template literals are strings written with backticks that support embedded expressions, multi-line
text, and tagged processing. They simplify string building compared to concatenation and preserve
line breaks as written.
Embedded expressions use ${ ... } to inject values. The expression can be any JavaScript expression.
Tagged templates call a function with the literal's parts before construction, enabling custom
escaping, i18n, or formatting logic.
Examples
```js
const name = "Sam",
score = 42;
[Link](`Hello ${name}, score: ${score}`);
```
```js
const multi = `Line 1
Line 2`;
```
```js
function join(strings, ...vals) {
return [Link]((s, i) => s + (vals[i] ?? "")).join("");
}
const user = "<admin>";
[Link](join`Hello ${user}`);
```
Common misconceptions
1. Template literals auto-sanitize output. They do not; security depends on your tag function or
escaping.
2. Only variables can be inside ${}. Any expression can.
84
3. Backticks are slower. They compile to efficient string operations; performance differences are
negligible in normal code.
Practice questions
85
Difference between for, for-in, for-of, and forEach
Difference between for, for-in, for-of, and forEach
- Best when you need full control over the index and step (e.g., count by 2s, go backwards, stop early
based on a condition).
- Works with anything that has a numeric length and index access (like arrays and strings).
- Works fine with await inside an async function (each iteration can await).
Example
```js
[Link](i, arr[i]);
```
- Not recommended for arrays (order can be surprising; it visits non-index properties too).
- Works fine with await inside an async function (each iteration can await).
86
```js
if ([Link](user, key)) {
[Link](key, user[key]);
```
- Iterates the values produced by any iterable: arrays, strings, Maps, Sets, typed arrays, generator
results, etc.
- Ideal for arrays when you want the values directly (no index math).
- Works well with await inside an async function (each loop can await before moving to the next
item).
Examples
```js
// Array values
[Link](value);
// String characters
[Link](ch);
87
// Map entries (each is [key, value])
["x", 1],
["y", 2],
]);
[Link](k, v);
```
- You cannot use break or continue to stop early; it always runs to the end.
- Returning from inside the callback only returns from the callback, not the outer function.
- About async/await: if you put await inside the forEach callback, the loop does not "pause" between
items. All callbacks are scheduled and your awaits run inside them, but the outer code keeps going. If
you need to process items one-by-one in order, prefer for...of or for.
Example
```js
[Link](index, value);
});
```
- "Await-aware" just means: if you use await inside the loop, the loop actually waits for the
asynchronous work to finish before moving to the next item.
88
- classic for and for...of: yes, they wait (when used inside an async function).
- forEach: no, it does not wait between items; it fires the callbacks and moves on.
- for...in: behaves like for (you can await inside and it will wait in an async function).
```js
```
If you tried the same with forEach, the outer flow wouldn't wait for each `doAsyncWork` to finish
before starting the next one.
- Use for when you need index control, custom step sizes, or want to break/continue at specific
counts.
- Use for...in for enumerating object keys (and guard with hasOwnProperty). Avoid it for arrays.
- Use for...of for clean iteration over values (arrays, strings, Maps, Sets, etc.). It's the go-to loop for
arrays when you don't need the index.
- Use forEach for quick, no-break, no-await list processing where order/pausing doesn't matter and
readability is your priority.
- Arrays: for and for...of preserve natural order; forEach also follows array order; for...in can produce
unexpected key order and include non-index keys—avoid it for arrays.
89
- Objects: plain objects are not iterable, so for...of will not work directly; use for...in (with
hasOwnProperty) or [Link]/[Link]/[Link] with for...of.
- Maps/Sets: for...of gives items in insertion order (with Map entries as [key, value]).
- Strings: for...of yields characters (including correct handling for many Unicode cases).
It can include non-index keys and odd ordering. Prefer for, for...of, or forEach for arrays.
You can't break or continue from forEach. If you need to stop early, use for or for...of.
forEach doesn't pause between items. If you need to wait per item, switch to for...of (inside an
async function).
Not by default; plain objects aren't iterable. Use for...in (with hasOwnProperty) or:
```js
/* ... */
```
Use entries:
```js
/* ... */
90
}
```
```js
const obj = { a: 1, b: 2 };
[Link](k, v);
```
```js
[Link](i, v);
```
```js
await doAsyncWork(item);
91
```
Early exit
```js
```
Common misconceptions
1. for...in and for...of are interchangeable. They are not: for...in gives property names (keys); for...of
gives iterable values.
2. forEach is the same as a loop. It cannot break/continue, and it doesn't pause with await.
3. Plain objects work with for...of. They don't; use [Link]/values/entries or for...in (with a
hasOwnProperty check).
4. You can always replace for with forEach. If you need index math, early exit, or await per item,
prefer for or for...of.
Practice questions
1. You have to process a list of tasks one by one, waiting for each network call to finish before
starting the next. Which loop do you choose and why?
2. Show how to iterate key/value pairs of a plain object in insertion-like order without using for...in.
3. Explain why `break` does not work inside forEach and provide an alternative that supports early
exit.
92
What are objects and how are they stored in memory?
What are objects and how are they stored in memory
An object is a container of key-value pairs. Keys are strings or symbols; values can be anything
(numbers, strings, arrays, functions, other objects). Objects are dynamic: you can add, change, or
remove properties at runtime.
Primitives (number, string, boolean, bigint, symbol, null, undefined) are stored directly as values.
Objects live on the heap; variables hold a reference (a pointer) to them. If two variables point to the
same object, changing it through one is visible through the other. When an object becomes
unreachable from any live reference (including from other reachable objects), the garbage collector
reclaims it. Cycles don't prevent collection as long as the whole cycle becomes unreachable.
```js
```
```js
const user = {
age,
greet() {
// method shorthand
93
return `Hi, I'm ${[Link]}`;
},
};
```
```js
```
```js
function Person(name) {
[Link] = name;
[Link] = function () {
};
```
Properties set inside the constructor are per-instance; methods placed on the prototype are shared
by all instances (memory-efficient).
```js
class Person {
94
constructor(name) {
[Link] = name;
sayHi() {
static species() {
```
Under the hood, classes still use prototypes. Instance methods are on `[Link]`. Static
methods are on `Person` itself.
```js
child.x = 1;
```
Great for building objects with a chosen prototype without invoking constructors.
```js
const entries = [
95
["a", 1],
["b", 2],
];
```
Each object has an internal link to a prototype (another object or null). When you access `[Link]`,
the engine:
No copying happens; lookup is dynamic. This is why methods placed on `[Link]` are
shared across instances.
Property names can be strings or symbols. String keys that look like integers (e.g., "0", "1") may get
special ordering during enumeration, but for most cases assume insertion order for own string keys,
with symbol keys not included in typical enumeration.
```js
// keys only
[Link](k, user[k]);
96
}
// values only
[Link](v);
[Link](k, v);
```
`for...in` walks enumerable keys including inherited ones; guard with `hasOwnProperty` when
needed.
Every property has attributes: `value`, `writable`, `enumerable`, `configurable` (for data properties)
or `get`/`set` (for accessor properties).
```js
[Link](user, "id", {
value: 123,
});
```
97
```js
const meter = {
_value: 0,
get value() {
return this._value;
},
set value(v) {
if (v >= 0) this._value = v;
},
};
```
```js
const ID = Symbol("id");
const o = { [ID]: 99 };
```
98
Introspection
- Deep copy: `structuredClone(obj)` (modern) deep-clones many structured values; falls back to
libraries for older environments or special cases.
- `[Link](a, b)` like `===` but treats `NaN` equal to `NaN` and distinguishes `+0` vs `-0`.
Converting to data
99
- `[Link](str)` back to an object.
Objects can have `toString`, `valueOf`, or `[Link]` to control how they convert to strings
or numbers.
```js
const money = {
amount: 1000,
[[Link]](hint) {
},
};
String(money); // "$1000"
+money; // 1000
```
Instance methods exist on each object, prototype methods are shared. In classes:
```js
class Counter {
inc() {
[Link]++;
```
Arrow methods as fields (`handler = () => {}`) capture `this` per instance (great for callbacks), but
consume more memory than a single shared prototype method.
100
Objects vs Map/Set
Use a plain object when keys are known strings/symbols and you want prototype features, JSON, and
simple literals. Use `Map` when keys can be anything (including objects), when you need guaranteed
insertion order + efficient size, and methods like `[Link]`, `[Link]`, `[Link]`. Use `Set` for unique
value collections. WeakMap/WeakSet hold weak references to object keys/values and don't prevent
garbage collection (useful for caches without leaks).
Shallow copies copy only one level; nested objects remain shared.
```js
```
If you want immutable patterns, avoid mutating original objects; create new copies with changed
fields:
```js
const updated = {
...user,
};
```
101
Performance notes (pragmatic)
Engines optimize objects using hidden classes/shapes; consistent property creation order and shape
(define all fields in the constructor, avoid adding/removing fields later) can help performance.
`[Link]` and changing shapes at runtime can deoptimize; prefer stable layouts.
Objects stay alive as long as something reachable references them. Accidental retention (e.g., a
global cache never cleared, long-lived event listeners, closures holding large data) can cause leaks.
Use:
```js
function createUser(name) {
return {
name,
greet() {
},
};
```
102
Class
```js
class User {
constructor(name) {
[Link] = name;
greet() {
```
Prototype + [Link]
```js
const proto = {
greet() {
},
};
const u = [Link](proto);
[Link] = "Ava";
```
```js
[Link](obj, "id", {
value: 1,
103
writable: false,
enumerable: false,
});
```
Common misconceptions
1. Assigning an object to another variable copies it. It copies the reference; both variables point to
the same object.
2. Prototype properties are copied into the object. They are not; they're looked up dynamically
through the chain.
3. `[Link]` makes everything inside immutable. It's shallow; nested objects are still mutable
unless you freeze them too.
4. `for...of` works on plain objects. Plain objects are not iterable; use `[Link]/values/entries` or
`for...in` with `hasOwnProperty`.
5. JSON is a reliable clone for all objects. It drops functions/symbols and fails on cycles or special
types; prefer `structuredClone` when available.
Practice questions
1. Show four different ways to create an object that has a `greet()` method, and explain which
method is shared vs per-instance in each case.
2. Explain what happens in memory when you execute `const a = { x: 1 }; const b = a; b.x = 2;`.
3. How would you create a non-enumerable, read-only property `id` on an object? How would you
verify its descriptor?
4. What's the difference between `{ ...obj }`, `[Link]({}, obj)`, and `structuredClone(obj)`?
5. When would you choose a `Map` over a plain object? Give two concrete reasons.
104
Explain pass-by-value vs pass-by-reference
Explain pass-by-value vs pass-by-reference
When you pass something to a function in JavaScript, the language always passes it **by value** —
but _what that value represents_ depends on whether the thing you're passing is a **primitive** or
an **object**.
To understand the difference, let's first see what each category really is.
Instead, the variable stores a **reference** — a kind of "address" or pointer to where the real data
lives in memory (on the heap).
So, in JavaScript:
When you pass a **primitive** to a function, JavaScript copies its actual value into the parameter.
That means changes inside the function don't affect the original variable, because the function is
working on its own copy.
Think of it like photocopying a document — the function gets the copy, not the original.
105
So when we say "pass-by-value," it literally means:
Now, when you pass an **object**, what gets copied is **not the whole object**, but the
**reference** (the address pointing to it).
That means both the original variable and the function parameter now point to the **same object**
in memory.
If the function changes something _inside_ that object, both will see that change — because they're
both looking at the same underlying data.
Even though this behavior _looks like_ pass-by-reference, JavaScript is technically still **passing by
value** — the value just happens to be a _reference_.
In other words, the function gets a _copy of the reference_, not the object itself.
But because the reference points to the same memory location, changing the contents inside that
object affects both.
However, if you make the parameter point to a new object inside the function, that new reference
doesn't affect the original one.
- An object variable holds an **address tag** pointing to where the object lives.
106
- For a primitive: the value (like `10` or `"hi"`) is copied.
- For an object: the reference (the tag pointing to the heap) is copied.
Now both the original and the parameter hold tags that point to the same place.
This explains why modifying the object's inside properties affects the outside variable too — both are
looking at the same location.
---
1. **Immutable vs mutable:**
- Primitives are immutable (you can replace them, but not modify them directly).
2. **Function design:**
- When you pass a primitive, you can be sure the original won't be affected.
- When you pass an object, if you don't want to mutate the original, you should clone or copy it
first.
→ No — the reference itself was passed _by value_, but both refer to the same object.
- "If I reassign the parameter inside the function, shouldn't it change outside too?"
→ No — that's a new reference; the outer one still points to the old object.
---
107
### 6. How memory fits into this
In memory:
- Variables hold stack entries that either contain a direct value (for primitives) or a pointer to a heap
object (for objects).
- New local variables and parameters are placed on its stack frame.
- When the function exits, those local stack variables are discarded.
- But the heap objects they pointed to may continue to exist if something outside the function still
references them.
That's why objects persist beyond function calls — they're not copied around; only their references
are.
---
- So modifications to object properties affect the same object, but reassignment inside the function
doesn't affect the original variable.
Or in simpler terms:
108
> but for objects, that value is the **key to a shared box**, not the box itself.
Examples
```js
function inc(n) {
n = n + 1;
let a = 5;
inc(a);
[Link](a); // 5
```
```js
function setName(obj) {
[Link] = "Alex";
setName(user);
[Link]([Link]); // "Alex"
```
```js
function replace(obj) {
replace(user);
```
Common misconceptions
109
1. JavaScript is pass-by-reference. The reference is passed by value.
2. Reassigning a parameter changes the caller's variable. Only mutations to the object's contents are
observed by the caller.
3. Passing objects is unsafe. Avoid mutation or copy when needed; passing references itself is fine.
Practice questions
2. Show how to avoid accidental mutation when passing objects into functions.
3. Implement a deep clone and explain when you would use it.
110
What is object destructuring and array destructuring?
What is object destructuring and array destructuring
Destructuring lets you extract values from arrays and properties from objects into variables using a
compact pattern. Arrays destructure by position; objects destructure by property name. You can
rename, set defaults, skip elements, and destructure nested structures.
Examples
```js
```
```js
```
```js
```
```js
```
111
Common misconceptions
Practice questions
3. Write a function that destructures options with defaults in its parameter list.
112
Explain [Link], [Link], and
[Link]
Explain [Link], [Link], and [Link]
[Link] stops adding new properties to an object. Existing properties can still be
changed or deleted depending on their descriptors. [Link] checks whether new
properties can be added.
[Link] prevents adding or deleting properties and marks all existing properties as non-
configurable. Values of writable properties can still be changed. [Link] checks the sealed
state.
[Link] prevents adding, deleting, or reconfiguring properties and makes all existing properties
non-writable and non-configurable. Values cannot be changed. [Link] checks the frozen
state. All three methods are shallow: nested objects are unaffected unless also processed.
Examples
```js
const a = { x: 1 };
[Link](a);
```
```js
const b = { x: 1 };
[Link](b);
b.x = 2; // ok if writable
```
```js
113
const c = { x: 1 };
[Link](c);
```
Common misconceptions
2. seal prevents value changes. It prevents deletion and reconfiguration but not writing (if writable).
3. preventExtensions is equivalent to seal. It only blocks adding new properties; deletion and
configuration still depend on descriptors.
Practice questions
2. Write a deepFreeze utility and explain when you would use it.
3. What happens when you assign to a frozen property in strict vs non-strict mode?
114
What is prototypal inheritance?
What is prototypal inheritance
Prototypal inheritance is the way JavaScript lets objects share behavior without copying it. Every
object can have an internal link to another object called its prototype. When you read a property
from an object and the engine doesn't find an "own" property, it automatically walks this prototype
link to look for the property on the prototype, then that prototype's prototype, and so on until it
reaches null. This chain is consulted at read time for every lookup; nothing is copied automatically.
Because of this, a single shared method defined on one prototype can serve many instances at once,
saving memory and keeping behavior consistent. If you later change the shared method on the
prototype, all instances immediately observe the change on their next property read (unless they
have their own property with the same name, which "shadows" the prototype's property).
In day-to-day code, you usually encounter prototypal inheritance through constructor functions and
classes. A constructor's prototype object holds the shared methods; instances created with new link
to that prototype. With class, the instance methods you write are placed on the class's prototype
under the hood, and instances created with new Class() link to it. You can also build prototype chains
directly with [Link](parent), which makes an object whose prototype is parent without
involving constructors. This flexibility allows both classical, class-like hierarchies and ad-hoc
delegation where objects share only what they need.
Understanding prototypal inheritance clarifies several behaviors: method dispatch uses the receiver
object as this even if the method was found higher in the chain; assignment creates or updates own
properties by default (it does not climb the chain to overwrite a prototype property); and property
enumeration can include or exclude inherited properties depending on the mechanism you use. The
model is dynamic and late-bound, so you should avoid mutating prototypes at runtime in
widely-shared libraries to reduce surprises; prefer establishing shapes once and treating prototypes
as stable interfaces.
Examples
```js
function Person(name) {
[Link] = name;
[Link] = function () {
115
};
```
```js
const mover = {
move() {
},
};
[Link] = "R2";
```
```js
```
Common misconceptions
1. Prototypal inheritance copies methods into children. Nothing is copied; reads consult the chain on
demand.
116
2. Methods run with this bound to the prototype. this is the receiver object (the thing before the
dot), not the prototype where the method lives.
3. Assigning obj.x updates the prototype's x. Assignment creates/updates an own property unless an
accessor or descriptor intercepts it.
4. Classes remove prototypes. class is syntax over prototypes; the mechanism is unchanged.
Practice questions
1. Theory: In your own words, describe how a property read travels along the prototype chain and
how shadowing works.
2. Coding: Implement a Shape constructor with an area method on [Link]. Make Rectangle
inherit from Shape's prototype and override area on [Link]. Show both in action.
3. Coding: Create a base object with [Link] and prove that adding a method to the base later
becomes visible on already-created children.
117
How does the prototype chain work?
How does the prototype chain work
The prototype chain is the ordered sequence of objects the engine searches to resolve a property
read. When you evaluate [Link], the engine checks for an own property prop on obj. If it does not
exist, it follows obj's internal [[Prototype]] link to another object (often the constructor's prototype)
and repeats the check. This continues until the property is found or the end of the chain (null) is
reached. If nothing defines prop, the result is undefined. Because lookup is dynamic, changing any
object along the chain affects future reads; because own properties take precedence, adding an own
property with the same name "shadows" the one found higher in the chain.
Method calls use the same lookup. When you call [Link](), the engine finds method using the
chain, but during the call it binds this to obj (the receiver). That allows a single shared function living
on a prototype to behave as if it belonged to each instance. Writes behave differently: a plain
assignment [Link] = v creates or updates an own property on obj; it does not climb the chain to
modify a prototype property. Accessors (get/set) and property descriptors can change this behavior
by intercepting reads/writes. Enumeration tools differ too: for...in traverses enumerable keys
including inherited ones; [Link] shows only enumerable own string keys;
[Link] includes non-enumerable own string keys; [Link] includes
all own keys (strings and symbols).
The global chain for typical objects ends at [Link], which provides methods like toString
and hasOwnProperty. Arrays and functions have their own prototype objects that themselves link to
[Link]. Null-prototype objects ([Link](null)) deliberately have no inherited
properties, which is useful for "dictionary" maps without prototype interference. Understanding the
chain helps you reason about performance (engines optimize common shapes), avoids accidental
leaks of shared mutable state on prototypes, and clarifies why late changes to a prototype surface
everywhere.
Examples
```js
const A = {
tag: "A",
greet() {
},
118
};
const B = [Link](A);
const C = [Link](B);
[Link] = function () {
};
```
```js
const base = { x: 1 };
child.x = 2;
[Link](child.x, base.x); // 2, 1
```
```js
const P = {
_v: 0,
get v() {
return this._v;
},
set v(n) {
this._v = n < 0 ? 0 : n;
},
};
const o = [Link](P);
119
o.v; // 0
```
Common misconceptions
1. If a property exists on the prototype, assignment updates that one. Default assignment
creates/updates an own property instead.
2. Methods run with this equal to the prototype. this is the receiver.
3. Changing a prototype only affects future instances. Reads always consult the current prototype, so
existing instances observe changes immediately (unless shadowed).
4. for...in shows only own properties. It walks inherited enumerable keys too.
Practice questions
1. Theory: Explain why [Link] = value does not update [Link] and how accessors can alter
this behavior.
2. Coding: Build A -> B -> C where A supplies a method. Prove that shadowing on C hides A's method,
then delete from C and show the lookup falls back to A again.
3. Coding: Use a prototype setter to validate assignments on child instances; show that the setter's
this refers to the instance.
120
What are constructor functions?
What are constructor functions
Constructor functions are the pre-class way to initialize similar objects that share behavior. A
constructor is just a normal function intended to be invoked with new. The new operator performs
four coordinated steps: (1) it allocates a fresh empty object, (2) it sets that object's internal
[[Prototype]] to the constructor's prototype property, (3) it binds this inside the constructor to that
fresh object so you can assign per-instance fields, and (4) it returns the new object automatically
unless you explicitly return a non-primitive value. By placing methods on
[Link] instead of inside the constructor, all instances share one function, which
is memory-efficient and keeps behavior consistent.
Constructor functions compose into hierarchies by linking prototypes. To emulate "subclassing," you
create the child's prototype from the parent's prototype ([Link]) and call the parent
constructor within the child constructor to initialize shared fields. You also repair the child's
[Link] property if you care about reflection. While ES6 class offers cleaner syntax,
understanding constructor functions explains what class does under the hood and clarifies why
instances see prototype methods, how this is bound during construction, and why returning an
object from a constructor changes the returned value regardless of prototype setup.
Examples
```js
function Person(name) {
[Link] = name;
[Link] = function () {
};
b = new Person("Ben");
[Link]();
[Link]();
```
121
```js
function Parent(x) {
this.x = x;
function Child(x, y) {
this.y = y;
[Link] = [Link]([Link]);
[Link] = Child;
```
```js
function Odd() {
[Link] = "Odd";
```
Common misconceptions
1. new copies methods into instances. It links instances to the prototype; methods remain on the
prototype.
2. Returning an object from a constructor is harmless. It replaces the instance entirely, possibly
breaking the intended prototype link.
3. The constructor property is always meaningful. After replacing a prototype, restore constructor
manually if you rely on it.
Practice questions
122
1. Theory: List the four steps performed by new and explain why placing methods on the prototype
saves memory.
2. Coding: Implement a Point(x, y) with a distance method on [Link]. Create two points and
verify the method is shared.
3. Coding: Implement Parent/Child inheritance using [Link] + [Link], and verify Child
instances access Parent's prototype methods.
123
Explain class syntax in ES6 and how it’s sugar over
prototypes
Explain class syntax in ES6 and how it's sugar over prototypes
Classes in JavaScript are a more readable way to write what the language has always done with
functions and prototypes. They do not introduce a new inheritance system like in Java or C++; they
just make the old prototype-based pattern easier to write and understand. When you define a class,
JavaScript actually creates a special kind of function under the hood - the constructor. Every method
you write inside the class body is automatically added to that class's prototype, not copied into each
object. So when you create an object from a class using the new keyword, that object gets linked to
the class's prototype, just like it would if you used a constructor function. That's why calling a
method on one instance doesn't duplicate the code - all instances share the same method from the
prototype.
When you extend one class from another using extends, JavaScript connects the new class's
prototype to the parent class's prototype. This means if a method isn't found on the child, it looks
upward in the chain to find it on the parent. Inside the child's constructor, you can call super() to run
the parent's constructor, ensuring the base setup happens before adding the child's specific logic.
You can also call [Link]() to reuse parent methods from inside overrides.
Classes also provide a few new conveniences that make object-oriented programming easier. For
example, you can define fields (variables) directly inside the class body instead of only inside the
constructor. Fields declared with the # prefix are private, meaning they can only be accessed inside
that class. This gives real data privacy - something older JavaScript patterns couldn't enforce. There
are also static methods and fields, which belong to the class itself instead of instances. Static
methods are used for utility operations or factory functions related to the class, while static fields can
store constants or configuration shared across all instances.
Despite these modern features, classes in JavaScript are still built on the same prototype mechanism.
Each class creates a constructor function and a prototype object that stores shared methods.
Instances link to that prototype, and method calls use that link to find and execute the right code.
Nothing is copied; the language simply hides the prototype wiring behind a cleaner syntax. That's
why classes are often said to be "syntactic sugar" - they sweeten the old prototype system but don't
change it. Understanding this helps you realize that features like inheritance, method overriding, and
shared behavior in JavaScript are all just prototype relationships under the hood.
Examples
```js
124
class Shape {
area() {
return 0;
constructor(w, h) {
super();
this.w = w;
this.h = h;
area() {
} // override
static kind() {
return "rect";
[Link](); // 12
[Link](); // "rect"
```
```js
class Counter {
#count = 0; // private
inc() {
this.#count++;
value() {
return this.#count;
125
}
[Link]();
[Link](); // 1
```
```js
// Field initializers
class Task {
run() {
[Link] = "done";
```
Common misconceptions
1. class changes inheritance to classical copying. It still uses prototypes and delegation.
2. Methods are per-instance with class. Methods live on the prototype; fields are per-instance.
3. Private fields are just naming tricks. They are enforced; accessing #private outside the class is a
syntax error.
Practice questions
1. Theory: Explain where instance methods and static methods are stored at runtime and how
extends links prototypes.
2. Coding: Implement a Base class with a greet method, extend it with Admin that overrides greet
and calls [Link]().
3. Coding: Create a class with a #private counter and public inc/value methods; prove that external
code cannot access #counter.
126
What is [Link] used for?
What is [Link] used for
[Link](proto, descriptors?) constructs a new object whose internal [[Prototype]] is proto and
whose own properties can be defined via an optional descriptors map. This is the most direct way to
express "make an object that delegates to this other object" without invoking constructors. Because
the prototype is set at creation time, you avoid the performance and complexity costs of altering the
prototype later. Passing null creates a "dictionary" object with no inherited properties (no toString,
no hasOwnProperty), which is ideal when you want a clean key space for arbitrary keys.
The descriptors parameter lets you define properties with precise control over writability,
enumerability, configurability, and getters/setters in one call. Using [Link] makes delegation
explicit and encourages composition: you can assemble objects that share only what they need by
choosing suitable prototypes. It is also convenient for building test doubles or simple hierarchies,
where you can add methods to the parent after children exist and they will see the new methods on
subsequent reads.
Examples
```js
const base = {
greet() {
return "hi";
},
};
});
[Link](); // "hi"
```
```js
// Null-prototype "dictionary"
127
dict["__proto__"] = "ok"; // safe: not an inherited key
```
```js
const a = [Link](base);
[Link] = "A";
[Link] = function () {
return [Link];
};
[Link](); // "A"
```
Common misconceptions
2. You need constructors to set up inheritance. [Link] directly sets the prototype relationship.
3. Null-prototype objects behave like normal objects. They lack [Link] utilities; use
[Link] and Reflect helpers.
Practice questions
2. Coding: Create a null-prototype dictionary and demonstrate safe key storage and lookup without
collisions.
3. Coding: Build a base -> child chain with [Link], add a method to the base afterwards, and
show the child can call it.
128
Explain call, apply, bind — and differences between
them
Explain call, apply, bind and differences between them
In JavaScript, functions are special objects that can be called in many different ways. By default,
when you call a regular function, the value of `this` inside it depends on _how_ you call it — not
where it was written. Sometimes you need to control what `this` points to, or you might need to pass
arguments flexibly from another source. That's where **call**, **apply**, and **bind** come in.
These three methods belong to every function in JavaScript, and they let you explicitly decide what
`this` should refer to when the function runs.
The **call** method runs a function immediately, but allows you to specify what `this` should be
and pass arguments one by one. For example, `[Link](user, "Hi")` calls `sayHello` right away
with `this` bound to `user`. It's just like calling `[Link]("Hi")`, but gives you manual control
over the context. The **apply** method works almost the same way — it also invokes the function
immediately — but instead of taking arguments individually, it expects them as a single array (or
array-like structure). For instance, `[Link](user, ["Hi"])` does the same as `[Link](user,
"Hi")`. The main difference is only in how you pass the arguments. This was particularly useful before
the spread operator (`...`) was introduced, since you could easily forward arrays of arguments to a
function.
The **bind** method is slightly different. It doesn't call the function right away. Instead, it creates
and returns a _new function_ that remembers the `this` value you provided and optionally some of
the arguments you passed. When you later call that returned function, it will automatically use the
bound `this` and pre-filled arguments. For example, if you write `const greetJohn = [Link](user,
"John")`, you now have a new function that always greets using that same `user` context and name,
no matter how or where you call it. This is extremely useful when you need to pass functions as
callbacks — like event listeners or setTimeout handlers — and want them to remember which object
they belong to.
```js
function say(greeting) {
129
// call - runs immediately
```
It's also worth noting how **arrow functions** differ here. Arrow functions do not have their own
`this` — they inherit it from the surrounding scope where they were defined. Because of that, using
call, apply, or bind on arrow functions has no effect on `this`; they simply ignore those changes.
That's one of the main differences between arrow functions and regular ones when it comes to
controlling context.
Finally, there's a special rule when combining **bind** and **new**. If you create a bound function
and later use it as a constructor (with the `new` keyword), the newly created object becomes `this`,
overriding the bound value. This ensures that bound functions still behave correctly when used to
create objects. So, binding only fixes `this` for normal function calls, not for object construction.
In short:
- **call** -> runs the function immediately, takes `this` and individual arguments.
- **bind** -> returns a new function that remembers its `this` and optional pre-filled arguments for
later.
130
These tools make JavaScript more flexible by letting you borrow functions, stabilize the meaning of
`this`, and reuse logic in different contexts without rewriting it.
Examples
```js
function show(prefix) {
```
```js
function firstArg() {
return arguments[0];
```
```js
function add(a, b, c) {
return a + b + c;
add5(10, 20); // 35
131
```
Common misconceptions
1. bind mutates the original function. It returns a new function; the original remains unchanged.
2. You can rebind an arrow's this with call/apply/bind. Arrow functions' this is fixed lexically.
3. bind prevents new from changing this. Constructing a bound function with new binds this to the
newly created instance.
Practice questions
1. Theory: In one paragraph, contrast call, apply, and bind and describe when each is most
convenient.
2. Coding: Write a logger that prints [Link]; demonstrate changing receivers with call/apply and
permanently with bind.
3. Coding: Create a multiply(a, b) function and a double(x) by partially applying multiply with bind.
132
What are higher-order functions?
What are higher-order functions
A higher-order function (HOF) is a function that takes one or more functions as inputs, returns a
function as output, or both. This idea lets you separate "what to do" (the callback) from "how to do
it" (the control flow). In practice, [Link], filter, and reduce are classic HOFs: you
provide small functions that describe how to transform, select, or combine items, and the HOF
handles iteration, indexing, and collection building. HOFs are central to functional programming
patterns, enabling composition, reuse, and testable logic where side effects are limited and easy to
locate.
Beyond arrays, HOFs underpin event systems (registering handlers), async flows (then handlers,
executor functions), and middleware/decorators (wrapping behavior to add cross-cutting concerns
like logging, caching, and retry). Two related techniques often used with HOFs are partial application
(pre-filling some arguments) and currying (turning a multi-argument function into a chain of
one-argument functions). Used thoughtfully, HOFs reduce boilerplate and make data pipelines clear;
used excessively, they can obscure simple logic behind many tiny layers. The balance is to
encapsulate common patterns while keeping core steps explicit.
Examples
```js
```
```js
function once(fn) {
value;
133
if (!called) {
called = true;
return value;
};
```
```js
function curry2(fn) {
add2(3)(4); // 7
```
Common misconceptions
1. HOFs are only for arrays. They appear anywhere you pass or return functions (events, promises,
middleware).
2. Currying and partial application are identical. Currying makes N unary functions; partial application
fixes some arguments in place.
3. HOFs are always slower. In real apps, clarity and maintainability outweigh micro-overheads;
measure before optimizing.
Practice questions
1. Theory: Explain why separating iteration mechanics (map/filter/reduce) from per-item logic
improves testability.
134
2. Coding: Implement a compose(...fns) utility that composes functions right-to-left; test with simple
math functions.
3. Coding: Implement a memoize(fn) that caches results based on arguments for pure functions.
135
What is callback hell and how to avoid it?
What is callback hell and how to avoid it
Callback hell is the tangled control flow that emerges when you nest many asynchronous callbacks
inside one another. Each step depends on the previous step's result, so you place the next callback
inside the prior one's success handler, creating a pyramid shape. This structure makes code hard to
read, error paths easy to forget, and sequencing brittle (e.g., double invocations or missed errors).
The problem is not callbacks themselves; it's unstructured composition that interleaves business
logic with control flow and error handling.
The escape is to use composable abstractions. Promises represent future values and let you chain
steps with then and catch in a flat structure; errors propagate by default, so you don't have to thread
error callbacks manually. async/await builds on promises and lets you write sequential async steps
top-to-bottom with try/catch for errors while still returning promises. Additional techniques include
extracting named functions instead of inline anonymous ones, using [Link] to run independent
tasks in parallel, wrapping callback-style APIs with Promise constructors (promisify), and centralizing
error handling so you have one place to log and recover.
Examples
```js
fetch(url1)
```
```js
try {
136
const r1 = await fetch(url1);
[Link](d2);
} catch (e) {
[Link](e);
```
```js
function delay(ms) {
```
Common misconceptions
2. Promises are always sequential. [Link], allSettled, and any provide parallel coordination.
3. Callbacks are obsolete. They are still used in many APIs; promisify or wrap them for composition.
Practice questions
1. Theory: Describe three concrete problems with deeply nested callbacks and how promises
mitigate each.
2. Coding: Convert a "pyramid" of setTimeout calls into a promise chain with a delay(ms) helper.
3. Coding: Rewrite a two-step dependent async flow using async/await with proper try/catch and a
finally clean-up.
137
Explain promises and how they work
Explain promises and how they work
In JavaScript, a Promise is a special object that represents the result of an operation that hasn't
finished yet but will finish in the future — either successfully or with an error. It acts as a placeholder
for a value that will be available later. Think of it as a box that starts empty but is guaranteed to
eventually contain either a result or a reason why it failed. This idea lets JavaScript handle long-
running tasks (like fetching data from a server) without freezing the browser or blocking other code
from running.
1. Pending — it's still working on the task and hasn't produced a result yet.
Once a promise moves from pending to either fulfilled or rejected, it stays settled — it never changes
again. You can attach handlers to a promise to know when that happens. The `then()` method lets
you specify what should happen when the promise fulfills, and `catch()` specifies what to do if it
rejects. The `finally()` method runs no matter what, useful for cleanup (like hiding a loading spinner).
Under the hood, JavaScript schedules these `then` and `catch` callbacks to run as microtasks,
meaning they execute right after the current synchronous code finishes but before other queued
tasks like timers. This guarantees consistent ordering: your promise handlers always run after the
code that created them, even if the promise resolves instantly.
```js
});
promise
138
.then((result) => [Link](result)) // runs after 1s: "Data loaded"
```
When this code runs, the `Promise` constructor starts an asynchronous operation (simulated by
`setTimeout`). After 1 second, it calls `resolve`, marking the promise as fulfilled. JavaScript then calls
the function you provided to `then`, passing in the result. Even though the work started earlier, the
callback runs later, once the main thread is free.
The key strength of promises is chaining. The `then` method itself returns a new promise, allowing
you to connect multiple asynchronous steps linearly. If a `then` handler returns a plain value, the
next promise in the chain automatically resolves with that value. If it returns another promise,
JavaScript waits for that promise to finish before moving on. If a handler throws an error, the next
promise becomes rejected automatically. This consistent rule is what flattens complex asynchronous
flows into simple, readable sequences.
Example:
```js
fetch("/user")
```
Each `then` waits for the previous promise to settle before running. Errors in any step skip the rest
and jump straight to `catch`, simplifying error handling that would otherwise require multiple nested
callbacks.
Promises also provide combinator methods for working with multiple async tasks:
139
- `[Link]([a, b, c])` runs all at once and fulfills when _all_ succeed (or rejects on the first failure).
- `[Link]([a, b, c])` waits for all to finish, whether successful or not, returning their
outcomes.
- `[Link]([a, b, c])` settles as soon as _any_ one promise settles (either fulfillment or rejection).
- `[Link]([a, b, c])` fulfills on the first success and rejects only if _all_ fail.
For instance:
```js
[Link]([api1, api2])
```
Here, both API requests start together. `[Link]` waits until both complete, then continues. This is
efficient because it doesn't run them sequentially — they happen in parallel.
Imagine ordering food at a restaurant. You place your order (start an async task), and the waiter gives
you a token number — that's your promise. The token doesn't contain the food yet; it just represents
a guarantee that your meal will eventually be ready or the restaurant will tell you it can't be served
(error). While waiting, you're free to chat or browse your phone — your program isn't blocked. Later,
when your meal is ready, the kitchen signals fulfillment, and your waiter delivers the result. If they
run out of ingredients, the promise is rejected and you're informed of the error. The token (promise)
itself never changes; it only moves from "pending" to "fulfilled" or "rejected." You can even attach
multiple handlers — maybe one person is waiting for the food (then) and another for the bill (finally).
This analogy helps illustrate that promises aren't about doing tasks faster — they're about managing
time and coordination. They let your code keep running other tasks while waiting for asynchronous
operations, without getting tangled in callback pyramids.
140
In summary:
- `then`, `catch`, and `finally` let you handle those outcomes cleanly.
- Promises make asynchronous code predictable, composable, and far easier to reason about.
Examples
```js
// Basic chaining
fetch("/[Link]")
```
```js
// Combinators
const a = fetch("/a");
const b = fetch("/b");
[Link]([a, b])
/* use both */
})
.catch([Link]);
```
141
```js
// Creating a promise
function delay(ms) {
```
Common misconceptions
1. A promise can resolve twice. Settle happens once; further resolve/reject calls are ignored.
2. then runs immediately. Handlers run in the microtask queue after current synchronous work.
3. [Link] ignores rejections. It rejects fast on the first rejection; use allSettled to observe all
outcomes.
Practice questions
1. Theory: Explain how returning a value, returning a promise, and throwing inside then affect the
next link in the chain.
2. Coding: Implement a timeout wrapper that rejects if a promise doesn't settle within ms; test it
with fetch.
3. Coding: Fetch three URLs in parallel, parse all JSON results, and handle partial failures gracefully
using allSettled.
142
What is async/await and how is it different from
promises?
What is async/await and how is it different from promises
async/await is syntax that builds on promises to make asynchronous code read like synchronous
code. Declaring a function async means it always returns a promise. Inside an async function, await
pauses that function until the awaited promise settles: if it fulfills, await yields its value; if it rejects,
await throws that reason. This pause is cooperative—JavaScript's single thread is not blocked; the
runtime schedules the rest of the async function as a microtask continuation. Errors are handled with
ordinary try/catch around awaits, and finally works as expected, which makes control flow clearer
than long then chains for sequential steps.
Despite the different look, the semantics are still promise-based. Awaiting a non-promise converts it
to an already-fulfilled promise; returning a value from an async function resolves the returned
promise with that value; throwing creates a rejected promise. For parallel work, start multiple
promises first and then await them together (e.g., [Link]). If you write await inside a plain for
loop with a slow async operation, you serialize the work one-by-one; that's correct when order
matters but wasteful when tasks are independent. The right pattern is to kick off all tasks, then await
their combined completion. Understanding these patterns yields readable, efficient async code that
behaves predictably under error conditions.
Examples
```js
try {
return { a, b };
} catch (e) {
[Link](e);
throw e;
143
} finally {
[Link]("done");
```
```js
const pa = fetch("/a");
const pb = fetch("/b");
return { a, b };
```
```js
return [Link]();
```
Common misconceptions
1. await blocks the thread. It suspends the async function only; the event loop continues running
other work.
2. async functions return plain values. They return promises; use await or then to get the result.
3. await is a replacement for [Link]. It is not; use [Link] for true parallelism when tasks are
independent.
144
Practice questions
1. Theory: Describe how await interacts with the microtask queue and why try/catch maps cleanly to
promise rejection handling.
2. Coding: Convert a 3-step then chain into an async function with proper error handling and a finally
block.
3. Coding: Given an array of URLs, implement (a) sequential fetch with await in a loop and (b) parallel
fetch with [Link]—compare behavior and performance.
145
What are microtasks and macrotasks?
Explain microtasks and macrotasks
Modern JavaScript runs on a **single-threaded** event loop. It executes code, handles events and
renders the UI by processing a queue of tasks one after the other. To keep the browser responsive,
the runtime splits work into **macrotasks** (also called "tasks") and **microtasks**.
Understanding the difference helps you predict when your code will run and how it interacts with the
rest of the page.
Macrotasks represent the "big" units of work handled by the browser. Each macrotask comes from a
different source: an entire script block, a callback from an event (click, scroll, etc.), timers like
`setTimeout()` and `setInterval()`, or I/O such as network responses. The event loop picks one
macrotask from the queue, runs it to completion (including any synchronous code), then moves on
to the next one. Because the browser only processes one macrotask at a time, long-running tasks can
block rendering and make the page feel sluggish.
Microtasks are tiny jobs that must run _after_ the current macrotask finishes but _before_ the
browser does anything else. They're created by promises (`.then`, `.catch` and `.finally` callbacks) and
the `queueMicrotask()` API When a macrotask ends, the event loop empties the microtask queue —
executing each microtask in order — **before** it renders the page or processes the next
macrotask. This guarantees that promise handlers always run after the code that scheduled them,
but before other asynchronous events.
2. When that macrotask finishes, execute **all** pending microtasks. If a microtask queues more
microtasks, they run before the loop continues
146
Because microtasks run **before** rendering and before the next macrotask, they have higher
priority. This ensures that promise chains update data consistently and avoids race conditions.
However, a microtask that continually re-queues itself can starve rendering and freeze the UI.
```js
[Link]("start");
[Link]("end");
```
```
start
end
promise
timer
```
`setTimeout` schedules a **macrotask** with a minimum delay of 0 ms. Even though the delay is
zero, it won't run until after the current macrotask and all microtasks finish.
`[Link]().then(...)` schedules a **microtask**, so its callback runs immediately after the
current macrotask completes, and before the `setTimeout` callback.. As a result, "promise" appears
before "timer".
147
Imagine a restaurant kitchen. The head chef (the event loop) works on one **order** at a time (a
macrotask). Between orders he must finish some **quick chores** (microtasks) like wiping the
counter or plating dishes from finished orders. Even if a new order arrives (`setTimeout`), the chef
won't start it until he has completed both the current order and all the small chores. Microtasks keep
the kitchen tidy and ensure the next order starts with a clean slate.
### Summary
- **Microtasks** come from promises and `queueMicrotask()`. They run after the current
macrotask, but before the browser renders and before the next macrotask.
- The microtask queue is emptied completely before moving on, even if microtasks add more
microtasks.
- Using microtasks lets you schedule code to run as soon as possible without blocking user events;
however, an infinite microtask loop can freeze the page.
1. **"Promises run immediately."** The executor function inside a `new Promise(...)` runs
synchronously, but the `.then()` and `.catch()` callbacks run as microtasks after the current macrotask
finishes.
2. **"`setTimeout(fn, 0)` is synchronous."** Even with a delay of 0ms, `setTimeout` schedules a
macrotask that runs after microtasks and the current macrotask.
3. **"Microtasks can be ignored."** Because microtasks run before rendering, forgetting to empty
the microtask queue (for example, by chaining lots of promises) can delay UI updates and cause
performance issues.
1. **Theory:** Describe the order of logs in the following snippet and explain why:
```js
[Link](1);
148
setTimeout(() => [Link](4), 0);
[Link](5);
```
3. **Coding:** Create a timer that uses `setTimeout` recursively instead of `setInterval`. Why might
this be preferable when the task takes longer than the interval?
149
Explain setTimeout, setInterval, and clearTimeout
Explain `setTimeout`, `setInterval` and cancellation functions
JavaScript timers let you schedule code to run later or repeatedly. They're essential for tasks like
animations, polling a server or debouncing user input. The language provides four related functions:
`setTimeout()`, `setInterval()`, `clearTimeout()` and `clearInterval()`. Understanding how they work
and when to use each will help you write responsive programs.
`setTimeout()` sets a one-off timer. It takes a callback function, a delay in milliseconds and optional
arguments to pass to the callback. After at least `delay` milliseconds have elapsed and the current
call stack is empty, the callback runs. The call returns a numeric **timer ID** which you can pass to
`clearTimeout()` to cancel the timer. If `delay` is omitted or coerced to `0`, the callback is scheduled
as soon as possible but still runs after all pending microtasks and the current macrotask.
```js
}, 1000);
clearTimeout(id);
```
Notes:
- JavaScript runs tasks one at a time using something called the "event loop." When you use
`setTimeout()` to schedule a function, you're asking the browser to run that function after a certain
number of milliseconds. However, this waiting time isn't an exact guarantee. If other code is already
running or if the browser is busy handling things like user events or rendering the page, your timer's
callback will wait its turn and may fire later than you requested.
Browsers also set a lower limit on how frequently timers can fire when they're nested deeply. After
you schedule a few timers inside one another (beyond about five levels of nesting), most browsers
150
won't let them run faster than roughly every 4 milliseconds. This "clamping" prevents runaway loops
from bogging down the tab. In practice, it means that when you repeatedly schedule one
`setTimeout()` from inside another, the callbacks might not run back-to-back; the browser will slow
them down slightly to keep the page responsive.
- The callback runs asynchronously and does not block subsequent code execution.
`setInterval()` repeatedly calls a function with a fixed delay between calls. It returns an **interval
ID**. The callback continues to run until you call `clearInterval(id)`. Interval IDs share the same pool
as timeout IDs; you can technically clear an interval with `clearTimeout()`, but it's better practice to
match functions.
```js
let count = 0;
[Link]("Tick", ++count);
if (count === 5) {
}, 1000);
```
Important considerations:
When you use setInterval() to run a function repeatedly, the browser tries to call that function every
X milliseconds regardless of how long the function itself takes to finish. If your function takes longer
to run than the interval you've set, the browser can end up stacking multiple calls on top of each
other, it might start the next call before the previous one has finished. This can slow your page or
create unexpected overlaps. A common workaround is to use setTimeout() inside your function to
schedule the next run only after it has completed. That way, each run waits for the previous one to
finish before starting the timer again, so nothing overlaps.
Browsers also have built-in limits to prevent timers from running too rapidly. If you have a chain of
timers (or intervals) nested more than a few levels deep, most browsers will automatically slow them
151
down so they don't fire more often than about once every four milliseconds. This "throttling" helps
keep the page responsive by avoiding a flood of very fast, back-to-back timer events.
`clearTimeout()` and `clearInterval()` stop scheduled timers. Both functions accept the numeric ID
returned by `setTimeout` or `setInterval` and cancel any pending execution. If the ID is invalid,
nothing happens. Timer IDs are stored on the global `window` object, so clearing an interval with
`clearTimeout()` technically works, but for readability you should use the matching clear function.
```js
clearTimeout(timeoutId);
clearInterval(intervalId);
```
Think of `setTimeout` as setting a single **alarm clock**: you set it for the future, go about your day,
and when it rings you perform the task. `setInterval` is like a **repeating alarm** that keeps ringing
every morning until you switch it off. The IDs returned are like the alarm handles; calling
`clearTimeout` or `clearInterval` is pressing the off button.
### Summary
- **`setTimeout`** schedules a callback to run once after a delay. It returns an ID used to cancel the
timer.
- **`setInterval`** schedules a callback to run repeatedly with a fixed delay between executions and
returns an ID. Use `clearInterval` to stop it.
152
- For better control over periodic tasks (especially when the task itself takes time), prefer a recursive
`setTimeout` over `setInterval`.
- Timers execute asynchronously; even a delay of 0 ms doesn't make the callback synchronous.
1. **"`setTimeout(fn, 0)` runs immediately."** A zero delay still defers execution until after the
current call stack and microtasks are finished.
2. **"`setInterval` is always precise."** Intervals don't compensate for execution time; if the
callback takes longer than the interval, calls can overlap or drift. Use recursive timeouts when
accuracy matters.
3. **"You can only cancel a timeout with `clearTimeout`."** While timeouts and intervals share an
internal ID pool, mixing clear functions works but is bad practice.
1. **Theory:** Why might repeated calls with `setInterval` drift over time? How does recursive
`setTimeout` alleviate this?
2. **Coding:** Write a function `delay(ms)` that returns a promise and resolves after `ms`
milliseconds using `setTimeout`.
3. **Coding:** Implement a countdown from 10 to 0 that prints a number every second and then
prints "go!" using a timer. Include a button to cancel the countdown with `clearInterval`.
153
What is debouncing and throttling?
Explain debouncing and throttling
Web pages often listen to frequent events such as `resize`, `scroll` and `keyup` also multiple user
initiateed events like clicks. Without control, handlers for these events can fire dozens of times per
second, causing performance issues or unnecessary network requests. **Debouncing** and
**throttling** are two patterns for rate-limiting function calls. They serve similar goals but behave
differently.
### Debouncing
Debouncing delays a function call until a certain amount of time has passed since the last invocation.
When an event triggers repeatedly, the debounce wrapper resets its timer each time; only when no
new events occur during the wait period does it finally call the function. This technique reduces the
number of calls by ensuring the function executes **once** after a burst of activity【
52406376895244†L118-L128】. Common use-cases include auto-saving form data or making API
requests after a user stops typing.
```js
let timerId;
clearTimeout(timerId);
};
[Link](
"input",
debounce(() => {
// This runs only after the user stops typing for 300 ms
154
performSearch([Link]);
}, 300)
);
```
### Throttling
Throttling ensures that a function runs at most once per specified interval. When events occur more
frequently than the limit, extra calls are ignored or delayed. Unlike debouncing, throttling does not
wait for the activity to stop — it guarantees regular execution (e.g., every 100 ms) regardless of how
many events fire. Throttling is useful for events like `scroll` or `mousemove` where you need periodic
updates (such as updating a progress bar or showing scroll position).
```js
let lastCall = 0;
lastCall = now;
[Link](this, args);
};
[Link](
"scroll",
155
throttle(() => {
}, 100)
);
```
This version executes the callback immediately and then prevents further calls until the time window
has passed. A more advanced implementation might schedule the final call after the event burst
ends.
**Advantages:** Throttling enforces a consistent rate of execution. It can keep UI updates smooth
under heavy event load. **Disadvantages:** It may skip intermediate events; if the interval is too
long, the handler might miss important changes.
- Use **debounce** when you want to wait for a "pause" in activity — for example, delaying an API
call until the user stops typing.
- Use **throttle** when you want periodic updates regardless of continuous activity — for example,
updating the scroll position every 100 ms during a scroll.
- **Debouncing** is like waiting until a friend finishes speaking before responding. If they keep
talking, you hold off; you only reply once they pause.
- **Throttling** is like setting a timer to check your phone every minute. Even if you get dozens of
notifications in between, you only look when the minute timer goes off.
### Summary
Debouncing waits for a quiet period before running a function. Each time the button is clicked, a
timer is started (or restarted). If no additional clicks happen during that timer window (say 3
156
seconds), the API call executes. If another click occurs before the delay elapses, the timer resets and
the countdown starts over. This ensures that the API is called only once after the user has stopped
clicking.
Throttling allows the action to happen immediately, but then suppresses additional triggers for a set
period. On the first click, the API call runs right away. For the next few seconds (again, say 3 seconds),
any further clicks are ignored. Only after that interval has passed will a new click result in another API
call. This guarantees that the function is called at most once per interval, regardless of how often the
user clicks.
Debounce: Imagine clicking a button that triggers an API call. When the function is debounced, a
delay is introduced between the moment the user interacts and the moment the call actually fires.
For example, if you set a delay of three seconds, the API call will execute only after three seconds
have passed without any further clicks. If the user clicks again before those three seconds are up, the
delay resets, and the function waits for another full three seconds from the most recent click before
firing.
Throttle: With throttling, the first click triggers the API call immediately, just as it normally would.
However, subsequent clicks are ignored until a set period has elapsed. Continuing the three-second
example, after the initial call runs, no additional calls will occur until three seconds have passed. Only
then will another click trigger a new API call.
- Debouncing reduces call count but adds delay. Throttling provides regular updates but may drop
events.
1. **"Debounce and throttle are the same."** They both rate-limit calls, but debounce waits for
inactivity while throttle guarantees periodic execution.
2. **"Debounce functions always improve UX."** Over-debouncing can make an interface feel
unresponsive, because the action waits until the user stops for long enough.
3. **"Throttle always catches every event."** Throttling discards events between intervals; if the
interval is too long, important changes may be skipped.
157
1. **Coding:** Write your own `debounce` function that accepts a callback and a delay, and
demonstrate it with a `keyup` handler that sends an API request only when the user stops typing.
2. **Coding:** Implement a `throttle` function that ensures a callback fires once every 200 ms, and
test it on a `scroll` event.
3. **Theory:** Describe a scenario where debouncing would be inappropriate but throttling would
work well, and vice versa.
158
What is event bubbling and capturing?
Explain event bubbling and capturing
When you click, type or otherwise interact with elements on a page, the browser fires an "event"
that moves through the document tree. How and where you respond to that event depends on
understanding the two propagation phases:
Capturing (also called trickling) - The event is dispatched from the top of the DOM hierarchy (window
or document) and moves downward through each ancestor element until it reaches the actual
target. Listeners registered with addEventListener() using {capture: true} fire in this phase, starting
from the outermost ancestor and ending with the target. Capturing is disabled by default because
most code relies on bubbling, but it's available when you need to intercept an event before it hits its
destination.
Target phase - Once the event reaches the element that originally triggered it (for example, the
button you clicked), any event listeners attached directly to that element run, regardless of whether
they were registered for capturing or bubbling.
Bubbling - After the target has processed the event, it bubbles back up the DOM tree. The event
moves from the target's parent up to the root, invoking listeners along the way. This is the default
behavior for most event types. When you call addEventListener() without options, you're registering
a listener that will fire during this bubbling phase.
Understanding these phases allows you to decide where to attach your handlers:
Normal use case (bubbling): Attach a listener on a parent element to handle events from many
children (a technique known as event delegation). For example, a click on a list item bubbles up to
the <ul>, and a click handler on the <ul> can inspect [Link] to determine which <li> was
clicked.
Capturing use case: Attach a listener with {capture: true} when you need to act before the event
reaches the target. This can be useful for intercepting keyboard shortcuts or gestures at the
document level or for implementing custom UI controls that need to block events from hitting
certain elements.
Several other points are important for both beginners and experienced developers:
159
You can stop an event from continuing along its propagation path by calling [Link]().
To prevent other handlers on the same element from running as well, call
[Link]().
The properties [Link] and [Link] differ: [Link] is the element where the
event originated, while [Link] is the element whose listener is currently executing.
Not all events bubble (e.g. focus and blur do not), and some behave inconsistently across older
browsers, so always test your code in the environments you support.
By controlling where in the capture/bubble phases your handlers run and understanding how the
event travels, you can build more efficient and responsive UIs, whether you're just starting out or
writing complex component systems.
```html
<div id="outer">
</div>
```
```js
document
.getElementById("outer")
document
.getElementById("inner")
160
```
```
inner
outer
```
The click originates at `#inner`, triggers its handler, then bubbles up to `#outer` and runs the outer
handler.
### Capturing
Capturing is the mirror image of bubbling. The event starts at the top of the tree and flows down to
the target, invoking handlers registered for the capture phase. To attach a listener during capture,
provide `{ capture: true }`:
```js
document
.getElementById("outer")
capture: true,
});
document
.getElementById("inner")
```
```
161
outer capture
inner
```
The capturing handler runs before the target and bubbling handlers because the event travels down
the tree first.
Inside an event handler you can stop the event from continuing along the propagation path:
- `[Link]()` halts further propagation through both capturing and bubbling phases.
Stopping propagation is useful when you don't want parent elements to receive the event (for
example, clicking inside a modal dialog should not close the underlying page).
Imagine dropping a stone into a pond. The initial splash is the **target phase**. Ripples that travel
outward to the shore resemble **bubbling**: the disturbance spreads from the point of impact to
the edges. Now imagine someone at the shore sending a vibration back toward the point where the
stone hit — that's **capturing**: energy travels from the outside in.
### Summary
- DOM events have three phases: capturing (from root down), target, and bubbling (from target up).
- Most event listeners fire during bubbling by default. Pass `{ capture: true }` to listen during the
capturing phase.
- Understanding propagation helps with advanced patterns like **event delegation** and preventing
unwanted side effects.
162
### Common misconceptions
1. **"Events only bubble."** Many events bubble by default, but capturing exists and is enabled by
passing `{ capture: true }`.
1. **Theory:** In what order do the following handlers fire when capturing and bubbling are both
used? Explain why:
```html
<div id="parent">
<button id="child">Click</button>
</div>
<script>
capture: true,
});
</script>
```
2. **Coding:** Build a modal dialog component. Clicking outside the dialog should close it; clicking
inside should stop propagation so the backdrop doesn't close.
163
3. **Coding:** Create a custom event and dispatch it on a child element. Add listeners at different
phases and log `[Link]` and `[Link]` to see how they differ.
164
How does event delegation work?
Explain how event delegation works
Attaching event listeners to every element in a list can be inefficient, especially when the list is long
or dynamic. **Event delegation** solves this problem by taking advantage of event bubbling:
instead of listening on each child element, you attach a single listener on a common ancestor. When
an event bubbles up, you check which child triggered it and handle it appropriately. This approach
reduces the number of handlers and works for elements added later.
When a user interacts with an element, the event travels up through its ancestors during the
bubbling phase. Because of this, a handler on a parent element can "see" events from its children. In
a delegated handler you inspect `[Link]` (the original element that fired the event) and decide
whether to respond. You often use `[Link]()` to ensure the target matches the selector you
care about.
```html
<table id="data-table">
<tr>
<td>Cell A</td>
<td>Cell B</td>
</tr>
<tr>
<td>Cell C</td>
<td>Cell D</td>
</tr>
</table>
165
```
```js
table
.querySelectorAll(".selected")
[Link]("selected");
});
```
Because the listener is on the `<table>`, it handles clicks on any existing or future `<td>` elements.
Calling `closest('td')` ensures that a click on a nested element inside the cell still resolves to the cell
itself.
Event delegation also makes it easy to build component APIs. For example, a menu might contain
buttons with `data-action` attributes indicating what to do:
```html
<ul id="menu">
<li><button data-action="save">Save</button></li>
166
<li><button data-action="load">Load</button></li>
<li><button data-action="delete">Delete</button></li>
</ul>
```
```js
if (!button) return;
switch (action) {
case "save":
saveFile();
break;
case "load":
loadFile();
break;
case "delete":
deleteFile();
break;
});
```
Adding new actions later requires only new HTML. The single listener on `<ul>` handles all current
and future buttons.
- **Performance:** Fewer listeners reduce memory usage and avoid attaching thousands of
handlers to similar elements.
167
- **Dynamic content:** Elements created after the page loads are still handled, because the parent
listener continues to receive events.
- **Simpler management:** One handler centralizes logic; you don't need to add or remove
listeners when elements appear or disappear.
Imagine a party where guests bring their own cups. Instead of stationing a waiter at every table to
collect empty cups, you place a single bin near the exit. As guests leave (events bubble up), they drop
their cups into the bin (the delegated handler). There's no need to monitor each seat.
### Summary
- **Event delegation** uses event bubbling to handle events from multiple children with one parent
listener.
- Inside the delegated handler, inspect `[Link]` or use `closest()` to identify the relevant child.
- Delegation is great for lists, tables, menus and any dynamic content that may change after initial
rendering.
1. **"Delegation only works for click events."** Delegation works for any event that bubbles (e.g.
`input`, `mouseover`).
2. **"You must use capturing to delegate."** Delegation relies on bubbling; capture phase isn't
needed.
1. **Coding:** Create a list with "Delete" buttons next to each item. Use event delegation on the list
`<ul>` to handle clicks and remove the corresponding `<li>`.
2. **Coding:** Build a tab component where clicking a tab activates its panel. Use delegation so new
tabs added later still work.
168
3. **Theory:** Explain why delegation fails on events that do not bubble (e.g., `focus`). How can you
handle those events on many elements?
169
Difference between document, window, and this in
different contexts
Difference between `document`, `window` and `this` in different contexts
JavaScript in the browser exposes several objects representing different parts of the environment.
Two of the most important are the **`window`** and **`document`** objects, and the keyword
**`this`** behaves differently depending on how a function is called. Mixing them up can lead to
subtle bugs. Let's clarify their roles.
The `document` object represents the web page loaded in the browser. It is part of the DOM
(Document Object Model) and provides methods and properties to access and manipulate HTML
elements. You can select elements by ID, class, tag name or CSS selector and modify them at
runtime. For example, `[Link]('title')` returns an element with the given ID, and
`[Link]('.card')` returns a list of elements with the class `card`. Internally,
`document` is accessible via `[Link]`, but you rarely prefix it. The DOM defines a logical
structure for documents, allowing us to create, manipulate or delete elements and attributes.
The `window` object represents the browser window or frame itself. It sits at the top of the Browser
Object Model (BOM) and exposes features like screen size, history and location. Properties such as
`[Link]`, `[Link]`, `[Link]` and methods like `alert()`, `setTimeout()`,
`open()` or `close()` belong to the `window` object. In a browser, all global variables and functions
become properties of `window`. For instance, declaring `var x = 5;` makes `window.x === 5` true. This
is why you can often omit `window.` when calling `alert()` or `setTimeout()`. Note that the BOM isn't
standardized, so some properties may vary between browsers.
`this` is a special keyword whose value is determined by how a function is invoked. It does **not**
point to the function itself, but to the object on which the function was called.
- **Global context:** Outside any function, `this` refers to the global object (`window` in browsers)
in non-strict mode. In strict mode, `this` in the global scope is `undefined`.
170
- **Simple function call:** When a function is called without an explicit receiver, `this` is the global
object in non-strict mode, or `undefined` in strict mode.
- **DOM event handler:** In an event handler added via `[Link]`, `this` is set to
the element on which the event fired. In inline event handlers (e.g., ` `this` also refers
to the element.
- **Arrow functions:** Arrow functions do not have their own `this`; instead they capture the `this`
value from the surrounding lexical scope. This makes them unsuitable for event handlers if you rely
on `this` pointing to the element.
### Examples
```js
// Global context
function show() {
[Link](this);
const person = {
name: "Ada",
greet() {
[Link]([Link]);
},
};
[Link]("btn").addEventListener("click", function () {
});
171
[Link]("btn").addEventListener("click", () => {
[Link](this === window); // true; arrow functions inherit 'this' from the outer scope
});
```
Think of `document` as the **blueprint** for a house and its contents. It lists every room and piece
of furniture and allows you to remodel the house on the fly. The `window` is the **actual house**
— the physical container that holds the blueprint and provides features like doors (navigation) and
windows (screen properties). The keyword `this` is like a pronoun whose meaning depends on who is
speaking — it refers to the current "actor" at the moment the code runs.
### Summary
- `document` represents the loaded web page and provides methods to access and modify its
elements.
- `window` represents the browser window and exposes global functions, timer APIs and
browser-specific features.
- `this` is bound at call time and varies depending on whether the function is called globally, as a
method, as a constructor, or as an event handler.
- Arrow functions capture `this` from their lexical scope instead of creating a new binding.
1. **"`document` and `window` are the same."** While `document` is a property of `window`, it
specifically refers to the DOM of the page, whereas `window` includes the DOM plus browser APIs
like `alert()` and `location`.
2. **"`this` always refers to the object where a function is defined."** `this` depends on how a
function is called, not where it's defined.
3. **"Arrow functions make `this` predictable everywhere."** Arrow functions inherit `this` from
their outer scope; using them in event listeners or object methods may not give you the element or
object you expect.
172
### Practice questions
1. **Coding:** Write a function that logs `this` and call it: (a) globally; (b) as a method of an object;
(c) as an event handler. Observe how `this` changes.
2. **Coding:** Create a custom object with a method that references `this`. Bind that method to
another object using `.call()` or `.apply()` and observe the output.
173
Explain DOM vs BOM
Explain DOM vs BOM
JavaScript runs in the context of a web browser, which provides two related but distinct models: the
**Document Object Model (DOM)** and the **Browser Object Model (BOM)**. Both expose
objects and methods to your scripts, but they serve different purposes.
The DOM is a **standardized programming interface** that represents an HTML or XML document
as a tree of nodes. When an HTML document is loaded, the browser parses it and creates a DOM
tree. JavaScript can traverse this tree, create new elements, remove existing ones, and update
attributes and styles. Functions like `getElementById()`, `querySelector()`, `appendChild()` and
properties like `[Link]` or `[Link]` allow you to manipulate the structure and
content of the page. As GeeksforGeeks notes, the DOM defines the logical structure of documents
and provides methods to access and modify tags, IDs, classes and attributes.
The BOM refers to the **collection of objects provided by the browser** that let JavaScript interact
with the browser itself rather than the document. Unlike the DOM, there is no formal standard for
the BOM, so implementations vary slightly among browsers. The root of the BOM is the `window`
object. It exposes properties like `navigator` (information about the browser), `location` (current
URL), `history` (the user's navigation history), `screen` (screen size and color depth), and `document`.
The BOM also includes methods for controlling windows such as `open()`, `close()`, `moveTo()` and
`resizeTo()`. For example:
```js
"[Link]
"_blank",
"width=400,height=300"
);
174
// ... later
[Link]();
```
Think of a website as a **book** in a library. The DOM is the table of contents and the pages of the
book; it represents the structure of the content and lets you read or edit chapters. The BOM is the
**building** that houses the library — it includes the doors, windows and elevators. It lets you open
a new room (tab), check the building's address (URL) or find out the size of the reading rooms
(screen).
### Summary
- The **DOM** is a standardized API for representing and interacting with documents. It lets you
create, access and modify HTML elements and their attributes.
- The **BOM** is a browser-specific collection of objects that let you interact with the environment:
windows, frames, navigation history and screen information.
- They complement each other: `document` (DOM) is a property of `window` (BOM), but the DOM
focuses on the page content while the BOM focuses on the container.
1. **"The BOM is part of the DOM."** The BOM includes `window` and related objects which exist
outside the document. The DOM deals only with the document's content and structure.
2. **"The BOM follows a standard API."** There is no formal specification for the BOM; browser
vendors implement it differently, so not all methods behave identically across browsers.
175
3. **"You can manipulate the page through the BOM alone."** While the BOM provides access to
high-level browser features, it does not provide methods to access or change specific HTML
elements.
1. **Coding:** Use the DOM to create a new `<div>` element, set its text content, append it to the
body and then use the BOM to open a new tab displaying its inner text.
2. **Theory:** List three properties or methods available on `window` but not on `document`. What
are their purposes?
3. **Theory:** Why can't you rely on the BOM for consistent behavior across browsers? What
precautions should you take when using methods like `[Link]()`?
176
What are Web APIs?
Explain Web APIs
At its core, JavaScript is a programming language that manipulates numbers, strings, objects and
arrays. But when you run JavaScript in a web browser you get access to a rich set of **Web APIs** —
additional objects and functions built into the browser that let you do things like manipulate the
DOM, fetch data over the network, store data locally and interact with device hardware. These APIs
are not part of the JavaScript language itself; they are provided by the host environment.
- **Browser (built-in) APIs** are integrated into the browser. They let you access data from the
browser and the computer (e.g., geolocation, camera, file system) and perform tasks like drawing
graphics or storing data offline. The Web Audio API, for instance, provides constructs to manipulate
audio in the browser.
- **Third-party APIs** are delivered by external services such as Google Maps or Facebook and
allow you to integrate their functionality into your app. Unlike browser APIs, you need to load these
via external scripts or modules.
MDN explains that client-side programming typically involves several layers: **JavaScript** (the core
language), **browser APIs** on top of it, **third-party APIs**, and optionally libraries or
frameworks. JavaScript by itself can't, for example, fetch a resource over the network; it needs the
Fetch API. Libraries like React and frameworks like Angular build on these APIs to provide higher-level
abstractions.
177
### Common categories of browser APIs
Modern browsers provide a huge number of APIs. Here are some of the most common categories:
```js
fetch("[Link]
.then((data) => {
})
```
The `fetch()` function is part of the Fetch API. It returns a promise that resolves to a `Response`
object; calling `[Link]()` parses the JSON body. Behind the scenes, the browser handles
networking and security. Without this API you'd need to rely on older, less consistent interfaces.
Web APIs are like **power outlets** in a house. The JavaScript language itself is the wiring and
switches; the outlets (APIs) let you plug in powerful appliances like vacuum cleaners (audio/video),
ovens (file access) or televisions (graphics) without worrying about how electricity is generated.
Different rooms (categories) offer different outlets tailored to specific devices.
### Summary
178
- Web APIs are sets of functionality provided by the browser or third parties that extend what
JavaScript can do.
- **Browser APIs** expose features like DOM manipulation, network requests, graphics, media,
device access and storage.
- **Third-party APIs** offer services from external providers such as maps, payments or social
features.
- APIs sit on top of JavaScript; libraries and frameworks build on top of APIs to create higher-level
abstractions.
1. **"Web APIs are part of JavaScript."** APIs are provided by the browser or external services;
they are not defined by the ECMAScript language specification.
2. **"All APIs require a network."** Many browser APIs (DOM, Canvas, Web Audio) operate entirely
locally and don't involve network requests.
3. **"Using a library replaces Web APIs."** Libraries and frameworks build on Web APIs. Even when
using React or jQuery, under the hood they still call DOM methods or Fetch.
1. **Coding:** Use the Geolocation API to get the user's current latitude and longitude and display it
on the page. Handle errors if the user denies permission.
2. **Coding:** Build a simple drawing app using the Canvas API that lets the user draw lines with the
mouse.
3. **Theory:** Explain the difference between a browser API like `localStorage` and a third-party API
like the Google Maps API. What extra steps are needed to use the latter?
179
What is localStorage, sessionStorage, and cookies?
Explain `localStorage`, `sessionStorage` and cookies
Web applications often need to remember information between page loads. Browsers provide
several mechanisms to store data on the client side: **cookies**, **sessionStorage** and
**localStorage**. Each has different characteristics regarding scope, lifetime, size and how data
travels.
### Cookies
Cookies are small pieces of data (name/value pairs) that a server sends to the browser. The browser
stores cookies and sends them back to the same server with subsequent requests. Cookies allow
web applications to remember state across HTTP requests, which are otherwise stateless. A typical
use case is session management: after a user signs in, the server sets a cookie containing a session
ID; on later requests the browser includes that cookie so the server knows the user is authenticated.
Cookies are also used for personalization and tracking.
- **Size and number limits:** Browsers restrict the number of cookies per domain and limit each
cookie to around 4KB.
- **Automatic transmission:** Cookies are sent with every HTTP request to their associated domain,
which can impact performance on slow connections.
- **Expiration:** Cookies can be set with an `Expires` or `Max-Age` attribute to persist for a given
time. Without those attributes they are **session cookies** and are deleted when the browser
session ends.
- **Access:** In JavaScript you can read and write cookies using `[Link]`, but they are not
as straightforward to manage compared to Web Storage.
The Web Storage API provides a simpler key/value storage mechanism than cookies. Data stored via
Web Storage never travels to the server; it stays entirely on the client. Two separate storage areas
exist per origin:
180
- **`sessionStorage`** is scoped to the **browser tab** and **origin**. Each tab (and its iframes)
gets its own session storage. Closing the tab clears the data. Data is not shared across tabs or
browser windows. You access it via `[Link]`.
- **`localStorage`** is scoped to the **origin** only. All pages from the same origin share the same
storage, and the data persists even when the browser is closed and reopened. You access it via
`[Link]`.
```js
[Link]("counter", "1");
```
Choosing among cookies, `sessionStorage` and `localStorage` depends on what you need to store and
who needs to read it.
- **Server-side needs**: Cookies are automatically included in HTTP requests to their associated
domain. This makes them suitable for storing session identifiers, authentication tokens or user
preferences that the server needs to see on each request.
- **Cross-page or cross-tab persistence**: Cookies persist across tabs and sessions if you set an
expiration date, so they can maintain login state or language preferences.
- **Small data**: Because cookies are limited to a few kilobytes each and contribute to the size of
every request, they should only hold small pieces of information.
181
Although cookies are often set by the server via the `Set-Cookie` header, client-side scripts can read
and write them using `[Link]`. Keep in mind that cookies marked as `HttpOnly` by the
server cannot be accessed from JavaScript, and sensitive cookies should always be set with the
`Secure` and `SameSite` attributes to mitigate security risks.
- **Per-tab or per-window data**: If you need to store temporary state that is specific to a single
browser tab—for example, progress through a multi-step form or data that should reset when the
user closes the tab—`sessionStorage` is ideal. Each tab gets its own storage area, and the data is
cleared when that tab is closed.
- **Client-only data**: Use `sessionStorage` for data that the server doesn't need, since it never
leaves the browser.
- **Persistent client-side data**: `localStorage` retains data across browser sessions. It's well suited
for things like theme preferences, "remember me" flags, or other settings that should survive a page
refresh or browser restart.
- **Larger storage needs**: Browsers typically allow several megabytes of storage via `localStorage`,
so it can hold more data than cookies. However, it's still best to avoid storing highly sensitive
information, as any script running on the page can read it.
1. **Does the server need to read it?** Use cookies for data that must accompany every request
(e.g., session IDs). Use Web Storage for data the server never needs.
2. **How long should it last?** Use `sessionStorage` for temporary, per-tab data; `localStorage` for
data that persists until explicitly cleared; cookies for short-term or long-term server-visible data
depending on their expiration.
3. **How big is the data?** Cookies are limited to a few kilobytes and should stay small.
`sessionStorage` and `localStorage` can hold significantly more.
4. **Security considerations:** Cookies can be protected with `HttpOnly` and `Secure` flags and sent
over HTTPS, making them suitable for credentials. Data in Web Storage is accessible to any script
running in that origin, so don't store secrets there.
182
In practice, it's common to see both client-side and server-side code setting and reading cookies. For
example, a front-end app might set a cookie to track a non-essential preference or to trigger
analytics, while the server uses its own cookies for authentication.
Important characteristics:
- **Persistent vs temporary:** `localStorage` persists across sessions; `sessionStorage` lasts until the
tab or window is closed.
- **Per-origin isolation:** Storage is partitioned by origin; pages from different domains cannot read
each other's storage.
- **Synchronous operations:** Reading and writing to Web Storage are synchronous; large or
frequent writes can block the main thread.
- **Capacity:** Browsers typically allow several megabytes of storage, far more than the few
kilobytes allowed for cookies.
Imagine a hotel. A **cookie** is like a hotel key card that you must present every time you enter
your room; the hotel (server) issues and recognizes the card to know which room you should access.
**`sessionStorage`** is like a personal note pad you carry during your stay; it exists only as long as
you're in the hotel and doesn't leave the building. **`localStorage`** is a storage locker you rent in
town; it remains yours even when you leave the hotel and come back later.
### Summary
- **Cookies** are small name/value pairs sent to and from the server. They enable sessions,
personalization and tracking but are limited in size and number.
- **`sessionStorage`** stores data per tab and origin. It's cleared when the tab closes and isn't
shared across tabs.
- **`localStorage`** stores data per origin and persists across browser sessions until explicitly
cleared.
183
- Unlike cookies, data in Web Storage (both session and local) is not sent to the server and can hold
much more data.
1. **"`localStorage` is secure storage."** Data in Web Storage is accessible to any script on the
page. Do not store sensitive information (like passwords) there.
2. **"`sessionStorage` persists across tabs."** Each tab has its own session storage; closing the tab
deletes its data.
3. **"Cookies can hold large amounts of data."** Each cookie is limited to a few kilobytes and
browsers limit the number of cookies per domain.
1. **Coding:** Store a user's preferred theme ("dark" or "light") in `localStorage` and apply it when
the page loads.
2. **Coding:** Create a page counter using `sessionStorage` that increments every time the user
reloads the tab but resets when the tab is closed.
3. **Theory:** Describe scenarios where cookies are necessary instead of Web Storage. What
security attributes (e.g., `HttpOnly`, `Secure`) should be set on cookies used for authentication?
184
What is CORS and how does it work?
Explain CORS and how it works
Modern web applications often need to request resources from different domains — for example, a
single-page app hosted on `[Link]` may fetch data from an API at `[Link]`. **Cross-
Origin Resource Sharing (CORS)** is a mechanism that allows (or disallows) such cross-origin
requests in a secure way. To appreciate why CORS exists, you first need to understand the **same-
origin policy**.
Browsers enforce a security model called the **same-origin policy**: a script running on a web page
can only read data from the same protocol, domain and port that served it. This prevents malicious
pages from accessing sensitive information on another site via `fetch()` or `XMLHttpRequest`. For
example, a page loaded from `[Link] cannot make a request to `[Link] and
read the response.
Cross-Origin Resource Sharing (CORS) is an HTTP-header based protocol that relaxes the same-origin
policy for approved requests. It allows a server to specify which origins are permitted to read its
resources. When a page makes a cross-origin request, the browser adds special CORS headers and
may send a **preflight** request to check whether the server will accept the actual request.
1. **Simple requests:** If the request uses a safe HTTP method (`GET`, `HEAD` or sometimes `POST`
with simple headers) and does not include custom headers, the browser automatically adds an
`Origin` header specifying the requesting domain. The server's response must include `Access-
Control-Allow-Origin` with either the requesting origin or `*` to permit the read.
2. **Preflight requests:** For requests that could modify server data (e.g. `PUT`, `DELETE`, or `POST`
with non-simple headers) the browser first sends an `OPTIONS` request to the server containing
`Access-Control-Request-Method` and `Access-Control-Request-Headers`. The server responds with
`Access-Control-Allow-Origin`, `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers`
to indicate whether the actual request is allowed. If approved, the browser proceeds with the actual
request; otherwise it aborts.
185
server must respond with `Access-Control-Allow-Credentials: true` along with a specific `Access-
Control-Allow-Origin` (not `*`), otherwise the browser will reject the response.
4. **Errors:** When a CORS request fails, JavaScript code cannot see the details. The browser simply
reports a generic error to prevent information leaks.
### Example
```js
// From [Link]
fetch("[Link]
```
Think of visiting a secure building. Normally, only employees (same origin) can enter any office. CORS
is like a guest policy: an employee (server) can put a list on the door (CORS headers) of which visitors
from other companies (origins) are allowed to enter and what rooms (methods/headers) they can
access. Before allowing a visitor into restricted rooms, security might call ahead (the preflight
request) to confirm it's okay.
### Summary
- The **same-origin policy** restricts scripts to resources from the same scheme, domain and port.
- **CORS** relaxes this policy by letting servers indicate which origins may access their resources via
HTTP headers.
186
- Browsers handle CORS enforcement; failure results in a generic error visible in the console but not
to JavaScript.
2. **"CORS allows any cross-origin request."** Only the origins explicitly allowed by the server are
permitted. Omitting the `Access-Control-Allow-Origin` header will still cause the browser to block
access.
1. **Theory:** Describe the difference between the same-origin policy and CORS. What problem
does CORS solve?
2. **Coding:** Write a simple Express or Node server that responds to `GET /api/data` with JSON
and sets `Access-Control-Allow-Origin: *`. Test fetching this endpoint from a different domain.
3. **Theory:** Why are preflight requests necessary? What headers does the browser send during a
preflight, and how should the server respond?
187
Difference between synchronous and asynchronous
code
Difference between synchronous and asynchronous code
JavaScript traditionally executes code **synchronously**: it runs one statement after another in the
order you wrote them, and each statement must finish before the next begins. MDN describes
synchronous programs as those where the browser "steps through the program one line at a time ...
waiting for the line to finish its work before going on to the next". This is straightforward to reason
about, but it has a drawback—long-running functions block the single JavaScript thread. For
example, an inefficient prime-number generator can freeze the user interface for seconds because
nothing else can happen until it returns.
- **Execution order** - In synchronous code, statements execute sequentially; the call stack must be
empty before the browser can do anything else. In asynchronous code, long-running tasks start and
then yield control back to the browser. When the task completes, its callback or promise handler
runs later via the event loop. This makes the application appear to "do two things at once" even
though JavaScript is single-threaded.
- **Blocking vs. non-blocking** - A synchronous function blocks the main thread: user interactions
and rendering wait until it finishes. The MDN asynchronous requests guide warns that synchronous
requests "block the execution of code" and cause the UI to freeze. Asynchronous functions, on the
other hand, don't block; the browser continues handling user input while awaiting the result.
- **Handling results** - Synchronous calls return their result immediately. Asynchronous calls return
a promise or accept a callback that will be invoked when the result is ready. Using promises
(`.then()`/`catch()`) or `async`/`await` makes asynchronous flows easier to read than nested callbacks.
```js
188
function generateLargePrimes(count) {
let num = 2;
function isPrime(n) {
return true;
if (isPrime(num)) [Link](num);
num++;
return primes;
function generatePrimesAsync(count) {
setTimeout(() => {
resolve(generateLargePrimes(count));
});
generatePrimesAsync(100000).then((primes) => {
});
189
[Link]("UI stays responsive while primes are computed");
```
In the synchronous version the browser must finish generating primes before doing anything else. In
the asynchronous version we wrap the computation in a `Promise` that resolves after a timeout. The
promise allows the event loop to process other events (like user input) before running the heavy
computation in a later tick.
Imagine standing in line at a coffee shop. A **synchronous** process would require you to wait at
the counter until your drink is made before the next customer can order; the barista can serve only
one person at a time. An **asynchronous** process is like taking a numbered ticket: you place your
order, receive a token and then sit down. While you chat with friends, the barista prepares multiple
orders. When your number is called, you pick up your drink. You were free to do other things while
waiting, and the barista could work on many orders without people blocking the counter.
1. **Asynchronous code runs on multiple threads.** In browsers, JavaScript still runs on a single
thread; asynchronous functions simply defer execution until the call stack is free. Web APIs or worker
threads perform the heavy work in the background, but your callbacks run on the main thread.
2. **Asynchronous code is always faster.** It doesn't make a task execute sooner; it just prevents
the UI from freezing. A network request still takes the same time to complete; asynchronous
handling lets your program respond to other events during that time.
3. **`async` functions run in parallel.** Declaring a function `async` means it returns a promise and
allows you to use `await`. It does not make the function concurrent by itself.
1. **Theory:** Explain why long-running synchronous functions cause the browser to become
unresponsive, and how asynchronous functions avoid that problem.
2. **Coding:** Convert a synchronous function that fetches data from an API using
`XMLHttpRequest` into an asynchronous version using the Fetch API and promises. Ensure that the UI
remains responsive while the data is loading.
190
3. **Coding:** Write an `async` function that performs three network requests in parallel using
`[Link]()` and returns the combined results. How would you handle errors if one of the requests
fails?
191
What is the Fetch API and how is it different from
XMLHttpRequest?
What is the Fetch API and how is it different from XMLHttpRequest?
Modern web applications need to communicate with servers without reloading the page. Two main
browser APIs provide this capability: **`XMLHttpRequest`** (XHR) and the newer **Fetch API**.
Both allow you to make HTTP requests from JavaScript, but they differ significantly in syntax, features
and design.
`fetch()` is a modern, promise-based interface for making HTTP requests in JavaScript. It replaces the
older `XMLHttpRequest` (XHR) with a cleaner syntax and integrates well with features like CORS and
service workers. A basic `fetch()` call looks like this:
```js
fetch("[Link]
.then((response) => {
if (![Link]) {
})
```
2. **Options object** - an optional `RequestInit` object where you configure the request.
192
It returns a **Promise** that resolves to a `Response` object once the server responds with headers.
If a network error occurs (e.g., DNS failure), the promise rejects. However, HTTP error status codes
(4xx/5xx) do **not** cause rejection; you must check `[Link]` yourself.
---
The optional `init` object passed to `fetch()` lets you specify details such as the HTTP method,
headers, body, credentials, caching, and more. Key properties include:
---
To send data to the server, change the `method` and supply a suitable `body`. When sending JSON,
set the `Content-Type` header and `body` to a stringified object:
```js
method: "POST",
headers: {
"Content-Type": "application/json",
},
193
body: [Link](user),
});
if (![Link]) {
return [Link]();
// Usage:
```
```js
[Link]("title", "Hello");
[Link]("image", [Link][0]);
fetch("/upload", {
method: "POST",
body: formData,
});
```
Here, you should **not** set a `Content-Type` header; the browser will set the correct multipart
boundary.
194
---
A `Response` object provides properties and methods to inspect and consume the reply:
```js
fetch("[Link]
.then((blob) => {
[Link] = url;
[Link](img);
});
```
---
195
### Error handling
Network errors (no response, DNS failure) reject the fetch promise. HTTP errors do not, so always
check `[Link]` or `[Link]` and throw accordingly. Errors thrown in `.then()` handlers or
within `async` functions propagate to the nearest `.catch()` or `try...catch` block.
```js
fetch('/long-request', { signal })
.catch(err => {
});
```
---
```js
196
"Content-Type": "application/json",
});
method: "POST",
headers,
});
```
---
The `cache` option influences how the browser interacts with its HTTP cache. Using `'no-store'`
ensures a fresh request; `'force-cache'` retrieves from cache even if expired. Service workers can
intercept and respond to fetch events, enabling offline support and advanced caching. You can also
use the [Cache Storage API]([Link] to
programmatically cache responses.
---
### Summary
- **Promises & async/await:** Fetch uses promises, making asynchronous code more readable than
XHR callbacks.
- **Flexible options:** You can set HTTP method, headers, body content, CORS mode, credentials,
cache behavior, redirects and abort signals via the options object.
197
- **Response handling:** Check `[Link]`/`[Link]`, then use `.json()`, `.text()`,
`.blob()`, `.arrayBuffer()`, or the streaming `[Link]` to consume the body.
- **Integration:** Fetch works in browsers and in modern [Link], and plays nicely with service
workers and CORS.
With this knowledge you should be able to perform most network interactions—GET, POST, file
uploads, streaming downloads, and more—without needing to refer to external resources.
`XMLHttpRequest` is the original API for AJAX (Asynchronous JavaScript and XML). It allows you to
send HTTP requests, track their progress and handle responses via event listeners. XHR can operate
in both synchronous and asynchronous modes, but synchronous requests block the main thread and
are deprecated because they freeze the UI. A typical XHR usage involves creating a new
`XMLHttpRequest`, calling `.open()`, attaching `onload`/`onerror` handlers and then calling `.send()`.
- **Syntax and promises** - `fetch()` returns a promise that resolves with a `Response` object. You
can chain `.then()` handlers or use `await` to process the response. XHR uses event callbacks (`load`,
`error`, `progress`) and does not return a promise. Promise-based code tends to be cleaner and
avoids "callback hell".
- **Error handling** - With `fetch`, network errors reject the promise, but HTTP errors (status codes
4xx/5xx) do **not**—you must check the `[Link]` property. XHR surfaces HTTP errors in its
`status` property; you manually check `[Link]` inside the `onload` handler.
- **Headers and bodies** - The Fetch API accepts an options object where you can set method,
headers, body and other settings. It supports Request/Response streams and easily handles JSON or
binary data. XHR also supports setting headers via `.setRequestHeader()`, but its API is less flexible
and does not handle streams.
- **Cancellation** - To abort a fetch request you create an `AbortController` and pass its signal to
`fetch()`. With XHR you call `.abort()` directly on the XHR instance. Fetch does not yet support
progress events (though they are proposed), while XHR emits `progress` events useful for displaying
upload/download progress.
- **Caching and service workers** - Fetch integrates with service workers and allows controlling
caching via the `cache` option. XHR has no built-in cache control; caching must be handled manually
or via browser heuristics.
198
- **Environment support** - XHR is built into browsers and has long been supported. It is not
natively available in older [Link] versions. Fetch is part of modern JavaScript; it works in browsers,
recent [Link] and Deno. Many existing [Link] libraries still use XHR for historical reasons, and
some features like progress events are still exclusive to XHR.
```js
// XMLHttpRequest example
function loadUser_XHR(id) {
[Link] = "json";
[Link] = () => {
};
[Link]();
});
// Fetch example
`[Link]
);
return [Link]();
199
}
loadUser_Fetch(1)
```
In the fetch version, the intent is clear: we request the resource, check `[Link]`, parse JSON and
return it. Error handling can be centralized with `try`/`catch`. The XHR version requires more
boilerplate to set up handlers and parse the response.
You can think of XHR as ordering food by calling a restaurant and waiting on hold while the operator
writes down your order. You must stay on the line and listen for status updates, and if you hang up
you have to start over. Fetch is like placing an order through a modern delivery app: you send your
order (a promise) and receive a notification when it's ready. You can cancel the order with a tap
(AbortController) and track its status in the app. Both achieve the same goal, but the user experience
is smoother with the newer tool.
1. **Theory:** Describe two advantages of the Fetch API over `XMLHttpRequest` and one feature
that XHR still offers that Fetch does not.
2. **Coding:** Write a function that retrieves a list of posts from an API using `fetch()`, parses the
JSON and handles HTTP errors gracefully. Then rewrite the same functionality using
`XMLHttpRequest` and compare the readability.
3. **Exploration:** Research how to cancel fetch requests using `AbortController` and implement a
button that aborts a pending request. Why is aborting important in single-page applications?
200
What is [Link] and [Link] and what are
their pitfalls
What is `[Link]` and `[Link]`, and what are their pitfalls?
---
## What is JSON?
**JSON (JavaScript Object Notation)** is just text: a string that uses a strict format to represent data
structures such as objects and arrays. Because it's just text, you can send it over the network or save
it to disk. JavaScript can then parse this text back into real objects.
```json
```
Notice that keys and string values are **always wrapped in double quotes**, and trailing commas
are not allowed.
---
The `[Link](text, reviver?)` function takes a JSON string (`text`) and converts it into a JavaScript
value. It also accepts an optional **reviver function** to transform values during parsing.
201
```js
[Link]([Link]); // "Alice"
[Link]([Link]); // 30
```
A reviver lets you customize how values are constructed. It receives each key and value, from the
deepest properties up to the root, and you return the final value to use. For example, to convert ISO
date strings into `Date` objects:
```js
? new Date(value)
: value;
});
```
- **Numeric precision:** JavaScript stores numbers as 64-bit floats. Very large integers (beyond 2⁵³)
can lose precision when parsed. MDN warns that numbers "may lose precision in the process". If you
need to safely transport big integers, serialize them as strings and convert them back (e.g., to
`BigInt`) after parsing.
202
- **Type revival:** JSON supports only objects, arrays, strings, numbers, booleans and `null`.
Complex types (`Date`, `Map`, custom classes) are parsed as plain objects. Use the reviver to
reconstruct them.
- **Invalid JSON throws:** `[Link]()` will throw a `SyntaxError` if the input string isn't valid
JSON—single quotes, comments or trailing commas aren't allowed. Always wrap parsing in
`try`/`catch` when dealing with user input.
- **Security:** Never parse untrusted JSON that includes executable code. JSON is data only; it
doesn't allow functions, so any function-like content indicates something is wrong.
---
`[Link](value, replacer?, space?)` converts a JavaScript value to a JSON string. It can take an
optional **replacer** (to filter or transform properties) and **space** (to pretty-print the output).
```js
[Link](text); // {"name":"Bob","age":25}
```
### Replacer
A replacer lets you control which properties are included or how they are transformed. It can be an
array of keys to include, or a function that processes each key/value.
```js
203
const publicData = [Link](person, ["name", "age"]);
[Link](publicData); // {"name":"Carol","age":28}
return value;
});
[Link](sanitized); // {"name":"Carol","age":28}
```
```js
const obj = { a: 1, b: { c: 2, d: 3 } };
[Link](pretty);
/*
"a": 1,
"b": {
"c": 2,
"d": 3
*/
```
Use `null` for the replacer if you don't need to filter anything.
204
### Pitfalls and caveats
- **Unsupported values:** JSON does not support `undefined`, functions or symbols. When
encountered as property values, they are **omitted**; in arrays they become `null`. For example:
```js
[Link]({
say() {
return "hi";
},
}); // "{}"
```
- **Special numbers:** `Infinity`, `-Infinity` and `NaN` are not valid JSON values; they are converted
to `null`.
- **Custom serialization:** If an object has a `toJSON()` method, `[Link]()` will call that
method to get a serializable representation. This lets you define how your objects are stringified.
- **Circular references:** If you attempt to stringify an object that references itself (directly or
indirectly), `[Link]()` will throw a `TypeError`. Use a custom replacer to handle circular
structures or use a library like `flatted`.
```js
const obj = {
205
big: BigInt("9007199254740993"), // > Number.MAX_SAFE_INTEGER
};
return value;
});
[Link](json);
// {"big":"9007199254740993","date":"2025-11-07T13:00:00.000Z"}
return value;
});
```
---
## Summary
206
| `[Link]()` | Converts a JSON string to a JavaScript value | Large numbers lose precision; only
supports basic types; invalid JSON throws; use reviver to handle dates and
BigInts |
| `[Link]()` | Converts a JavaScript value to a JSON string | Ignores `undefined`, functions and
symbols; `Infinity`/`NaN` become `null`; circular references throw; use replacer/`toJSON()` for custom
serialization |
By understanding the basic usage and pitfalls of `[Link]()` and `[Link]()`, you can
confidently serialize and deserialize data without surprises—even as a beginner.
```js
const obj = {
name: "Alice",
nested: {},
};
try {
[Link]([Link](obj));
} catch (err) {
return value;
});
207
[Link](safe);
return value;
});
```
The first attempt to stringify `obj` throws because of the circular reference. The custom replacer
removes the function and `undefined` values and converts the bigint to a string, allowing the
serialization to succeed. The reviver then restores the bigint when parsing.
Think of JSON serialization as packing items into a standardized shipping box. Only certain item types
(strings, numbers, booleans, `null`, arrays and plain objects) fit in the box. If you try to pack a live
plant (function), a piece of paper with no label (`undefined`) or something infinitely large (`Infinity`),
the packer will either throw the item away or mark its slot as empty. If an item contains a loop of
rope that attaches back to itself (circular reference), the packer doesn't know how to untangle it and
refuses to pack the box at all. You must convert unusual items into a supported form before shipping
and mark them carefully so that the receiver can reconstruct them.
1. **Theory:** Why does `[Link]({x: undefined, y: NaN})` produce `{"y":null}`? What happens
if you try to stringify a function?
2. **Coding:** Write a custom replacer and reviver to serialize and deserialize a `Map` object. How
can you preserve the key/value pairs?
3. **Troubleshooting:** Describe how you would detect and handle circular references when
stringifying a deep object graph. Compare using a replacer function versus using the built-in
`structuredClone()` method.
208
Explain module patterns in JS — ESM vs CommonJS
Explain module patterns in JS: ESM vs CommonJS
JavaScript uses modules to organize code into reusable pieces. Historically, [Link] adopted
CommonJS (CJS), while browsers and the ECMAScript standard now support ES modules (ESM). Each
pattern has its own syntax and characteristics.
CJS is the original module system for [Link]. Modules are loaded **synchronously** using
`require()`, and they export values via `[Link]` or `exports`. Because `require()` is just a
function, you can call it dynamically based on conditions. CJS modules run immediately when
required and their exports are cached in `[Link]`. According to the Node documentation, CJS
modules have access to variables like `__filename` and `__dirname` and can modify `[Link]`
to expose functionality.
Example:
```js
// [Link] (CommonJS)
function add(a, b) {
return a + b;
[Link] = { add };
// [Link]
[Link]([Link](2, 3));
```
209
ES modules are the official standard for JavaScript modules. They use the `import` and `export`
keywords and are loaded **asynchronously**. Because import statements are **static** (must be
at the top level), bundlers can analyze dependencies ahead of time and eliminate unused exports
(tree shaking). ESM does not expose CommonJS-specific features such as `require`, `[Link]`,
`__filename` or `__dirname`. LogRocket notes that ES modules are more readable and are the
standard moving forward, whereas CommonJS is primarily used for backward compatibility.
Example:
```js
// [Link]
return a + b;
// [Link]
[Link](add(2, 3));
```
- **Loading strategy** - CJS loads modules synchronously; code executes as soon as `require()` is
called. This works well in Node because modules are on disk, but it blocks execution during network
fetches in the browser. ESM loads modules asynchronously, returning a promise when using dynamic
`import()`. Static imports are hoisted and executed before other code.
- **Syntax and static analysis** - CJS uses `require()` and `[Link]`; ESM uses
`import`/`export`. Because ESM uses static declarations, bundlers can perform tree shaking to
remove unused exports. CJS allows imports anywhere, even conditionally, which makes static
analysis harder.
- **Interop** - Mixing CJS and ESM can be tricky. ESM default export maps to
`[Link]` in CJS. Node requires the file extension `.mjs` or a `"type": "module"` field
in `[Link]` to enable ESM.
- **Environment support** - CJS is built into Node and works in any version. ESM is supported in
modern browsers and in Node 12+ behind certain flags; older tools may still rely on CJS.
210
- **Tree shaking** - Bundlers like webpack can eliminate unused code in ESM because the structure
is static. This is harder with CJS because functions can be required dynamically.
For new projects targeting modern environments, ES modules are recommended because they are
the standard, allow static analysis and tree shaking, and work in both browser and Node with
minimal configuration. However, you may need CommonJS when using legacy Node modules or
building libraries that must support old tooling. Many projects provide dual builds (e.g., `[Link]`
and `[Link]`) to support both systems.
1. **Theory:** Describe two advantages of ES modules over CommonJS and one reason you might
still use CommonJS.
2. **Coding:** Convert a CommonJS module that exports multiple functions into an ES module using
named exports, and adjust its import statements.
3. **Exploration:** Research how to dynamically load modules using `import()` in ES modules and
how that compares to conditional `require()` calls. When might dynamic loading be useful?
211
What is tree shaking and dead code elimination
What is tree shaking and dead code elimination
Bundlers and compilers try to remove code that is never used. There are two related concepts:
**dead code elimination (DCE)** and **tree shaking**. DCE is a compiler optimization that discards
unreachable code; tree shaking is a technique applied specifically to JavaScript modules to include
only the parts of a dependency graph you actually use.
Dead code elimination removes unreachable or unused code after an entire file or bundle has been
generated. Traditional optimizers mark functions and variables that are never referenced and delete
them. However, because JavaScript allows dynamic behavior (e.g., using `eval` or property lookup by
string), a bundler cannot always detect unused code. DCE is limited to obvious cases.
Tree shaking is a form of dead code elimination tailored for ES modules. [Link] compares it to
pruning a dependency tree: bundlers analyze static `import` statements and only include the
exported members you actually import. Instead of starting with all code and removing dead pieces,
tree shaking builds your bundle from the top down, adding only "live" code that is referenced. It
works best when using ES modules because `import`/`export` syntax is static; CommonJS `require` is
dynamic and hampers static analysis. Named imports help bundlers know exactly which exports you
need.
Example:
```js
// [Link]
return a + b;
return a - b;
212
// [Link]
[Link](add(2, 3));
```
When bundling `[Link]`, a tree-shaking bundler includes only the `add` function and omits `subtract`,
reducing bundle size. If you import the entire module (`import * as math`), the bundler may include
both functions because it cannot know which properties will be used.
- Use ES module syntax (`import`/`export`) rather than CommonJS. Avoid dynamic `require`.
- Prefer named imports over namespace or default imports so bundlers know exactly what to
include.
- Avoid side effects (code that runs when the module is imported); mark pure modules with
`"sideEffects": false` in your [Link] so bundlers can safely remove unused files.
- Understand that tree shaking does not remove code executed for side effects; functions like polyfills
or global initialization will always be included.
1. **Theory:** Explain how tree shaking differs from traditional dead code elimination and why
static `import` statements are important.
2. **Coding:** Refactor a module that exports multiple functions so that unused functions can be
tree-shaken away when bundling. Test with a bundler like webpack or Rollup.
3. **Exploration:** Research how the `"sideEffects"` flag in `[Link]` affects tree shaking. Try
enabling/disabling it in a simple project and observe the bundle size.
213
What is a polyfill?
What is a polyfill
Web standards evolve, but not all browsers support new features immediately. A **polyfill** is a
piece of code that implements a feature on browsers that do not natively support it. MDN defines a
polyfill as a JavaScript implementation that provides modern functionality to older browsers.
Developers write polyfills to mimic APIs like `[Link]`, `[Link]` or CSS
features so that applications work across browsers.
Polyfills typically check whether a feature exists and, if not, define it. For example, to add
`[Link]` support in older browsers:
```js
if (![Link]) {
if (
){
return true;
return false;
};
```
When run in modern browsers, the `if` condition prevents overriding the native implementation.
When run in older browsers, it adds the method so that code using `includes` will work.
214
### Polyfills vs transpilers and shims
- **Transpilers** (e.g., Babel) convert modern syntax to older syntax (e.g., arrow functions to
traditional functions). They cannot add new APIs. Polyfills complement transpilers by providing
missing methods.
- **Shims** are similar to polyfills; some authors use the terms interchangeably, but "shim" often
refers to code that wraps existing APIs to provide a consistent interface.
- **Polyfills** mimic features with as close to spec-compliant behavior as possible but may have
performance limitations compared to native implementations.
Use polyfills when you need to support older browsers that lack specific features. Many libraries and
frameworks include polyfills automatically based on browser targets (e.g., core-js). Be careful not to
include unnecessary polyfills because they increase bundle size. Also, never polyfill features that
change global behavior (like `Promise`) if you cannot guarantee spec compliance; some
environments may already provide partial implementations.
2. **Coding:** Write a polyfill for the `[Link]()` method and test it on an array
of strings.
3. **Exploration:** Investigate how modern build tools like Babel and core-js work together to
provide polyfills based on targeted browsers. How can you reduce the number of polyfills included in
your bundle?
215
Explain memoization
Explain memoization
Many algorithms repeatedly compute the same subproblems (e.g., in recursion). Without
memoization, each call recomputes results, wasting time. Memoization saves results in a lookup
table so that repeated calls with the same arguments are instantaneous. This is especially useful for
functions with deterministic outputs (pure functions) where the same input always yields the same
output.
```js
function memoize(fn) {
if (key in cache) {
cache[key] = result;
return result;
};
216
// Example: memoized Fibonacci
function fib(n) {
if (n <= 1) return n;
[Link](memoizedFib(35)); // computed
[Link](memoizedFib(35)); // cached
```
The `memoize` helper returns a new function that checks the cache before invoking the original
function. The key is derived from the arguments so each input set gets its own cached result.
### Considerations
- Memoization is most effective for pure functions with no side effects. Functions that depend on
external state or cause side effects should not be memoized.
- Caching consumes memory; use strategies like size limits (LRU caches) or TTL (time-to-live) to avoid
excessive memory usage.
- Functions with complex arguments may need custom key generators instead of simple JSON
stringification.
2. **Coding:** Implement a memoized version of a function that computes factorials. Compare its
performance to a naive recursive version for large inputs.
3. **Exploration:** Research how libraries like Lodash's `_.memoize` implement memoization and
how you might customize the cache behavior.
217
What are generators and iterators
What are generators and iterators
Think of an **iterable** as something you can loop over, one item at a time. Arrays, strings, and
Maps are all iterables in JavaScript. What makes them iterable is that they implement a special
protocol: when you start iterating (for example, with `for...of`), JavaScript calls a method named
`[Link]` on the object to get an **iterator**.
An **iterator** is just an object with a `next()` method. Each call to `next()` returns an object with
two properties:
```js
const counter = {
current: 1,
last: 5,
[[Link]]() {
return {
next: () => {
},
};
},
};
218
// You can iterate with for...of:
[Link](num); // logs 1, 2, 3, 4, 5
```
When you call `for...of` on `counter`, JavaScript calls `counter[[Link]]()` to get the iterator. It
then repeatedly calls `next()`, pulling values until `done` becomes `true`. Because each value is
computed only when requested, iterators can represent very large or even infinite sequences
without storing everything in memory.
Writing custom iterator objects by hand can be verbose. **Generator functions**—declared with
`function*`—make this easier. When you call a generator function, it doesn't run immediately;
instead, it returns a **generator object**. This object is both iterable and an iterator. Inside the
generator, you use `yield` to produce values one at a time. Execution "pauses" at each `yield` and
resumes when `next()` is called again.
```js
function* countUpTo(max) {
[Link](n); // 1, 2, 3, 4, 5
```
219
Because `yield` pauses the function, generators naturally maintain state between iterations. They
also let you:
- **Send values back in**: When calling `next(value)`, the `value` becomes the result of the previous
`yield` expression. This can be used to modify the generator's internal logic.
- **Finish early**: Calling `return(value)` on a generator ends it early and returns `value` as the final
result.
- **Throw errors into the generator**: Calling `throw(error)` will cause the corresponding `yield`
expression to throw, letting you handle errors inside the generator.
```js
function* fibonacci() {
let a = 0,
b = 1;
while (true) {
yield a;
[Link]([Link]().value); // 0
[Link]([Link]().value); // 1
[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
```
Since the generator never finishes by itself, you must decide when to stop iterating—either by
breaking a loop after a few values or by calling `return()`.
220
### Summary for beginners
- An **iterable** is anything you can loop over with `for...of`. It must implement `[Link]`
and return an iterator.
- An **iterator** is an object with a `next()` method that returns `{ value, done }`. You control how
values are produced.
- **Generator functions** (`function*`) are a convenient way to create iterators. Each `yield`
produces a value and pauses the function until the next value is requested.
- Generators simplify stateful or infinite sequences and let you write cleaner asynchronous code
(when combined with `async` generators).
By understanding these concepts, you can build custom sequences on demand and handle complex
iteration patterns more cleanly.
- Implementing asynchronous flow control using async generators (`async function*`) and `for await
... of`.
1. **Theory:** Describe the difference between an iterable and an iterator. What methods must an
iterator implement?
2. **Coding:** Write a generator function that yields values from a nested array (e.g., `[1, [2, 3], 4]`)
in a single sequence.
3. **Exploration:** Research async generators and `for await...of`. How do they simplify working
with streams or asynchronous data sources?
221
Explain currying and partial application
Explain currying and partial application
Think of a multi-argument function like a machine with several input slots. **Currying** turns that
machine into a series of single-slot machines chained together. Each call fills one slot and returns a
new function that expects the next input. The original function only runs when all slots have been
filled.
In concrete terms, if you start with a function `f(a, b, c)`, a curried version would look like `f(a)(b)(c)`.
The function doesn't execute until you've provided all three arguments. That might seem strange at
first, but it allows you to create partially specialized versions of the function by "locking in" some of
the inputs.
```js
function sum(a, b, c) {
return a + b + c;
// A curried version
function currySum(a) {
return a + b + c;
};
};
222
[Link](add1and2(3)); // prints 6 (1+2+3)
```
A helper function can automate currying so you don't have to write nested functions by hand, but
the idea is the same: you gradually supply arguments until the function has enough to run.
---
**Partial application** is a looser technique: it lets you pre-fill **some** arguments of a function to
create a new function. Unlike curried functions, the partially applied function still expects all
remaining arguments at once. In the example you provided:
```js
function multiply(a, b, c) {
return a * b * c;
};
[Link](doubleAndTriple(4)); // 24 (2 * 3 * 4)
```
Here, `doubleAndTriple` is a new function where two of the original three arguments are pre-set, and
you only need to supply the last one. Partial application can also be done with built-in methods like
`[Link]()`, which fixes the `this` context and leading arguments.
223
---
- **Creating specialized functions**: Suppose you have a logging function `log(level, namespace,
message)`. You can curry or partially apply it to make a `debugLog = [Link](null, 'debug', 'MyApp')`
so you only need to supply the message.
- **Reusable configuration**: If a function accepts many configuration options, currying lets you
create pre-configured versions for different situations (e.g.,
`makeApiCall(baseURL)(endpoint)(params)`).
- **Readable callbacks**: Sometimes event handlers or array methods require a callback with a
single parameter. Currying can wrap a multi-argument function into the expected signature while
keeping the original logic intact.
For beginners, it helps to see currying and partial application as ways to reuse and adapt functions
without rewriting them. Currying breaks a function into a chain of one-argument steps, while partial
application "locks in" some arguments and returns a function that expects the rest. Both are
powerful techniques for writing flexible, declarative code once you get comfortable with passing
functions around.
- **Arity** - Currying always returns unary functions; partial application may return functions that
expect multiple arguments.
- **Execution** - Curried functions wait until called with all arguments; partial application returns a
new function that calls the original with a mix of fixed and new arguments.
- **Implementation** - Currying typically uses nested functions and may provide a flexible wrapper
that collects arguments until the original arity is met. Partial application uses `bind()` or a wrapper to
pass preset arguments.
- Creating specialized functions such as logging functions with preset date or level.
224
- Simplifying event handlers by pre-filling context.
1. **Theory:** Explain how currying transforms a two-argument function into a series of unary
functions. How does partial application differ?
2. **Coding:** Write a curry helper that converts any three-argument function into a curried
version. Test it with a function that concatenates three strings.
3. **Exploration:** Consider the `[Link]()` method. How can it be used for partial
application? What are the limitations compared to writing your own partial helper?
225
What is the Intl API and how is it used for localization?
What is the Intl API and how is it used for localization
Internationalizing applications involves presenting dates, numbers, lists and messages in formats that
users expect based on their locale. The **Intl** object is a namespace providing the ECMAScript
Internationalization API. It exposes constructors such as `[Link]`, `[Link]`,
`[Link]` and others. According to MDN, the Intl API offers language-sensitive string
comparison, number formatting and date/time formatting and provides standard ways to display
data in a user's locale.
Locales are strings like `'en-US'` (English, United States) or `'fr-FR'` (French, France). They may include
language, country and optional numbering system or calendar (e.g., `'de-DE-u-ca-gregory'`). The Intl
API uses the best available locale fallback when a specific locale is not fully supported.
`[Link]` formats dates and times according to locale. You can pass options to specify
styles (e.g., `'long'` or `'short'`):
```js
dateStyle: "long",
timeStyle: "short",
});
```
226
```js
style: "currency",
currency: "USD",
});
style: "currency",
currency: "EUR",
});
[Link]([Link](price)); // "$1,234.50"
```
- **ListFormat** formats lists using conjunctions appropriate to the locale (e.g., "A, B and C" vs "A, B
y C").
- **Collator** compares and sorts strings according to language-specific rules, useful for ordering
names or dictionary entries.
Using Intl helps avoid writing custom formatting logic and improves consistency across browsers. It is
not a translation library; it formats data, not text. For full localization you still need to translate
messages separately. Many frameworks integrate Intl, and [Link] includes Intl by default.
227
### Practice questions
1. **Theory:** What is the purpose of the Intl API? List three constructors provided by Intl and their
roles.
2. **Coding:** Format the number `12345.6789` as currency in Japanese yen and as a percentage in
German locale using `[Link]`.
228
Explain the repaint and reflow process in browsers
# Explain the repaint and reflow process in browsers
## Introduction
When you build a web page, the browser has to calculate how every element should
appear and then draw those pixels to the screen. **Reflow** and **repaint** are
reflows versus repaints helps you write more efficient code and avoid janky
(positions and sizes), while repaint changes only the **visual appearance**
## Detailed explanation
Reflow (also called **layout**) occurs when the browser recalculates the
or resizing the browser window—forces the browser to walk through the DOM
Reflows can be expensive because one element's layout often depends on its
parents and children. Adjusting a single element can ripple up and down the
modern engines optimize this process, large reflows can still block
229
Repaint (also called **render** or **redraw**) happens after the layout is
triggers a repaint. Repaints are generally cheaper than reflows because the
browser does not need to compute positions; it simply needs to fill new
1. **Batch DOM changes**: group multiple style or DOM changes together rather
230
browser to reflow synchronously. Cache values when possible.
4. **Reduce deep nesting**: complex DOM hierarchies require more work during
## Real-world analogy
furniture in a room—if you move a sofa, you might need to shuffle other pieces
walls or changing the curtains—nothing has moved, but the appearance has
changed. Painting the walls is quicker than moving furniture, but doing either
## Example
process:
```html
<style>
.box {
width: 100px;
height: 100px;
background: skyblue;
margin: 10px;
.moved {
margin-top: 100px;
} /* triggers reflow */
.recolored {
background: salmon;
231
} /* triggers repaint */
</style>
<button id="move">Move</button>
<script>
[Link]("color"). =>
[Link]("recolored");
</script>
```
Clicking "Move" adds or removes a class that changes the box's margin, causing
a reflow because the layout changes. Clicking "Change colour" only changes the
## Common misconceptions
- **"Repaints are free."** While cheaper than reflows, repaints still require
the browser to redraw pixels and can hurt performance if triggered rapidly.
- **"Only DOM changes trigger reflow."** Reading layout properties can also
force a reflow if the browser must flush pending changes to answer your
query.
- **"Reflows always block the UI."** Modern browsers perform some reflows
asynchronously or off the main thread, but large reflows can still cause
noticeable delays.
## Practice questions
232
1. What is the difference between repaint and reflow?
2. Name two actions that trigger a reflow and two that trigger only repaint.
performance?
233
What is garbage collection and how does mark-and-
sweep work?
# What is garbage collection and how does mark-and-sweep work?
## Introduction
In languages like C or C++, developers must manually allocate and free memory.
without worrying about leaks, but understanding how it works helps you write
collection.
1. **Mark phase**: Starting from root objects, the GC traverses references and
connected to a root.
2. **Sweep phase**: After marking, the GC scans through memory and frees any
234
object that was not marked. Unmarked objects are unreachable and their
This algorithm avoids freeing objects that are still in use. Modern
**incremental collection**:
- _Generational GC_ divides objects into "new" and "old" generations. Most
objects die young, so the collector scans the young generation frequently and
the old generation less often. This reduces overall work because short-lived
- _Incremental and idle-time GC_ break the mark-and-sweep work into smaller
chunks that run during idle moments, preventing long pauses that would
arrays, Maps, caches) prevents them from being collected. Clear entries
- **Detached DOM nodes**: Removing an element from the DOM does not free it if
your code still references it. Always nullify references to elements you
remove.
closures may keep them alive longer than necessary. Avoid capturing heavy
## Real-world analogy
235
Imagine your working desk as computer memory. You keep important documents
within reach (roots). Occasionally you clean your desk: you go through each
document, marking which ones you still need. Anything unmarked is thrown
away. To save time, you might check recent documents more often (generational
While you cannot manually force garbage collection in most environments, you
```js
function allocate() {
// allocate ~1 MB string
[Link](data);
setInterval(allocate, 1000);
setInterval(() => {
[Link](0, [Link]);
[Link]("Cache cleared");
}, 10000);
```
236
This code continuously allocates memory; clearing the cache allows the GC to
reclaim it. Tools like Chrome DevTools' Memory panel help identify leaks.
## Common misconceptions
the engine decides memory needs to be reclaimed. You cannot rely on exact
timing.
There may be delays; incremental GC might wait until the next idle period.
## Practice questions
237
Explain shadowing and variable masking
# Explain shadowing and variable masking
## Introduction
When a variable is defined in an inner scope with the same name as a variable in
an outer scope, the inner variable **shadows** the outer one. Within the
inner scope, references to that name refer to the inner variable, effectively
normal part of lexical scoping, but accidental shadowing can lead to bugs.
```js
function sayHi() {
sayHi();
```
Inside `sayHi`, the `greeting` declared with `let` hides the `greeting` in the
outer scope; the outer variable is still there but cannot be accessed until the
inner scope ends. Once `sayHi` finishes, the outer `greeting` is used again.
238
### Illegal shadowing
Not all shadowing is legal. Mixing `var` and `let`/`const` for the same
with the same name, trying to declare a `var` inside will throw a
shadowing.
- Avoid using `var` in modern code; prefer `let` and `const` to get
block-scoped variables.
## Real-world analogy
Imagine you work at an office with two break rooms (scopes). Both rooms have
a coffee machine labeled "Coffee." When you're in the inner break room,
pressing the "Coffee" button gives you the coffee from that room—not the
coffee from the outer break room. Once you leave the inner room, the outer
239
```js
function updateCount() {
// This inner count shadows the outer one. Did we mean to overwrite it?
updateCount();
```
which masks the outer `count`. However, JavaScript cannot initialize `count`
with its own value (`count + 1`), because at that point the inner `count` is
## Common misconceptions
variables unexpectedly.
## Practice questions
240
3. How can you avoid accidental shadowing in your code?
4. What happens in the code example above, and how would you fix it?
241
What is event propagation and stopPropagation?
# What is event propagation and `stopPropagation`?
## Introduction
there; it propagates through the DOM. Understanding this propagation helps you
decide where to attach event listeners and how to control event flow.
1. **Capturing (trickling)** - The event moves down from the root (`window` or
do not run during this phase unless `capture: true` is specified when
2. **Target** - The event reaches the target element and runs handlers attached
directly to it.
through ancestors, invoking handlers on each. This is the default phase for
Inside an event handler, `[Link]` refers to the element where the event
242
## Stopping propagation
Sometimes you need to prevent an event from reaching other listeners. Two
the DOM. Other handlers on the current element will still run.
Stopping propagation can be useful when you want to ensure a handler runs
overusing it can make your code harder to reason about. Prefer letting events
## Capturing listeners
To listen during the capturing phase, pass `{ capture: true }` as the third
```js
[Link](
"click",
(e) => {
[Link]("capturing at document");
},
{ capture: true }
);
243
[Link]("click", () => {
[Link]("bubbling at body");
});
// Click anywhere on the page to see the order: capturing runs first
```
## Real-world analogy
ground floor and call out someone's name, the sound travels up and down the
stairs. People on lower floors hear it first (capturing), then the person you
want hears it (target), then people on higher floors hear echoes as the sound
bubbles back up. If someone interrupts and stops the message (calls
## Common misconceptions
`[Link]()`.
## Practice questions
244
`[Link]()`?
245
What is Symbol in JavaScript?
# What is a `Symbol` in JavaScript?
## Introduction
Symbols are a primitive data type introduced in ES6. They were added to
provide unique, non-string keys for object properties. Each symbol value is
## Key characteristics
```js
const s1 = Symbol("id");
const s2 = Symbol("id");
```
symbol with the given key exists, it returns it; otherwise, it creates one.
246
object) and `[Link]` (customizes `instanceof`).
```js
const user = {
name: "Alice",
};
[Link]([Link]); // "Alice"
[Link](user[secret]); // 12345
```
The `secret` property is not visible with typical enumeration methods. It can
```js
[Link]([Link](uid1)); // 'uid'
```
your code base; it stores the symbol in the global symbol registry.
247
## Real-world analogy
office drawers. You can write any label (string) on a folder, but two people
might choose the same label and accidentally put their files together. Symbols
are like using a unique, secret sticker that only you know. Even if others
describe the sticker the same way, their stickers will be different and
## Common misconceptions
through normal property iteration, but any code that holds a reference to the
- **"Symbols replace strings for all keys."** Symbols are useful for
## Practice questions
enumeration?
248
What is WeakMap and WeakSet?
# What is `WeakMap` and `WeakSet`?
## Introduction
Unlike regular `Map` and `Set`, they provide _weak references_ to their
contents, which helps avoid memory leaks when associating data with objects.
## WeakMap
they did, iterating over keys would reveal when garbage collection happens,
```js
function getData(obj) {
249
[Link](obj, data);
return data;
// at some point later, the key and its associated data will be collected
```
## WeakSet
Each value may appear only once, and like WeakMap keys, values are held
iteration methods and a `size` property for the same reason: values can
```js
function process(node) {
if ([Link](node)) {
250
[Link](node);
```
## Real-world analogy
Imagine renting lockers (objects) at a gym. You keep a notebook to track which
locker holds which customer's clothes. If a customer leaves and clears out
their locker (no other references), the locker and its entry in your notebook
are freed automatically. You don't maintain a master list of all lockers in
use because people leave at different times. WeakMap and WeakSet behave
similarly: they store data associated with objects, but entries vanish when
## Common misconceptions
- **"WeakMap keeps objects alive."** The key's reference is weak; it does not
- **"WeakMap can have string keys."** Only objects and non-registered symbols
## Practice questions
251
3. Describe a scenario where a WeakMap is preferable to a regular Map.
removed?
252
What are Map and Set and how do they differ from
objects?
# What are `Map` and `Set`, and how do they differ from objects?
## Introduction
traditional objects and arrays. They offer more flexible key and value
how they differ from plain objects helps you choose the right data structure.
## `Map`
object, number, string, boolean, symbol, even `NaN`—and the value can also be
any type. Maps preserve insertion order and provide built-in methods to
- **Arbitrary key types**: keys retain their type and are compared using the
- **Insertion order**: when iterating, entries are returned in the order they
were inserted.
`[Link]()`, `[Link]()`.
- **Object keys**: maps allow using objects as keys without converting them to
253
strings.
### Example
```js
[Link]("a", 1);
[Link](42, "answer");
[Link]([Link]("a")); // 1
[Link]([Link](42)); // 'answer'
[Link]([Link]); // 3
[Link](key, value);
```
## `Set`
preserves insertion order and provides methods to manipulate its contents. The
main idea is that a value can appear only once; repeated calls to `[Link](value)`
have no effect.
254
- **Any value type**: numbers, strings, objects, etc., can be added.
### Example
```js
[Link]("apple");
[Link]("banana");
[Link]([Link]); // 2
[Link]([Link]("banana")); // true
```
- Use **`Map`** when you need keys of any type, want predictable iteration order,
255
or need a large dictionary with frequent insertions/deletions.
- Use **`Set`** when you need a collection of unique values, such as tracking
- Use **plain objects** for simple key/value pairs where keys are known and
syntax.
## Practice questions
2. How does a `Set` ensure that each value is stored only once?
256
Explain shallow copy vs deep copy
# Explain shallow copy vs deep copy
## Introduction
properties; nested objects or arrays are shared between the source and the copy.
## Shallow copy
A shallow copy produces a new object whose properties point to the same values
the copy has its own copy of the primitive. If they are objects or arrays,
both objects reference the same nested object. Mutating nested data affects
### Example
```js
const original = {
name: "Alice",
};
257
[Link] = "Bob"; // affects only the copy
[Link]([Link]); // 'Tampa'
```
**shallow copies**.
Shallow copies are efficient for flat objects (no nested references) or when
you intentionally want the copy to share nested structures with the original.
objects is safe.
## Deep copy
A deep copy duplicates everything recursively, so that the new object shares no
references with the original. Modifying the copy does not affect the
258
`[Link]([Link](obj))`. This method fails for functions,
supports many types and cyclic references; see the next topic for more details.
```js
[Link](4);
[Link](2030);
```
## Real-world analogy
where all attachments (post-it notes) remain stuck on the original. Both the
attachment affects both. A **deep copy** is like making a copy and reprinting
all attachments separately; the new document has its own independent notes.
## Common misconceptions
- **"Spread syntax always makes a deep copy."** It only copies the first
259
- **"Deep copies are always better."** They are more expensive to create and
may not be needed for flat structures. Use shallow copies when you don't
## Practice questions
2. Name two methods to perform a deep copy. What are their limitations?
3. Why might you choose a shallow copy over a deep copy in certain situations?
260
What is structuredClone?
# What is `structuredClone`?
## Introduction
for functions, dates, maps, sets, typed arrays, or cyclic structures. The
plain objects, arrays, typed arrays, Maps, Sets, Dates, RegExps and more.
### Syntax
```js
```
261
### Return value and exceptions
If any part of the input contains unserializable data (like DOM nodes,
## Examples
```js
copy.b.c = 42;
[Link](5);
```
```js
[Link]([Link]); // 0
[Link]([Link]); // 8
```
262
## Real-world analogy
like using a special copying tool that not only handles simple documents but
also copies entire folders, compressed files and even broken links—everything
of copying it, the original folder disappears and only the new one remains.
## Common misconceptions
cannot clone functions, dates, maps, sets or typed arrays, and it fails on
- **"It can clone any JavaScript value."** Some types (DOM nodes, functions,
`DataCloneError`.
## Practice questions
2. What types can be transferred rather than cloned using the `transfer` option?
resource?
263
What are Web Workers and when should you use
them?
# What are web workers and when should you use them?
## Introduction
handles user input, renders the page and executes your code. If your code
performs a heavy computation, the browser cannot respond to user input until
new thread running the specified script. Workers have their own global
context (similar to a separate window), cannot directly access the DOM and
communicate with the main thread via message passing. Workers run scripts in background threads
and can perform tasks without
send messages to the main thread and receive messages back using
```js
// [Link]
264
[Link] = (e) => {
};
// [Link]();
```
**Worker script**
```js
// [Link]
[Link](result);
};
function fib(n) {
```
Let's walk through the interaction step by step to see what each line does and when each handler is
invoked.
265
### 1. Creating the worker
```js
```
On the main thread (the page), this line spawns a new background thread and instructs it to run the
code contained in `[Link]`. A worker has its own global scope—separate from the main thread—
and cannot access the DOM or use most of the `window` object. The `Worker` constructor returns a
`Worker` instance that the main thread can use to communicate with this background thread.
```js
};
```
The `onmessage` property of the `Worker` instance is an event handler for the `"message"` event. It
is called whenever the worker thread sends a message back to the main thread. The handler receives
an event object (`e`), whose `data` property contains whatever data the worker posted. At this point,
nothing is called yet; you're just registering a callback so you can handle responses in the future.
```js
```
Here, the main thread sends a message to the worker. It uses `postMessage()`, which serializes the
given data and delivers it to the worker thread. Because workers communicate via message passing,
data is copied rather than shared. In this example, the main thread sends an object with a `type` of
`"start"` and a `value` of `40`, instructing the worker to begin a computation.
266
### 4. Handling the message in the worker
```js
[Link](result);
};
```
The `self` keyword inside the worker refers to the worker's global scope. When the main thread calls
`postMessage()`, the worker's `onmessage` handler fires, receiving the data in `[Link]`. The worker
checks the message type; if it is `"start"`, it calls the `fib()` function to compute the 40th Fibonacci
number. After finishing, it calls `[Link](result)` to send the result back to the main thread.
Once the worker posts the result, the browser delivers it to the main thread and triggers the handler
you assigned earlier:
```js
};
```
At this point, `[Link]` contains the Fibonacci number computed in the worker. The callback logs the
value. Any subsequent messages from the worker will also trigger this handler.
267
### 6. Cleaning up
```js
// [Link]();
```
Calling `terminate()` immediately stops the worker's thread and frees its resources.
Putting it all together: the main thread creates a worker, sets up a response handler (`onmessage`),
and sends a message using `postMessage()`. The worker receives that message in its own
`onmessage` handler, performs the computation, and sends the result back using `postMessage()`.
The main thread then receives the result via its `onmessage` callback. Because the heavy
computation runs in a separate thread, the user interface remains responsive throughout.
## Types of workers
to a single script. Only the thread that created it can communicate with it.
running in different windows or tabs, provided they are from the same origin.
enabling offline caching and background sync. Service workers are not
directly used for computational tasks, but they share some worker
characteristics.
Use web workers when you need to perform CPU-intensive or blocking operations
268
- **Data processing**: Sorting large arrays, parsing big JSON files, doing
with heavy processing (like decompressing large files) benefits from a worker.
Avoid using a worker for simple tasks or frequent updates that would incur
## Real-world analogy
Think of a web worker as a personal assistant. While you (the main thread)
interact with users and handle immediate tasks, your assistant can work on a
but you don't look over each other's shoulders. If you want the report to
stop, you tell your assistant to stop working (terminate the worker).
## Common misconceptions
- **"Web workers can access the DOM."** They cannot. Only the main thread can
- **"Workers share memory with the main thread."** They communicate by copying
## Practice questions
269
2. How do the main thread and a worker communicate?
270
What are Service Workers and PWA concepts?
# What Are Service Workers and PWA Concepts?
Progressive Web Apps (PWAs) bridge the gap between traditional web pages and native applications.
They use modern browser APIs to deliver reliable, installable, offline-capable experiences. At the
heart of a PWA is the **service worker**, a script that runs separately from the main page and
controls how the app interacts with the network.
A service worker is a background script registered by your application. Once installed and activated,
it sits between your app and the network, intercepting network requests and deciding how to
respond. Because it runs independently of any web page, it can respond to events even when your
site is not open. Service workers can:
- **Cache assets and data** so your app can work offline or with poor connectivity.
- **Serve cached responses** immediately while fetching updated data in the background.
Service workers have no direct access to the DOM. They communicate with pages via the
`postMessage` API and must be served over HTTPS for security.
### Lifecycle
2. **Installation** - The service worker downloads and runs the installation code. You typically
pre-cache core assets in the `install` event so they are available offline.
3. **Activation** - After installation, the service worker activates. It clears old caches and takes
control of pages within its scope. New versions wait to activate until all pages using the old version
are closed, so updates don't disrupt the current users.
271
4. **Idle/Fetch** - Once active, the worker listens for events such as `fetch`, `push` and `sync`. It
decides how to respond to network requests—either from the cache, network or a combination.
Only one service worker can control a given scope (directory path) at a time. When you update your
worker script, the browser downloads the new version, installs it in the background and waits to
activate until the old version has no more clients.
Here's a minimal example that installs a service worker and caches an asset:
```js
if ("serviceWorker" in navigator) {
[Link]("load", () => {
[Link]("/[Link]").catch((err) => {
});
});
[Link](
);
});
272
[Link]("activate", (event) => {
[Link](
caches
.keys()
.then((keys) =>
[Link](
keys
);
});
[Link](
[Link]([Link]).then((cached) => {
return (
cached ||
fetch([Link]).then((response) => {
[Link]([Link], [Link]());
return response;
});
})
);
})
);
273
});
```
In the example, the service worker caches static assets during installation and serves them from the
cache when offline. It also updates the cache whenever a fresh version is downloaded.
1. **Web App Manifest** - A JSON file (`[Link]`) describing your app's name, icons,
theme colors and how it should appear when installed on a user's home screen. It allows users to
add your PWA to their device.
2. **HTTPS** - PWAs require secure contexts. Browsers block service worker registration on insecure
origins to prevent man-in-the-middle attacks.
3. **Responsive design** - PWAs should adapt to different screen sizes and device capabilities.
4. **Installability** - When a PWA meets criteria (served over HTTPS, has a manifest, registered
service worker and user engagement), the browser prompts users to install the app.
5. **Offline and connectivity independence** - Caching and local data storage enable the app to
function even when the network is unavailable.
Imagine a restaurant that uses a **waiter** to handle orders. Instead of every customer shouting
orders to the kitchen (the network), the waiter (the service worker) intercepts orders, writes them
down, and checks if some meals are already prepared (cached). If the meal is ready, the waiter
serves it immediately. Otherwise, the waiter sends the order to the kitchen and serves it when it's
done. If the restaurant closes (the user goes offline), the waiter can still serve meals that were
prepared earlier.
- **Scope placement**: Place your service worker file at the top of the directory you want it to
control. A worker registered at `/[Link]` controls the entire site, while `/blog/[Link]` only controls files
under `/blog`.
- **Updates not activating**: New versions wait until all pages using the old worker are closed. Use
`[Link]()` in the `install` event and `[Link]()` in the `activate` event to take control
immediately, but beware that this can refresh pages unexpectedly.
274
- **Caching too much**: Cache only assets that are necessary for offline use. Uncontrolled caching
can fill storage or serve stale content.
- **Offline fallbacks**: Provide fallback pages or messages when resources cannot be fetched.
## Practice questions
**Conceptual questions**
1. What is a service worker and how does it differ from a regular web worker?
2. Describe the lifecycle of a service worker. Why doesn't a new service worker take control
immediately after installation?
3. How does a Progressive Web App benefit from a service worker? List at least three capabilities.
**Coding exercises**
1. Write a script to register a service worker and log whether registration succeeded or failed.
2. Modify the example service worker so that it serves a fallback HTML page (`[Link]`) when the
requested page is not available offline and the network is down.
3. Implement a caching strategy where the service worker always fetches resources from the
network first and falls back to the cache if the network request fails.
4. Add code to your service worker to display a push notification when receiving a `push` event. How
would you handle the user clicking on the notification?
275
Explain [Link], [Link], and
[Link]
# Explaining `[Link]()`, `[Link]()`, and `[Link]()`
Modern JavaScript uses promises to represent asynchronous operations such as network requests,
timers or file reads. When you have **multiple promises** that should run in parallel, the language
provides helper methods to orchestrate them. The three most common helpers—`[Link]()`,
`[Link]()`, and `[Link]()`—behave differently when resolving or rejecting a group
of promises. Understanding these differences helps you choose the right tool for your scenario.
## `[Link]()`
`[Link]()` accepts an iterable (usually an array) of promises and returns a **new promise**.
This returned promise fulfills when **every** input promise fulfills. If **any** input promise
rejects, the returned promise rejects immediately with that reason. The order of results corresponds
to the order of the input promises, not the order in which they resolved.
Use `[Link]()` when you need all results to continue—for example, fetching user details,
preferences and settings before rendering a dashboard. It runs all promises concurrently and
aggregates their results.
```js
[Link](),
[Link](),
[Link](),
276
]);
})
.catch((err) => {
});
```
- The returned promise resolves to an **array of results** in the same order as the input promises.
- If any promise rejects, the entire operation fails immediately. This is useful when you cannot
proceed without all results.
- The original promises continue running even if one rejects—you cannot cancel them with
`[Link]()` alone.
## `[Link]()`
`[Link]()` takes an iterable of promises and returns a promise that settles (fulfills or rejects) as
soon as **the first input promise settles**. The returned promise adopts the value or reason of the
first settled promise.
Use `[Link]()` when you want to proceed with whichever promise finishes first, regardless of
success or failure. Common patterns include implementing timeouts or selecting the fastest source.
```js
return [Link]([
promise,
277
setTimeout(() => reject(new Error("Operation timed out")), ms)
),
]);
withTimeout(fetch("/api/data"), 3000)
```
- Other promises keep running in the background; use cancellation mechanisms like
`AbortController` to abort network requests if needed.
- Suitable for implementing **fallbacks**, such as requesting data from multiple mirrors and using
the first response.
## `[Link]()`
`[Link]()` returns a promise that fulfills **after all input promises have settled**,
regardless of whether they fulfilled or rejected. The result is an array of objects describing the
outcome of each promise. Each object has a `status` property (`'fulfilled'` or `'rejected'`) and either a
`value` or a `reason` property.
Use `[Link]()` when you want to wait for **all promises to finish** but don't want one
failure to short-circuit the rest. For example, you might want to display partial results while noting
which requests failed.
```js
278
[Link](fetchPromises).then((results) => {
} else {
});
});
```
- Always resolves, never rejects. You handle successes and failures separately by inspecting each
result.
- The order of the results matches the order of the input promises.
- Useful for parallel operations where failures are acceptable or expected (e.g., loading optional
resources).
| Scenario | Use |
| ----------------------------------- | ---------------------- |
279
- **`[Link]()`** is like waiting for all your suppliers to deliver before you can start assembling. If
any supplier fails to deliver, your project stalls.
- **`[Link]()`** is like taking the first quote that arrives. You proceed with whichever supplier
responds first.
- **`[Link]()`** is like checking in at the end of the day to see which suppliers delivered
and which didn't. You then decide what to do with the partial orders.
## Practice questions
**Conceptual questions**
1. Explain the difference between `[Link]()` and `[Link]()`. When would you choose
one over the other?
2. In `[Link]()`, what happens if the first promise rejects? How can you handle this case
gracefully?
3. Why does `[Link]()` reject as soon as any promise rejects? How could you modify your code
to collect all errors instead of failing fast?
4. Describe a real use case for each of the three methods discussed.
**Coding exercises**
1. Write a function `loadAll(urls)` that takes an array of URLs, fetches them concurrently and returns
an array of response bodies using `[Link]()`. It should reject if any fetch fails.
2. Implement a helper `firstResolved(promises)` that returns the value of the first fulfilled promise
and ignores any rejections. Use `[Link]()` along with additional logic to skip rejected promises.
3. Write a function that fetches multiple resources and logs which succeeded and which failed using
`[Link]()`. Then modify it to retry failed requests once.
280
What is BigInt in JavaScript?
# What Is `BigInt` in JavaScript?
JavaScript's `number` type is a 64-bit double-precision floating-point value. This format can exactly
represent integers up to 2⁵³ - 1 (9,007,199,254,740,991). Beyond this range, integer arithmetic loses
precision, resulting in rounding errors. To solve this problem, ECMAScript introduced the **BigInt**
type.
## Introducing BigInt
`BigInt` is a built-in primitive for representing **whole numbers of arbitrary size**. Unlike regular
numbers, BigInt values can grow as large as memory allows without losing precision. You create a
BigInt by either appending an `n` to an integer literal or by calling the `BigInt()` constructor.
```js
```
A BigInt literal cannot contain a decimal point or exponent; BigInt values always represent integer
quantities. Internally, BigInts use an arbitrary-precision representation separate from the IEEE-754
format used by `number`.
Most arithmetic operations (`+`, `-`, `*`, `/`, `%`, `**`) work with BigInts, but **you cannot mix BigInt
and `number` in a single operation**. Doing so throws a `TypeError` to prevent implicit coercion and
precision loss. Always convert between types explicitly:
```js
const n = 42;
281
const big = 10n;
// Invalid: TypeError
// [Link](n + big);
```
Division with `/` returns a truncated result (any fractional part is discarded), because BigInt
represents only whole numbers. Bitwise operators except `>>>` (unsigned right shift) work as well.
- **No mixing with regular numbers**: BigInts and numbers don't implicitly convert. Always cast
explicitly.
- **Math library is unsupported**: `Math` methods (`[Link]`, `[Link]`, etc.) do not accept
BigInts. Use third-party libraries for advanced operations.
- **JSON serialization**: `[Link]()` throws when encountering a BigInt because JSON doesn't
have a BigInt type. Convert BigInts to strings before serialization.
- **Inconsistent API support**: Some browser APIs accept only numbers. Check whether BigInt is
supported before using it.
282
- **Counters** or **IDs** that can exceed the range of 64-bit numbers (e.g., blockchain block
numbers).
However, BigInts cannot represent decimal fractions. For currency calculations involving cents,
consider using a decimal library or storing amounts as integers of the smallest unit (e.g., cents) with
BigInt.
Think of `number` as a **typical calculator**—it has a limited number of digits it can display. Once
you exceed those digits, it starts rounding. BigInt is like a **scientific calculator** with expandable
memory. It may be slower, but it allows you to keep adding digits without losing any.
**Conceptual questions**
1. What problem does BigInt solve that the regular `number` type cannot? Give an example where a
`number` loses precision.
2. How do you create a BigInt literal? Why can't BigInt values have decimals?
3. Why does JavaScript throw a `TypeError` when you try to add a `number` to a BigInt? How can you
perform such an addition correctly?
4. List at least three use cases where BigInt is a better choice than `number`.
5. What happens when you divide one BigInt by another? Explain why fractional results are handled
the way they are.
**Coding exercises**
1. Implement a function `factorialBig(n)` that returns the factorial of a non-negative integer `n` using
BigInt. For example, `factorialBig(20)` should return `2432902008176640000n`.
2. Write a function that sums a list of numbers and BigInt values. It should return a BigInt and handle
type conversions appropriately.
3. Create a function `compareBig(a, b)` that accepts two numbers or BigInts (or a mix) and returns `-
1`, `0` or `1` depending on whether `a` is less than, equal to, or greater than `b`.
283
4. Modify `[Link]()` to serialize objects containing BigInts by converting them to strings. Write
a helper `stringifyWithBigInt()` that replaces BigInts with their string representation before
serialization.
284
Explain dynamic imports and code splitting
# Dynamic Imports and Code Splitting in JavaScript
As applications grow, bundling all of your code into one large file slows down initial page loads. To
improve performance, modern tools let you **split code into smaller chunks** that load only when
needed. JavaScript's dynamic `import()` function plays a central role in this strategy.
```js
```
Static imports must appear at the top level of your file and are resolved during compilation. All
dependencies get bundled into the initial script. In contrast, **dynamic imports** are functions that
return a promise and can be called at runtime:
```js
[Link]([Link](2, 3));
[Link]("click", onCalculate);
```
With dynamic imports, you can load modules on demand, such as when a user clicks a button or
navigates to a new route. This reduces the upfront cost of downloading code that might never be
used.
285
## Code splitting
**Code splitting** is the practice of dividing your application code into separate bundles that can be
loaded independently. When using bundlers like webpack, Rollup or Parcel, dynamic `import()` calls
signal to the bundler that a new chunk should be created. Each chunk contains only the code
necessary for that part of the application.
- **Faster initial load** - Users download only the core functionality needed to render the first
screen.
- **Lazy loading** - Additional features (e.g., an admin panel or charting library) load only when the
user triggers them.
Many bundlers let you assign custom names to chunks for easier debugging and caching. In webpack,
you can specify a `webpackChunkName` comment:
```js
import(
/* webpackChunkName: "chart" */
"./components/[Link]"
).then((module) => {
new Chart();
});
```
The bundler generates a file like `[Link]` that is loaded only when the import is executed.
286
In single-page applications (SPAs), it's common to split code by route. Each route's component and its
dependencies are put into a separate chunk. When the user navigates to a route, the framework
dynamically imports the component and displays it. For example, in React with [Link]:
```jsx
function App() {
return (
<Router>
<Route
path="/admin"
element={
<AdminPage />
</Suspense>
/>
</Router>
);
```
Here the `AdminPage` component is bundled separately. It is fetched when the user navigates to
`/admin` and rendered inside a fallback UI while loading.
## Common pitfalls
287
- **Dynamic imports return a promise** - Always handle them asynchronously. If you forget to
await or use `.then()`, your code may try to access undefined exports.
- **CORS restrictions** - When loading modules from other domains, ensure the server sets the
appropriate CORS headers. Browsers enforce same-origin policies for module scripts.
- **Multiple imports** - Calling dynamic `import()` multiple times for the same module may return
cached copies, but bundlers can generate duplicate chunks if configured incorrectly.
- **Not prefetching** - For anticipated interactions (like the next page), use `<link rel="prefetch">`
or bundler features to prefetch chunks during idle time.
## Analogy
Imagine your application as a toolkit. **Static imports** pack all tools into a single heavy toolbox.
You might carry tools you never use. **Dynamic imports** let you keep seldom-used tools on a shelf
and grab them only when needed, making the initial load lighter and more efficient.
## Practice questions
**Conceptual questions**
1. What is the difference between static `import` and dynamic `import()`? Why can dynamic imports
be placed inside functions or conditional blocks?
2. How does code splitting improve page performance? Describe scenarios where splitting code by
route or component makes sense.
3. Explain how bundlers like webpack use dynamic imports to create separate chunks. What happens
when multiple dynamic imports refer to the same module?
**Coding exercises**
1. Rewrite a simple module `[Link]` that exports a function. Then write code that uses dynamic
`import()` to load and execute `greet()` only after the user clicks a button.
2. Using webpack (or another bundler of your choice), configure a project to split code into separate
bundles when certain routes are visited. Verify that the generated chunks are loaded on demand.
3. Write a helper `lazyLoad(path)` that wraps dynamic `import()` and caches the loaded module,
ensuring that subsequent calls don't fetch it again.
288
4. Implement a fallback UI that displays a spinner while a dynamically imported component is
loading. Once loaded, render the component.
289
What are optional chaining and nullish coalescing
operators?
# Optional Chaining (`?.`) and Nullish Coalescing (`??`) Operators
JavaScript applications often work with deeply nested objects. Accessing a property somewhere
down the chain can throw an error if an intermediate property is undefined. To simplify safe access
and default values, the language introduced two operators: **optional chaining** and **nullish
coalescing**.
The optional chaining operator (`?.`) allows you to safely access properties, call functions or index
arrays on a value that might be `null` or `undefined`. If any part of the chain is `null` or `undefined`,
the entire expression short-circuits and returns `undefined` instead of throwing a `TypeError`.
```js
```
```js
```
Both expressions return the same result—`undefined` if `user` or `[Link]` is missing—but the
latter is cleaner and less error-prone. Optional chaining can be used for:
290
- **Array/Map access**: `arr?.[index]`
Each `?.` checks the part before it. If that part is `null` or `undefined`, the entire expression returns
`undefined` and stops evaluating. Optional chaining does not catch other falsy values (such as `0` or
`''`).
When calling a function that might not exist, optional chaining prevents errors:
```js
[Link] = null;
// Later ...
```
In this case, if `onclick` is `null`, the call is skipped. Optional chaining only short-circuits the
immediate member access; side effects on the left side (e.g., increment operators) still happen
before the check.
Optional chaining tells you something is missing but doesn't fill the gap. If a value is required, you
must still validate it and handle the missing case appropriately.
The nullish coalescing operator (`??`) returns its right-hand operand when the left-hand operand is
`null` or `undefined`; otherwise it returns the left-hand operand. It's useful for providing default
values only when a value is truly absent, not when it is another falsy value.
```js
291
const name = [Link] ?? "Anonymous";
```
```js
```
## Analogy
Imagine navigating through a series of doors in a building. Optional chaining is like checking whether
each door exists before you walk through it. If a door is missing, you stop instead of walking into an
error. Nullish coalescing is like saying "If there is no room here (null or undefined), use this backup
room; otherwise, use the room you found."
## Practice questions
**Conceptual questions**
1. What problem does the optional chaining operator solve? Give an example where it prevents a
run-time error.
2. How does `obj?.prop` differ from `[Link]` in terms of short-circuiting behaviour? What happens
if `obj` is `null` or `undefined`?
292
3. Compare the nullish coalescing operator (`??`) with the logical OR operator (`||`). What values
cause each to use the default?
4. Can you use optional chaining with function calls and array indexing? Provide syntax examples.
**Coding exercises**
3. Write a function that takes an array of user objects and returns the first defined `email` property
using optional chaining and nullish coalescing, or returns `'no email'` if none exist.
4. Demonstrate how misuse of `||` instead of `??` could accidentally treat an empty string as missing.
Rewrite the example correctly.
293
Explain Proxy and Reflect API
# Proxy and Reflect APIs in JavaScript
A **proxy** is created with `new Proxy(target, handler)`. The `target` is the object being wrapped,
and the `handler` is an object whose properties are functions (called _traps_) that intercept
operations on the target. When an operation occurs, the corresponding trap executes; you can run
custom logic, block the operation, or forward it to the original target using Reflect.
```js
const handler = {
},
},
294
};
```
In this example, the `get` trap logs property reads and then uses `[Link]()` to perform the default
behaviour. The `set` trap validates the age property before assigning it.
- `set(target, prop, value, receiver)` - intercepts property writes; return `true` if successful.
- `construct(target, args, newTarget)` - traps object instantiation when the proxy is used with `new`.
Each trap can modify behaviour or delegate to the original using `Reflect` methods.
- **Logging and debugging** - Track when and how properties are accessed or modified.
- **Virtualized collections** - Represent large or remote datasets and fetch data lazily when
properties are accessed.
- **Reactive frameworks** - Libraries like Vue use proxies to detect changes and trigger UI updates.
295
## Reflect: default behaviour as functions
When writing proxy traps, using Reflect ensures your proxy behaves consistently with the language's
default semantics.
## Analogy
Think of a proxy as a **security guard** standing in front of a building. Every time someone tries to
go inside (access a property), the guard checks their credentials, logs their entry or perhaps stops
them. The building itself is the target object. The Reflect API is like the building's default operation
manual—when the guard decides to allow someone in, they follow the manual to open the door and
let them proceed normally.
## Practice questions
**Conceptual questions**
1. What is the purpose of the handler object in a Proxy? List at least four traps and explain when they
are invoked.
296
2. How does a proxy differ from a normal object when performing operations like property access,
assignment or function invocation?
3. Why is the Reflect API useful inside proxy traps? What advantages does it provide over directly
interacting with the target?
4. Give an example of how proxies can be used to implement data validation or default values.
**Coding exercises**
1. Create a proxy for an object that logs any attempt to read or write its properties and prevents the
deletion of any property.
2. Write a proxy that enforces that only string keys starting with an underscore (`_`) can be set. All
others should throw an error.
3. Implement a proxy for an array that returns `0` whenever an out-of-bounds index is read. Use
Reflect to delegate all other operations.
4. Build a proxy that records the time of every method call on an object and stores it in an array
called `calls` on the target.
297
What is destructuring / aliasing and how is it useful?
# Destructuring, Aliasing and Their Usefulness in JavaScript
Modern JavaScript includes syntax for **destructuring**—extracting values from arrays and objects
into distinct variables. It helps unpack complex data structures into convenient local variables with
concise syntax. **Aliasing** within destructuring lets you rename properties to avoid naming
conflicts or to choose clearer variable names.
## Array destructuring
```js
[Link](red); // 255
[Link](green); // 200
[Link](blue); // 100
```
You can skip elements by leaving a blank space (`, ,`) and provide default values if the array is shorter
than expected:
```js
const [x = 0, y = 0, z = 0] = coords;
// x = 10, y = 0, z = 0
```
298
```js
```
## Object destructuring
```js
```
Properties that don't exist produce `undefined`, but you can assign defaults:
```js
// nickname = 'Anon'
```
Sometimes a property name is not a valid identifier or conflicts with another variable in scope. You
can assign it to a new variable with a different name:
```js
299
// first = 'Bob', last = 'Smith'
```
```js
```
Here the parameter destructures the `name` property into a local variable `fullName` and extracts
`age` directly.
```js
const data = {
user: {
id: 42,
preferences: {
theme: "dark",
},
},
};
300
const {
user: {
id: userId,
preferences: {
theme,
},
},
} = data;
// userId = 42
// theme = 'dark'
// primaryLang = 'en'
```
- **Cleaner code** - Assign multiple variables in a single statement instead of writing repetitive
property accesses.
- **Readable function signatures** - Destructure parameters to name only the needed properties
and ignore the rest.
- **Aliasing** - Rename properties to avoid name conflicts, clarify meaning or match naming
conventions.
- **Easier pattern matching** - Combine destructuring with loops or pattern matching to process
data structures succinctly.
## Analogy
Think of destructuring as unpacking a gift basket. Instead of grabbing the whole basket and pulling
out items one by one, you list what you need on the table: apples here, oranges there, and leftover
301
treats in a pile. Aliasing is like labeling the apples as "fruit" and the oranges as "citrus" to make their
purpose clearer.
## Practice questions
**Conceptual questions**
1. How does array destructuring determine which variable receives which value? What happens if
the array has fewer elements than variables?
2. Describe how object destructuring matches properties. What happens when a property is not
present in the source object?
3. Why would you rename a destructured property? Give an example where aliasing improves clarity
or avoids a conflict.
4. Explain how the rest operator (`...`) works in array and object destructuring.
5. Can you use destructuring in function parameters? How does this improve function readability?
**Coding exercises**
1. Given `const point = [3, 4, 5]`, use destructuring to assign `x`, `y` and `z` variables. Provide a default
of `0` for any missing coordinate.
2. Write a function `swapFirstTwo(arr)` that takes an array and returns a new array where the first
two elements are swapped. Use array destructuring.
3. Create a function `describeBook(book)` that takes an object with properties `{ title, author, year }`
and logs a sentence. Use destructuring in the parameter list and rename `year` to `published`.
4. Destructure the following nested object to extract `theme`, the first language, and assign the rest
of the languages to a variable called `others`: `{ settings: { theme: 'light', languages: ['en', 'de', 'jp'] }
}`.
302
What is module federation in modern JS apps?
# Module Federation in Modern JavaScript Applications
As web applications become larger and teams more distributed, breaking your app into
independently deployable pieces—often called **micro-frontends**—helps manage complexity.
Sharing code between these pieces can be challenging. **Module Federation**, introduced in
webpack 5, allows multiple builds to share code and load modules from each other at runtime.
## Core concepts
At its heart, module federation enables one application (the **host**) to consume modules exposed
by another application (the **remote**). Both host and remote are separate builds with their own
dependency graphs. Instead of bundling shared code into the host, the host dynamically loads
modules from the remote when needed.
The host defines which remote applications it depends on via the `ModuleFederationPlugin` in its
webpack configuration:
```js
// [Link] in host
[Link] = {
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
app2: "app2@[Link]
},
}),
303
],
};
```
Here `app2` is a remote application available at the given URL. The `shared` section ensures both
host and remote use the same instance of shared libraries like React.
The remote app exposes modules via its own `ModuleFederationPlugin` configuration:
```js
// [Link] in remote
[Link] = {
plugins: [
new ModuleFederationPlugin({
name: "app2",
filename: "[Link]",
exposes: {
"./Button": "./src/components/[Link]",
},
}),
],
};
```
304
In the host application, import the remote module using a special syntax understood by webpack:
```js
return (
<div>
<RemoteButton />
</Suspense>
</div>
);
```
When the `RemoteButton` component is rendered, webpack fetches `[Link]` from the
remote and loads the `Button` module on demand. Because both apps share React as a singleton,
there are no version conflicts.
- **Code sharing** - Common libraries and components are shared rather than duplicated, reducing
bundle sizes and improving consistency.
- **Runtime integration** - Modules are loaded when needed, enabling dynamic features or
experiments without full rebuilds.
305
- **Micro-frontend architecture** - Teams can build and own separate parts of a larger application,
coordinating via well-defined interfaces.
- **Version compatibility** - Shared dependencies must be compatible across host and remotes.
Singleton configuration helps ensure only one version is used.
- **Complex setup** - Correctly configuring hosts, remotes and shared libraries requires careful
planning. Tools like `@module-federation/nextjs-mf` or `@module-federation/vite` simplify
integration with popular frameworks.
- **Network latency** - Loading remote modules introduces network requests. Use caching and
prefetching to mitigate latency.
- **Security** - Loading code from another domain means you must trust the remote application.
Implement proper content security policies and version controls.
## Analogy
Imagine your application as a city. Some districts are built by different teams (remote apps). Instead
of duplicating common resources like power plants (libraries) in every district, the city builds shared
infrastructure. When a district needs a new service (a module), it requests it from the central
provider and plugs it in without reconstructing the whole district.
## Practice questions
**Conceptual questions**
1. What problem does module federation solve in large applications? Describe the roles of the host
and the remote.
2. In the webpack configuration for the host, what does the `remotes` property specify? What is the
purpose of the `shared` property?
3. How does a remote application expose a module? What is the `filename` option used for?
**Coding exercises**
306
1. Set up a minimal host and remote using webpack 5. Expose a component from the remote and
import it in the host. Ensure that both share React as a singleton.
2. Modify the host so that it loads the remote component lazily using [Link] and displays a
fallback while loading.
3. Experiment with sharing a utility library (e.g., Lodash) between the host and remote. Observe how
changing the version in one app affects the other.
4. Design a simple micro-frontend dashboard where each widget (e.g., weather, news, stock) is built
as a separate remote. Use module federation to assemble the widgets in the host at runtime.
307
Explain Virtual DOM and reconciliation in React
conceptually (JS related)
# Understanding the Virtual DOM and Reconciliation in React
React revolutionized UI development by abstracting direct DOM manipulation. Its secret weapon is
the **Virtual DOM**—an in-memory representation of the real DOM—and an efficient
**reconciliation** algorithm that updates the real DOM only when necessary.
Manipulating the DOM is relatively slow. Every change triggers layout and paint operations, which
can degrade performance when updates are frequent. React solves this by building a lightweight tree
of elements (the virtual DOM) that mirrors the structure of the real DOM. When state or props
change, React creates a **new virtual DOM** and compares it to the previous one. This diffing
process determines the minimal set of changes needed to update the real DOM.
1. **Initial render** - React constructs a tree of JavaScript objects representing the DOM structure
and renders it to the real DOM.
2. **State/prop updates** - When application data changes, React builds a new virtual DOM tree
reflecting those changes.
3. **Diffing (reconciliation)** - React compares the new tree with the previous tree. For each node,
it determines whether to update an existing element, replace it, or leave it unchanged.
4. **Real DOM updates** - React batches the changes and applies them efficiently to the actual
DOM. Only the nodes that changed are updated; unchanged parts are left alone.
By minimizing direct DOM operations and batching updates, React achieves significant performance
gains, especially in complex interfaces.
React's reconciliation algorithm uses a set of heuristics to perform the diff efficiently in O(n) time:
308
- **Different element types lead to full replacement** - If the previous element is a `<div>` and the
new element is a `<span>`, React discards the old subtree and mounts a new one.
- **Same type updates** - If the types match, React updates only the changed attributes (e.g.,
updating a `className` prop) and leaves the DOM node in place. Component state persists across
renders when the component type stays the same.
- **Keys and lists** - When rendering lists of elements, assigning a `key` prop to each element helps
React identify which items have changed, been added or removed. Using stable, unique keys
prevents expensive reordering and preserves state.
```jsx
return (
<ul>
{[Link]((item) => (
<li key={[Link]}>{[Link]}</li>
))}
</ul>
);
// Using indices as keys can lead to incorrect updates when the list changes order.
```
By using `[Link]` as the key, React can match each `<li>` across renders and update only the text for
modified items.
- **Performance** - Updates are computed in memory and only applied to the DOM when
necessary, reducing costly reflows and repaints.
- **Declarative style** - Developers describe what the UI should look like for a given state. React
handles updating the DOM to match that state, freeing you from manual DOM manipulation.
309
- **Predictable updates** - React batches updates and applies them deterministically, which helps
avoid inconsistent UI states.
## Analogy
Think of the virtual DOM as an architect's **blueprint** of a building. Before renovating, the
architect revises the blueprint (new virtual DOM) and compares it to the current blueprint (previous
virtual DOM). They then instruct the builders to modify only the walls or rooms that changed (real
DOM updates). Without the blueprint, builders would wander the building, making unnecessary
changes and causing chaos.
## Common pitfalls
- **Using indices as keys** - When rendering lists, using the array index as a key can cause incorrect
component reuse and visual glitches when items are reordered or removed. Always use a stable
identifier if possible.
- **Unnecessary wrapper elements** - Extra divs in the JSX tree create additional nodes in the
virtual DOM. Use React fragments (`<> ... </>`) to avoid wrapping elements unnecessarily.
- **Large component trees** - Deeply nested structures can still result in many diff operations.
Consider splitting components and memoizing where appropriate.
## Practice questions
**Conceptual questions**
1. Explain the purpose of the virtual DOM. How does it improve performance compared to direct
DOM manipulation?
2. Describe the steps React takes during reconciliation. What happens when two elements have
different types?
3. Why are keys important when rendering lists? What problems arise when keys are not used or are
non-unique?
4. How does React batch updates to the DOM? Why is batching beneficial?
5. Give an example where using the array index as a key causes an issue when updating a list.
**Coding exercises**
310
1. Write a React component that renders a list of users. Demonstrate how adding and removing users
affects the DOM when using proper keys vs. using the index.
2. Build a simple counter component and log each phase: initial render, state update, virtual DOM
creation, diff and DOM update (use `useEffect` and console logs to illustrate the lifecycle).
3. Create a component that intentionally reorders items without keys and observe how React
updates the DOM. Then fix it by adding unique keys.
4. Implement a small custom hook that memoizes a list component to avoid re-rendering when the
list items haven't changed. Explain how memoization interacts with reconciliation.
311
What is event-loop starvation?
# What Is Event Loop Starvation?
JavaScript executes on a single thread. It uses an **event loop** to schedule and run different kinds
of tasks: macro-tasks (e.g., `setTimeout`, I/O callbacks) and micro-tasks (e.g., `Promise` callbacks,
MutationObservers). The event loop repeatedly takes a task from the macro-task queue, executes it
until completion, then runs all micro-tasks queued during that execution before moving on.
**Starvation** occurs when some tasks never get a chance to run because the event loop is
perpetually busy with other tasks.
2. **Flooding the micro-task queue** - Micro-tasks have higher priority than macro-tasks. After a
macro-task finishes, the engine runs all micro-tasks before taking the next macro-task. If your code
continually queues micro-tasks (for example, repeatedly calling `[Link]().then(...)` in a
loop), the engine may run micro-tasks indefinitely, causing macrotasks like `setTimeout` callbacks to
be delayed or never run at all.
```js
function scheduleMacrotask() {
setTimeout(() => {
[Link]("Macrotask executed");
}, 0);
function floodMicrotasks() {
312
for (let i = 0; i < 1e5; i++) {
[Link]().then(() => {
});
scheduleMacrotask();
floodMicrotasks();
```
In this example, a single `setTimeout` callback (a macro-task) is scheduled, but a loop adds 100,000
micro-tasks. Because micro-tasks run before the next macro-task, the event loop spends a long time
clearing the micro-task queue. The timeout callback doesn't run until all those micro-tasks complete.
```js
function longComputation() {
// Simulate work
[Link]("Start");
longComputation();
313
[Link]("End");
// 'Timeout fired' logs only after longComputation completes (~5 seconds later).
```
Because `longComputation()` runs synchronously for 5 seconds, the event loop cannot process the
`setTimeout` callback until it finishes. Any user interaction or UI updates also pause during this time,
making the page appear frozen.
## Avoiding starvation
- **Break up long tasks** - Split heavy computations into smaller chunks and schedule the next
chunk with `setTimeout` or `setImmediate` (in [Link]). This yields control back to the event loop,
allowing other tasks to run.
- **Use micro-tasks responsibly** - Avoid rapidly queuing micro-tasks in a tight loop. Instead, batch
work or schedule some tasks as macro-tasks using `setTimeout` with a delay of 0.
- **Use `requestIdleCallback`** - For non-urgent work (analytics, logging), schedule tasks during idle
periods. The browser calls `requestIdleCallback` when it's safe to run low-priority tasks without
blocking critical rendering or input.
- **Web workers** - Offload CPU-intensive tasks to a Web Worker, which runs in its own thread and
doesn't block the event loop.
## Analogy
Imagine a single checkout lane at a grocery store. People in line (tasks) are served one at a time.
Micro-tasks are like VIP customers that always cut to the front. If too many VIPs arrive, regular
customers may never reach the counter. Likewise, if one customer brings a cart full of items and the
cashier never pauses, everyone behind them waits. To keep the line moving, the cashier occasionally
pauses long customers to serve others, or opens a new lane (web worker) for large orders.
## Practice questions
**Conceptual questions**
1. Explain the difference between micro-tasks and macro-tasks in the event loop. Why can flooding
the micro-task queue lead to starvation of macro-tasks?
314
2. Provide two examples of long-running synchronous code that could cause event loop starvation.
How do they affect the user experience?
3. How does breaking a computation into smaller pieces and scheduling them with `setTimeout` or
`requestIdleCallback` help prevent starvation?
4. Describe scenarios where using a Web Worker is preferable to running code on the main thread.
**Coding exercises**
1. Write a function that processes an array of 1 million items without blocking the UI. Break the work
into chunks and use `setTimeout` to schedule each chunk.
2. Modify the `floodMicrotasks()` example so that it only queues micro-tasks in batches of 100,
allowing `setTimeout` callbacks to run between batches.
3. Implement a progress bar that updates in real time while computing a large Fibonacci number. Use
`requestIdleCallback` to schedule the computation and ensure the UI remains responsive.
4. Write a simple Web Worker that performs heavy computation (e.g., prime number generation)
and communicates results back to the main thread. Demonstrate that the main UI does not freeze
during the computation.
315
Explain call stack overflow and recursion depth limits
# Understanding the Call Stack, Recursion Depth Limits and Stack Overflow in JavaScript
JavaScript executes code in a **single thread** using a call stack, which is essentially a stack data
structure used to track function calls. Each time a function is invoked, an **execution context**—
containing its parameters, local variables and the location where the function should return—is
pushed onto this stack. When the function finishes, its context is popped off and execution returns to
the caller. This push-and-pop sequence continues as your program runs.
Recursion is a technique where a function calls itself (directly or indirectly) to solve a problem by
breaking it down into smaller pieces. Each recursive call pushes another execution context onto the
call stack. As long as there is a **base case**—a condition that stops the recursion—the stack will
eventually unwind and return control back down the chain of calls.
If a recursive function either lacks a proper base case or calls itself too deeply, the call stack grows
until the JavaScript engine cannot allocate any more stack frames. At that point, a **stack overflow
error** occurs (for example, `RangeError: Maximum call stack size exceeded`). Different browsers
and environments have different stack size limits; in many engines the maximum safe recursion
depth is in the range of a few thousand calls, so relying on very deep recursion can be fragile.
```js
function factorial(n) {
[Link](factorial(5)); // 120
```
316
This `factorial` function stops when `n` reaches 0 or 1, preventing unbounded growth of the call
stack. If you omit the base case or inadvertently call the function with a negative value, the recursion
never ends and a stack overflow occurs.
```js
function endless(n) {
return endless(n + 1); // no base case - runs until the stack overflows
try {
endless(0);
} catch (e) {
```
Depending on your environment, this code will throw a `RangeError` after several thousand recursive
calls. It shows how unbounded recursion can exhaust the call stack.
JavaScript engines implement the call stack with a fixed amount of memory. Each new call pushes an
execution context, so a deep recursive algorithm can exhaust this memory quickly. Some languages
support **tail call optimization**, a technique that reuses stack frames for certain recursive
patterns. JavaScript's specification allows tail call optimization in strict mode, but most engines don't
implement it yet, so you shouldn't assume it will save stack space.
For algorithms that require many iterations, consider converting recursive logic to an **iterative
approach** using loops or explicit stacks. Iterative solutions often avoid the risk of stack overflow
and can be easier to reason about when working with large datasets.
317
## Real-world analogy
Imagine a stack of plates at a buffet: you can take a plate off the top or add one on top, but you
never remove or insert plates in the middle. Each function call is like placing a new plate on the stack.
If you keep adding plates without ever removing them, eventually the stack becomes unstable and
collapses—that's the stack overflow. To prevent it, you must stop placing plates once you reach a
certain height (the base case) or remove plates as you go (using iteration).
- **Infinite recursion is not the only cause of stack overflow.** A finite recursion that simply goes
too deep will also overflow the stack. Always design your recursion to stop early when possible.
- **Tail recursion isn't automatically optimized in JavaScript.** Even in strict mode, most engines
do not implement tail call optimization, so writing a tail-recursive function will not necessarily
prevent stack overflow.
- **Using global variables for recursion counters can hide problems.** It's better to pass state
through function parameters or use local variables so you don't inadvertently depend on external
mutable state.
- **Stack overflow is not just about recursion.** A long chain of synchronous function calls (even
without recursion) can also exceed the stack limit if it nests too deeply.
## Practice questions
1. **Theory:** Explain the call stack in your own words. What happens to the stack when a function
calls another function? How does it differ when a function returns?
2. **Theory:** Why does a `RangeError: Maximum call stack size exceeded` occur? Describe two
ways to prevent it when using recursion.
3. **Coding:** Write a recursive function that sums the elements of a nested array (arrays can
contain numbers or other arrays) but uses an explicit stack (an array) internally instead of relying on
the call stack. This prevents stack overflow on very deeply nested arrays.
4. **Coding:** Convert the following recursive Fibonacci function into an iterative version that uses
a loop and avoids recursion:
```js
function fibonacci(n) {
if (n <= 1) return n;
318
return fibonacci(n - 1) + fibonacci(n - 2);
```
5. **Theory:** What is tail call optimization? Why isn't it currently relied upon in production
JavaScript code?
These questions and examples will help you deepen your understanding of how the call stack works,
why recursion must be designed carefully, and how to avoid stack overflow errors in real projects.
319
What are tagged template literals?
# Tagged Template Literals in JavaScript
Template literals, enclosed by backticks (\``), allow embedded expressions (`${...}`) and multi-line
strings. **Tagged template literals** build upon this by passing the literal's parts to a **tag
function**. This tag can interpret the string in any way it likes—formatting, escaping, localization, or
even generating custom data structures.
When you write a tagged template literal like `tag\`Hello, ${name}!\``, JavaScript translates it into a
call to `tag()`. The tag function receives:
1. **An array of strings** containing the literal text segments (everything outside `${...}`
placeholders). This array is frozen and reused for repeated calls with the same literal, so you can
cache results for performance.
You are free to return any value from the tag function, not just a string. This makes tagged templates
very flexible.
```js
const value =
}, "");
320
const user = "Alice";
```
Here, the `highlight` function wraps each interpolated value in `<strong>` tags. The tag gets the literal
pieces (`"Name: ", ", Age: "`) in the `strings` array and the values (`user`, `age`) in the `values` array.
By interleaving them, it produces a formatted string.
By default, escape sequences like `\n` are interpreted in template literals. If you need the raw text,
the `strings` array has a `raw` property containing the unprocessed versions. This is useful for writing
custom parsers that need to interpret backslashes or special characters exactly as written.
```js
function showRaw(strings) {
[Link]([Link][0]);
```
## Practical uses
- **Custom DSLs and domain-specific parsing:** Tagged templates are often used to implement
mini-languages within JavaScript. Libraries like GraphQL or styled-components use tags to parse
structured strings and generate queries or CSS at build time.
- **Localization and internationalization:** A tag function can look up translation keys for strings
and substitute variables based on locale settings.
- **Escaping untrusted input:** You can create a safe HTML tag that escapes user input to prevent
cross-site scripting attacks:
321
```js
String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
return [Link](
""
);
[Link](safeHtml);
```
- **Currying and performance optimizations:** Since the strings array is immutable and reused, tag
functions can cache results for identical literal patterns, improving performance when parsing
complex templates repeatedly.
## Common misconceptions
- **Not only for strings.** A tag function can return any JavaScript value—an object, array, DOM
element, or even a promise. Some libraries use tags to build complex queries or React components.
- **Template parts are not concatenated automatically.** The tag must handle the `strings` and
`values` arrays explicitly. Forgetting to join them will result in unexpected output.
322
- **Tags don't modify the template literal syntax.** The syntax inside `${...}` expressions is normal
JavaScript. The tag only sees the evaluated values, not the expressions themselves.
## Real-world analogy
Think of a mail merge program that uses templates with placeholders like `Dear {{name}}`. The
program reads the template, pulls out the fixed text ("Dear ") and the variable parts (e.g., the
recipient's name), then combines them to produce a personalised letter. A tagged template literal
works similarly: the tag function is the mail merge program, and the literal is the template.
## Practice questions
1. **Theory:** What arguments are passed to a tag function when evaluating a tagged template
literal? How is the `raw` property used?
2. **Theory:** Explain how tagged templates can help prevent cross-site scripting when inserting
user-supplied content into HTML.
3. **Coding:** Write a tag function called `formatCurrency` that takes a template like
``formatCurrency`Total: ${amount}```and returns a string that formats the`amount`as US dollars
using`[Link]`.
4. **Coding:** Create a tag function that builds an array of objects from a comma-separated list: ``
toPairs`a=1,b=2,c=3` `` should return `[{ key: 'a', value: '1' }, ...]`.
5. **Theory:** How does a tagged template differ from simply calling a function with a string
argument? What advantages does the tagged template syntax provide?
These questions encourage you to explore both the mechanics and creative uses of tagged template
literals.
323
What is lazy evaluation in JS?
# Understanding Lazy Evaluation in JavaScript
**Lazy evaluation** (or call-by-need) is an evaluation strategy in which expressions are not
computed until their values are actually required. This contrasts with JavaScript's default **eager
evaluation**, where expressions are evaluated as soon as they're encountered. Lazy evaluation can
improve performance by avoiding unnecessary work and supports patterns like infinite data
structures.
While JavaScript does not implement general lazy evaluation for all expressions, it provides
mechanisms that behave lazily in specific contexts. You can also build your own lazy constructs using
functions, generators and proxies.
The logical AND (`&&`) and OR (`||`) operators evaluate operands from left to right and stop as soon
as the result is determined. For example, in `a && b`, `b` is evaluated only if `a` is truthy. Similarly, in
`a || b`, `b` is evaluated only if `a` is falsy. This allows you to use these operators to conditionally
execute code:
```js
function doExpensiveWork() {
return true;
flag || doExpensiveWork();
324
flag = true;
flag || doExpensiveWork();
```
ES5 introduced getters, which are functions associated with object properties. A getter is executed
lazily—the computation happens only when the property is accessed, not when the object is created:
```js
const user = {
firstName: "Alice",
lastName: "Smith",
get fullName() {
[Link]("fullName computed");
},
};
[Link]("User created");
[Link]([Link]);
```
Generator functions (`function*`) produce values on demand. When you call a generator, it returns
an iterator. Each call to `next()` resumes execution until the next `yield` statement, returning a new
value. This allows you to represent large or even infinite sequences without precomputing them.
```js
325
function* naturalNumbers() {
let i = 1;
while (true) {
yield i++;
[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
```
Generators enable lazily building pipelines of operations with the `for...of` loop or the spread
operator. Since values are produced one at a time, memory usage stays low even when iterating over
large sequences.
You can simulate laziness using higher-order functions. A common pattern is to wrap a computation
in a function and call it only when needed:
```js
function lazy(fn) {
let result;
return () => {
if (!evaluated) {
result = fn();
evaluated = true;
return result;
326
};
[Link]("Computing...");
return [Link]();
});
```
This pattern, known as **memoization**, caches the result after the first computation. Combined
with closures, it defers the expensive work until the value is requested.
Another approach is to use `Proxy` to intercept property access and compute values lazily. However,
proxies add complexity and should be used judiciously.
- **Performance:** By delaying computations until needed, lazy evaluation can avoid unnecessary
work, which is helpful when dealing with large data structures or expensive operations.
- **Infinite data structures:** Generators allow you to model potentially infinite sequences (like the
Fibonacci numbers) without running out of memory.
- **Control flow:** Lazy evaluation makes it easy to implement custom control flow constructs, such
as conditional evaluation in template engines or domain-specific languages.
- **Debugging complexity:** Deferred computations can make it harder to trace when and where
values are computed.
327
- **Memory retention:** If cached values capture large objects, they may remain in memory longer
than expected. WeakMaps or manual cache invalidation can help mitigate this.
- **Non-uniform language support:** JavaScript is eager by default. Lazy patterns are implemented
manually and require discipline to maintain.
## Real-world analogy
Consider ordering a made-to-order sandwich: the ingredients aren't assembled until you place the
order. If you never order, the sandwich is never prepared, saving time and ingredients. Lazy
evaluation works the same way—computation happens only when you need the result.
## Practice questions
1. **Theory:** Explain the difference between eager and lazy evaluation. Provide examples of each
in JavaScript.
2. **Theory:** How do the logical operators `&&` and `||` demonstrate lazy behaviour? What would
happen if JavaScript evaluated both operands eagerly?
3. **Coding:** Implement a generator function that yields the squares of natural numbers (1, 4, 9,
...) indefinitely. Then write a loop that prints the first 10 squares.
5. **Theory:** Discuss the pros and cons of using lazy evaluation in a web application. When might
laziness introduce problems instead of solving them?
These exercises will help you understand how to implement and reason about laziness in a language
that is eager by default.
328
How does JavaScript handle memory leaks?
# How JavaScript Handles Memory and Avoids Memory Leaks
JavaScript is a garbage-collected language. You don't explicitly free memory; instead, the engine
automatically reclaims memory that is no longer needed. Understanding how garbage collection
works and how leaks can occur will help you write more efficient, leak-free code.
1. **Allocation:** When variables and objects are created, the engine reserves space in memory.
Modern engines use the **mark-and-sweep** algorithm. Starting from "roots" such as global
variables, the call stack, and closure scopes, the collector marks all reachable objects. Anything not
marked is considered unreachable and is collected. Cyclic references (objects referencing each other)
are not a problem because an unreachable cycle is still unmarked and will be reclaimed.
A **memory leak** occurs when the program retains references to objects that are no longer
needed, preventing the garbage collector from reclaiming their memory. Over time, these unused
objects accumulate, increasing memory consumption and potentially degrading performance.
- **Global variables:** Accidentally creating global variables (by omitting `let`, `const` or `var`) or
attaching data to the `window` object keeps them alive for the life of the page. Avoid polluting the
global scope.
329
- **Closures holding onto data:** Functions that capture variables from outer scopes can keep
those variables alive even when they're no longer needed. For example, storing a large object in a
closure used by an event handler may leak memory if the handler remains attached after the object
is irrelevant.
- **Detached DOM nodes:** Removing DOM elements from the document tree doesn't
automatically free associated memory if you still hold references to them in JavaScript. Keeping old
elements in arrays or caches without releasing them prevents garbage collection.
- **Accidental caches:** Libraries or your own code may cache data (e.g., API responses, computed
values) but never remove stale entries. Unbounded caches grow over time. Use size limits or
time-to-live policies.
- **Closures in loops:** Creating functions inside loops that capture loop variables can
unintentionally hold onto large structures if the closures persist longer than necessary.
- **Limit scope and use `const`/`let`:** Declare variables with block scope so they don't leak into
the global object. Minimise the lifetime of variables.
- **Remove event listeners:** If you attach an event listener to an element or the window, detach it
when it's no longer needed. Many modern frameworks automatically handle this for you, but vanilla
JavaScript requires manual cleanup.
- **Clear timers:** Always pair `setInterval()` with `clearInterval()` and `setTimeout()` with
`clearTimeout()` when the task is complete or when the component is destroyed.
- **Null out references:** When you're done with an object (especially large arrays or DOM
elements), set variables referencing it to `null` or remove them from arrays. This makes it easier for
the garbage collector to determine that the object is unreachable.
330
- **Use weak collections:** `WeakMap` and `WeakSet` hold **weak references** to their keys. If
no other references exist, the garbage collector can reclaim the key object and its associated value
automatically. Weak collections are ideal for memoizing data keyed by DOM elements or other
objects without preventing garbage collection.
- **Monitor memory usage:** Modern browsers provide memory profiling tools (e.g., Chrome
DevTools' Memory panel) that help detect leaks by showing detached nodes and snapshots of heap
usage over time.
## Real-world analogy
Imagine your program is like a house with rooms (scopes) holding objects. When you no longer need
an item, you should remove it from all rooms; otherwise, it will clutter the house forever. The
garbage collector is like a cleaning service that checks which objects are reachable (in rooms that
someone can enter) and removes objects left in inaccessible spaces. If you forget to take objects out
of accessible rooms, the cleaning service won't touch them—leading to clutter (memory leaks).
## Practice questions
1. **Theory:** Describe the mark-and-sweep algorithm in your own words. Why are cyclic
references not inherently problematic in modern JavaScript engines?
2. **Theory:** List three common sources of memory leaks in JavaScript applications and explain
how to prevent each.
3. **Coding:** Write a function that attaches an event listener to a button and updates a counter.
Modify your function to remove the listener when the button is removed from the DOM, ensuring no
leak occurs.
4. **Coding:** Create a cache using `Map` that stores results of an expensive computation. Add a
method to clear the cache after 10 entries to avoid unbounded memory growth.
5. **Theory:** Explain the difference between `Map` and `WeakMap` in the context of garbage
collection. When would you choose one over the other?
By understanding how the garbage collector works and being mindful about references, you can
avoid memory leaks and keep your applications fast and efficient.
331
Explain hoisting with function expressions vs arrow
functions
# Hoisting and Function Types: Declarations vs. Expressions vs. Arrow Functions
JavaScript's **hoisting** behaviour can be confusing. It refers to the way variable and function
declarations are moved to the top of their containing scope during compilation. Understanding how
hoisting applies to different kinds of functions—function declarations, function expressions and
arrow functions—will help you avoid unexpected `TypeError` or `ReferenceError` messages.
```js
function square(n) {
return n * n;
```
Function declarations are fully hoisted. Both the function's name and its body are moved to the top
of the current scope. You can call the function before its declaration appears in the code.
```js
332
var cube = function (n) {
return n * n * n;
};
[Link](cube(2)); // 8
```
Variables declared with `var` are hoisted but initialised with `undefined` until their assignment is
reached. Therefore, calling `cube(2)` before assignment fails: the variable exists, but holds
`undefined`, which is not callable.
Using `let` or `const` for function expressions tightens the rules further. Variables declared with
`let`/`const` are placed in a **temporal dead zone**—a period between entering the scope and the
declaration line where they exist but cannot be accessed. Accessing them early throws a
`ReferenceError`:
```js
return [Link] * r * r;
};
```
```js
333
// greet(); // ReferenceError or TypeError depending on declaration
[Link](`Hello, ${name}!`);
};
greet("Alice"); // Works
```
If you use `var` instead of `const`, `greet` is hoisted as `undefined`. Calling it before assignment will
throw a `TypeError` because you're trying to call `undefined` as a function. With `let` or `const`,
accessing `greet` before the declaration triggers a `ReferenceError` due to the temporal dead zone.
While hoisting is about when a function becomes available, function expressions and arrow functions
also differ in behaviour:
- **`this` binding:** Arrow functions do not have their own `this`. They capture `this` from the
surrounding lexical context. Function declarations and expressions get their own `this` depending on
how they're called. This difference makes arrow functions unsuitable as methods on objects that rely
on dynamic `this`.
- **`arguments` object:** Arrow functions do not have an `arguments` object; you must use rest
parameters (`...args`) to access arguments. Regular functions have their own `arguments` object.
- **Constructors:** Arrow functions cannot be used as constructors. Calling them with `new` throws
a `TypeError`. Traditional function declarations and expressions can be invoked as constructors if
designed for that purpose.
## Real-world analogy
Think of hoisting like a stage play. Script lines (declarations) are pinned to the top of the script before
the actors begin. The actors can deliver lines (call functions) right away if the lines were pinned
(function declarations). However, if a line is written on a cue card handed out later (function
expression), the actor doesn't know what to say until the cue card arrives. Arrow functions are
always on cue cards; the actor can't read them until they're handed over.
334
## Common pitfalls
- **Assuming all functions are hoisted.** Only declarations are. Expressions and arrow functions
need to be defined before use.
- **Mixing `var`, `let` and `const`.** Understand that `var` declarations hoist and initialise to
`undefined`, while `let` and `const` create a temporal dead zone.
- **Using arrow functions as methods.** Arrow functions capture `this` lexically; using them as
object methods can lead to unexpected `this` values.
## Practice questions
1. **Theory:** Explain the difference between hoisting of function declarations and variables. How
does the temporal dead zone apply to functions assigned to `let` or `const` variables?
2. **Coding:** Predict the output of the following code and explain why:
```js
show();
function show() {
[Link]("declaration");
show();
```
3. **Coding:** Rewrite the following function declaration as an arrow function. How would you call
it to avoid hoisting pitfalls?
```js
function multiply(a, b) {
return a * b;
```
335
4. **Theory:** Describe the differences in `this` and `arguments` handling between arrow functions
and traditional functions. Give examples where choosing one over the other makes a difference.
5. **Coding:** Write a function that uses an arrow function inside a method. Demonstrate how the
arrow captures `this` from its enclosing scope.
These exercises will solidify your understanding of hoisting and function types, helping you avoid
common runtime errors.
336
What is the Temporal API (upcoming JS proposal)?
# The Temporal API - A Modern Date and Time API for JavaScript
Dates and times are notoriously tricky. JavaScript's built-in `Date` object has long been criticised for
confusing behaviour, limited time-zone support and awkward APIs. To address these shortcomings,
TC39 (the committee that evolves JavaScript) has proposed a new **Temporal** API. Though still
experimental in many environments, Temporal aims to provide a robust, modern way to work with
dates, times, durations and time zones.
The original `Date` object was modelled on Java's `[Link]`, which was later superseded in Java
because of its design flaws. `Date` combines a timestamp and calendar fields into a single mutable
object, uses local time by default, lacks full time-zone support, and has inconsistent parsing and
arithmetic methods. Temporal's design addresses these issues by:
- **Immutability:** Temporal objects are immutable. Operations like `add()` or `with()` return new
objects instead of modifying the original, avoiding side effects.
- **Built-in time-zone and calendar support:** Temporal can represent dates in any IANA time zone
and supports different calendar systems. It handles daylight saving transitions and leap seconds
correctly.
- **Consistent parsing and arithmetic:** Temporal provides methods like `from()`, `toString()`,
`add()` and `subtract()` that behave predictably, allowing easy conversion between types and reliable
date arithmetic.
- **`[Link]`** provides static methods that return current date and time values in different
forms. For example, `[Link]()` gives today's ISO date in the local time zone, and
`[Link]()` returns a zoned date-time with the device's time zone.
- **`[Link]`** represents a single point on the timeline (like a UNIX timestamp) with
nanosecond precision. It is time-zone agnostic and can be converted to other Temporal types.
337
- **`[Link]`** pairs a calendar date-time with a specific time zone. It's useful
when you need an exact moment plus a zone—for example, scheduling meetings across time zones
or converting times for travel itineraries.
- **`[Link]`** represents the difference between two temporal values in terms of years,
months, days, hours, minutes, seconds and smaller units. Duration arithmetic is precise and respects
calendar rules.
```js
year: 2025,
month: 12,
day: 15,
hour: 9,
minute: 30,
timeZone: "America/New_York",
});
338
[Link]([Link]()); // adds two hours without modifying `meeting`
[Link]([Link]());
[Link]([Link]());
```
These examples illustrate Temporal's clarity: you explicitly specify time zones and units, and methods
return new immutable objects.
- **Immutability vs. mutability:** `Date` objects are mutable (methods like `setHours()` modify the
original). Temporal objects are immutable; methods like `add()` return new values.
- **Time-zone awareness:** `Date` stores a timestamp but defaults to the local time zone when
converting to strings. Temporal separates zone-aware and zone-agnostic types, making conversions
explicit.
- **Precision:** Temporal offers nanosecond precision and robust arithmetic. `Date` is limited to
milliseconds and has quirks like months being zero-indexed.
- **Parsing:** Temporal's `from()` methods accept object literals with named fields, removing
ambiguities of date string parsing.
As of 2025, the Temporal API is still experimental. It is implemented in some environments but not
available in all browsers. You can test Temporal in supported engines or via polyfills, but check
339
compatibility before using it in production. When the API is widely supported, it is likely to become
the recommended way to handle date and time in JavaScript.
## Real-world analogy
Consider scheduling flights. The original Date API is like using a single clock that only shows your local
time. When you travel across time zones, you constantly need to recalculate departure and arrival
times. The Temporal API is like having a world clock and a calendar: you can specify the departure
time, time zone and calendar clearly, perform accurate arithmetic and convert to local time zones
without confusion.
## Practice questions
1. **Theory:** What problems with the existing `Date` object motivated the creation of the
Temporal API? List at least three issues.
3. **Coding:** Create a `[Link]` for July 1, 2025 at 15:00 in the `Asia/Tokyo` time
zone. Convert it to New York time (`America/New_York`) and output the resulting date-time.
4. **Coding:** Write a function that takes two `[Link]` objects and returns the number
of days between them using a `[Link]`.
5. **Theory:** Explain how immutability in Temporal objects helps prevent bugs. Give an example
where using a mutable `Date` object could lead to unexpected behaviour.
These questions encourage you to explore the Temporal API and understand how it addresses the
shortcomings of the old `Date` API.
340
What is requestAnimationFrame and when to use it?
# `requestAnimationFrame`: Scheduling Efficient Animations in the Browser
Modern web applications often require smooth animations—moving elements, scrolling lists, or
game loops. Using `setTimeout()` or `setInterval()` to drive animations can cause janky motion and
wasted CPU cycles because they run independently of the browser's rendering schedule. The
`requestAnimationFrame()` API provides a better way to schedule visual updates.
`requestAnimationFrame(callback)` tells the browser that you want to perform an animation. The
browser calls your `callback` just **before** the next repaint. The callback receives a
**high-resolution timestamp** indicating when the repaint is scheduled. After executing your
callback, the browser paints the updated frame. Because `requestAnimationFrame()` is aligned with
the display's refresh rate (often 60 frames per second), it produces smoother animations and reduces
unnecessary work.
Key characteristics:
- **Runs before repaint:** The callback is invoked just before the browser paints a new frame, so
any DOM changes are drawn immediately. This reduces the risk of layout thrashing and ensures your
updates are visible on the next screen refresh.
- **Automatic throttling:** When the page is in a background tab or the browser is minimised,
`requestAnimationFrame()` callbacks are paused, conserving CPU and battery. Using `setInterval()`
continues to run timers even when the page is hidden.
```html
<style>
#box {
position: relative;
341
width: 50px;
height: 50px;
background: coral;
</style>
<div id="box"></div>
<script>
let startTime;
function move(timestamp) {
[Link] = `translateX(${distance}px)`;
requestAnimationFrame(move);
requestAnimationFrame(move);
</script>
```
In this example, a box moves smoothly to the right by 500 px at 100 px per second. Each frame
calculates how much time has passed and moves the box accordingly. When the box reaches 500 px,
the animation stops by not scheduling another frame.
- **Animations and game loops:** Whenever you are updating visual properties (position, opacity,
scale) or drawing to a `<canvas>`, `requestAnimationFrame()` ensures your updates sync with the
display.
342
- **Smooth scroll or parallax effects:** Use it to update scroll positions or CSS transforms during
user interactions for the smoothest performance.
`requestAnimationFrame()` returns an integer ID. If you need to stop a scheduled callback (for
example, when a component unmounts), call `cancelAnimationFrame(id)`. Cancelling prevents the
callback from running if it hasn't been executed yet.
```js
const id = requestAnimationFrame(myCallback);
// later ...
cancelAnimationFrame(id);
```
## Real-world analogy
Imagine painting a flipbook: rather than drawing arbitrarily at random times, you wait for the
moment right before turning to the next page to draw the next frame. This coordination ensures
each frame appears exactly when the viewer flips the page. `requestAnimationFrame()` provides
similar coordination with the browser's rendering loop.
## Common misconceptions
- **It doesn't guarantee 60 fps on its own.** `requestAnimationFrame()` only schedules callbacks
before repaints. If your callback performs expensive work, it can still cause janky frames. Keep your
callback work lightweight or offload heavy tasks to Web Workers.
- **You still need to call it repeatedly for continuous animations.** Unlike `setInterval()`,
`requestAnimationFrame()` doesn't loop automatically. Always invoke it again inside your callback if
you want the next frame.
343
## Practice questions
2. **Coding:** Write a function that smoothly fades out an element over two seconds using
`requestAnimationFrame()`. The element's opacity should decrease linearly from 1 to 0.
3. **Coding:** Create a basic game loop using `requestAnimationFrame()` that updates a character's
position and redraws a canvas. How would you pause and resume the loop?
Use these questions to explore efficient animation patterns and to practice writing smooth, efficient
browser animations.
344
Explain IntersectionObserver and MutationObserver
APIs
# Observing the DOM: IntersectionObserver and MutationObserver
Modern web pages are highly dynamic. Elements appear and disappear, and you may need to react
when an element enters the viewport or when the DOM structure changes. Polling the DOM on
every scroll or mutation is inefficient. The **IntersectionObserver** and **MutationObserver**
APIs provide efficient, event-driven ways to observe these changes.
The `IntersectionObserver` API lets you asynchronously detect when a **target element** enters or
leaves a specified **root**'s viewport or crosses visibility thresholds. This is useful for lazy loading
images, implementing infinite scrolling, triggering animations when content appears, and reporting
viewability for ads.
```js
const options = {
threshold: [0, 0.5, 1], // percentages of visibility that trigger the callback
};
[Link]((entry) => {
if ([Link]) {
});
345
}, options);
[Link](target);
```
The `callback` receives an array of `IntersectionObserverEntry` objects. Each entry contains details
about a target element's intersection: `isIntersecting` (whether it is visible), `intersectionRatio` (the
percentage of the element in view) and bounding rectangles. You can observe multiple elements
with a single observer instance.
`IntersectionObserver` does not continuously poll. The browser schedules callbacks when visibility
thresholds are crossed, making it efficient compared to listening to scroll events. Configuration
options include:
- **`root`**: The element or viewport used as the boundary for testing visibility. If omitted or `null`,
the browser viewport is used.
- **`rootMargin`**: Offsets applied to the root's bounding box, specified like CSS margins (e.g., `'0px
0px -50% 0px'` to trigger sooner).
- **`threshold`**: A number or array of numbers between 0 and 1 indicating the intersection ratios
that trigger the callback.
The `MutationObserver` API allows you to observe changes to the DOM tree itself: additions or
removals of nodes, changes to attributes, and text mutations. It replaces the deprecated Mutation
Events and runs asynchronously to avoid blocking the main thread.
To use a `MutationObserver`, create it with a callback and call `observe()` on a target node with
options describing what you want to watch:
```js
346
const list = [Link]("myList");
[Link]((record) => {
[Link](
[Link]
);
});
});
[Link](list, {
childList: true,
attributes: true,
});
// [Link]();
```
347
- **`characterData`**: Observe changes to text nodes.
The callback is called with an array of `MutationRecord` objects detailing the changes. Use
`disconnect()` to stop observation when it's no longer needed.
Use **IntersectionObserver** when you care about an element's visibility relative to the viewport
or a scroll container. It's especially useful for:
- Lazy loading images and resources when they come into view.
- Implementing infinite scrolling: detecting when the user reaches the bottom of a list and loading
more content.
Use **MutationObserver** when you need to react to changes in the DOM structure or attributes.
Typical use cases include:
- Building debugging tools that log when the page changes unexpectedly.
## Real-world analogy
Imagine you're managing a museum. You have one guard (IntersectionObserver) watching whether a
visitor enters a specific room, and another guard (MutationObserver) watching whether an artwork
is moved, swapped out or labelled differently. The first guard reports when something becomes
visible; the second reports when something changes its existence or attributes.
## Practice questions
348
1. **Theory:** Describe how `IntersectionObserver` differs from listening to the `scroll` event for
detecting when an element enters the viewport. What performance benefits does it offer?
2. **Theory:** What types of mutations can `MutationObserver` detect? How do the options you
pass to `observe()` control which mutations are reported?
3. **Coding:** Implement lazy loading of images: write a script that uses `IntersectionObserver` to
replace `data-src` with `src` when images scroll into view.
4. **Coding:** Write a `MutationObserver` that logs a message whenever a `<ul>` gains or loses
`<li>` items. Then write code to add and remove list items to test it.
These questions and examples will help you use observers effectively to build performant and
reactive user interfaces.
349
What is the difference between innerHTML and
textContent?
# `innerHTML` vs. `textContent`: Reading and Writing DOM Content Safely
When manipulating HTML elements, two commonly used properties are `innerHTML` and
`textContent`. Both allow you to inspect or update the contents of an element, but they behave
differently and should be used for different purposes.
The `innerHTML` property gets or sets the **HTML markup** contained within an element. Reading
`innerHTML` returns a string with the serialized HTML of the element's descendants. Writing to
`innerHTML` parses the provided string as HTML and replaces all existing children with the result.
```html
<script>
</script>
```
Because `innerHTML` parses and inserts HTML, it is considered an **injection sink**—a potential
source of **cross-site scripting (XSS)** vulnerabilities. Never insert untrusted user input via
`innerHTML`. Browsers treat the input as HTML, so any `<script>` tags or event handlers will run.
Mitigate this risk by sanitizing input or using the [Trusted Types
API]([Link] in modern browsers.
350
- **Performance:** Setting `innerHTML` replaces all child nodes, causing the browser to tear down
and rebuild the subtree. For small strings this is fine, but for large or frequent updates it can be
costly.
- **Security:** As noted, raw HTML insertion can lead to XSS if not sanitized.
Despite these caveats, `innerHTML` is useful when you intentionally want to insert or extract
markup—such as templating frameworks, building elements from strings, or copying fragments of
HTML.
The `textContent` property returns the **text** content of an element and all its descendants. It
ignores HTML tags and scripts, and returns all text including that within `<script>` or `<style>`
elements. Setting `textContent` on a node removes all existing children and inserts a single text node
with the provided string.
```html
<script>
</script>
```
- It **does not parse HTML**. Characters like `<` and `>` are treated as plain text.
- It includes text from `<script>` and `<style>` elements when reading. If you want only
human-readable text, consider using `innerText` (which is aware of styling and excludes hidden text),
although `innerText` triggers layout reflows and should be used sparingly.
- It is generally **faster and safer** for inserting or retrieving plain text because the browser doesn't
have to run the HTML parser or manage potential scripts.
351
## Which one should you use?
Use `innerHTML` when you need to work with HTML fragments—adding or retrieving markup.
Always sanitize or trust the input. Use `textContent` when you only need to handle text, especially if
the text may include characters that look like HTML. It prevents injection attacks and avoids
triggering a reflow when reading.
| Performance when writing | Replaces all children and reparses HTML | Replaces children with a
single text node |
| XSS risk | High if input is not sanitized | Low, since markup is escaped |
| Common use cases | Rendering templates, copying HTML, complex UIs | Setting or reading
user-visible plain text |
## Real-world analogy
Think of `innerHTML` as giving someone a bowl of ingredients and a recipe—the browser must cook
(parse) the recipe into a finished dish. If you hand over spoiled or dangerous ingredients (unsanitized
user input), you risk poisoning the dish. `textContent` is like handing over a sealed, pre-cooked
meal—no interpretation is needed and there's no risk of hidden hazards.
## Practice questions
1. **Theory:** Explain the security implications of using `innerHTML` with user-provided input. How
can you mitigate these risks?
2. **Coding:** Write a function `setSafeText(element, str)` that sets the text of `element` using the
safest property for displaying plain text. Test it by passing strings containing HTML tags.
352
3. **Coding:** Create a script that reads the text of every `<p>` element on a page using
`textContent` and appends the lengths of these texts after each paragraph.
4. **Theory:** Compare `textContent` with `innerText`. In what scenarios would you prefer one over
the other?
5. **Theory:** Why might setting `innerHTML` cause performance issues if used repeatedly in a
loop? Suggest a more efficient alternative for appending many elements.
These exercises will help you choose the right property and write secure, efficient DOM manipulation
code.
353
What are custom events and how do you dispatch
them?
# Custom events and how to dispatch them
Web pages are built around **events**. A click, keypress or network response triggers an event that
bubbles through the DOM. While browsers provide many built-in events, you can also define your
own to signal that something application-specific has happened. These are called **custom
events**.
Imagine you have a complex widget composed of smaller components. When one component
finishes a task (say, a form validates successfully), it needs to tell its parent about it without tightly
coupling the two. A custom event provides this channel: the child dispatches a named event on itself;
the parent listens for that name and reacts accordingly.
You create a custom event with the `CustomEvent` constructor. The first argument is the event type
(a string) and the second is an optional object where you can attach data:
```js
});
```
The `detail` property can contain any serialisable data. The `bubbles` flag determines whether the
event propagates up through ancestor elements. If you omit `bubbles`, the event will fire only on the
target element.
354
Custom events are dispatched using `dispatchEvent()` on any `EventTarget` (elements, `window`,
`document`, etc.):
```js
[Link](todoAdded);
```
When the event is dispatched, any listeners for `'todoAdded'` on the target or its ancestors (if
`bubbles` is `true`) will run. You add listeners with `addEventListener()` just like for native events:
```js
});
```
The event object passed to the handler has the usual properties (`type`, `target`, `currentTarget`,
`bubbles`) plus your data on `detail`. You can call `stopPropagation()` to prevent the event from
bubbling further or `preventDefault()` only if `cancelable` is `true` and you've defined a default
action.
Suppose you fetch data in a component and want to notify consumers that loading has finished.
Here's an example using a custom event:
```js
// [Link]
async connectedCallback() {
355
const res = await fetch("/api/users");
[Link](
new CustomEvent("data-loaded", {
bubbles: true,
})
);
// parent component
[Link]("Users:", [Link]);
});
```
By using a custom event, the `DataLoader` component doesn't need to know who is interested in the
data; it simply broadcasts that the data is ready.
- **Name events clearly.** Event names are case-sensitive, so `taskDone` and `taskdone` are
different events. Use a naming scheme (like kebab-case) to avoid collisions.
- **Use the `detail` property** to transmit data; avoid attaching arbitrary properties to the event
object.
- **Consider bubbling** when the event should be handled by ancestors, but avoid unnecessary
bubbling on large DOM subtrees to reduce overhead.
- **Don't overuse custom events.** For tightly coupled components, direct method calls or callback
functions are simpler. Custom events shine when decoupling components or implementing plugin
systems.
356
## Practice questions
1. **Theory:** Explain the difference between a built-in DOM event (such as `click`) and a custom
event created with `CustomEvent`.
2. **Theory:** What happens if you dispatch a custom event without setting the `bubbles` option?
How does it affect event propagation?
4. **Coding:** Given a component that validates user input, dispatch a `formValid` event with a
boolean indicating success. Demonstrate how a parent element listens for this event.
5. **Theory:** When would you choose to prevent the default action of a custom event? How do
`cancelable` and `preventDefault()` work together in the context of custom events?
357
Explain microtask queue vs nextTick in [Link]
# Microtask queue vs `[Link]()` in [Link]
JavaScript runtimes use an **event loop** to interleave work so the main thread can handle user
interactions, I/O and timers without blocking. Within each loop iteration there are two important
queues of callbacks: **macrotasks** (also called tasks) and **microtasks**.
Understanding the distinction between the microtask queue and Node's special `[Link]()`
queue helps you write efficient asynchronous code without starving I/O.
Microtasks are scheduled by features such as `Promise` resolutions, `queueMicrotask()` and (in
browsers) `MutationObserver`. After the currently executing script finishes and the call stack
unwinds, the event loop processes **all** microtasks in FIFO order before moving on to the next
macrotask. This guarantees that promise callbacks run as soon as possible, keeping program state
consistent:
```js
[Link]("script end");
// logs:
// script end
// microtask 1
```
Because the microtask queue is drained completely before the next task, adding more microtasks
within a microtask will delay the event loop until the queue is empty. Overusing microtasks
(especially in loops) can delay timers and I/O.
## `[Link]()`
358
[Link] provides its own queue processed even **earlier** than the microtask queue. Calling
`[Link]()` schedules a callback to run **immediately after the current operation
completes**, before any pending microtasks or I/O events:
```js
[Link]("start");
[Link]("end");
// output:
// start
// end
// nextTick
// microtask
```
Here, the `nextTick` callback runs before the promise's `.then()` handler. Node uses `nextTick()`
internally for its own housekeeping (such as emitting the `'exit'` event). Because callbacks scheduled
with `[Link]()` run before I/O, it's possible to starve the event loop. If a nextTick callback
recursively schedules itself, it can block the processing of timers or network events.
You might use `nextTick()` when you need to run code **immediately** after the current function
finishes but before any promise handlers or I/O. Common use cases include:
- **Error handling:** Ensure error listeners are attached before emitting an error event.
- **Breaking up synchronous work:** Large synchronous operations can block the event loop.
Splitting work across multiple `nextTick()` calls yields control back to Node between chunks.
However, prefer `setImmediate()` or promises when you simply need to schedule code on the next
iteration of the event loop. They run after I/O callbacks, avoiding starvation.
## Summary of differences
359
## Practice questions
1. **Theory:** What is the order of execution between `[Link]()`, promise microtasks and
timer callbacks? Explain with a simple example.
2. **Coding:** Write a [Link] script that logs messages in the following order: `script`, `nextTick`,
`microtask`, `timer`. Use `[Link]()`, `[Link]().then()` and `setTimeout()`.
3. **Theory:** Why can overusing `[Link]()` lead to starvation of I/O? How can you
mitigate this risk?
4. **Coding:** Convert a synchronous loop that blocks the event loop for 100 ms into a non-blocking
loop using `[Link]()` or `setImmediate()`. Observe the differences.
360
What is the difference between V8 engine internals and
standard JavaScript?
# Difference between V8 engine internals and standard JavaScript
JavaScript is defined by the **ECMAScript specification**, which describes the syntax and behaviour
of the language. A JavaScript engine is an implementation of that specification. **V8** is one of
those engines. It powers Google Chrome, [Link], Deno and many other environments.
Understanding the difference between the two helps explain why some features exist in one
environment but not another.
## Standard JavaScript
The ECMAScript specification (often shortened to **ECMA-262**) defines how the language
behaves: how variables are scoped, how functions are called, how promises work and so on. It
deliberately leaves out details about performance, memory layout or integration with operating
systems. When you write `const x = 5`, you're using standard JavaScript syntax and semantics that
any conforming engine should implement.
The spec also defines _built-in_ objects like `Array`, `Promise` and `Map`. It does **not** define
host-specific APIs such as `document`, `console`, `require()` or file system access. Those are provided
by the host environment (browser, [Link]) on top of the core language.
## V8 engine internals
V8 is written in C++ and implements the ECMAScript spec. It also implements Web APIs (when
embedded in Chrome) or Node APIs (when embedded in [Link]) by exposing additional objects.
Internally, V8 performs many optimisations to execute JavaScript quickly:
- **Hidden classes and inline caching.** Unlike statically typed languages, JavaScript objects can
change shape at runtime. V8 groups objects with the same property layout into hidden classes.
When properties are accessed, V8 can look up their offset quickly using the hidden class and caches
the location for subsequent accesses. This avoids expensive dictionary lookups on every property
access.
361
- **Generational garbage collection.** V8 allocates objects in a young generation and assumes most
die young. The young generation is collected frequently, and surviving objects are promoted to an
old generation. This reduces pause times compared to scanning the entire heap.
These internal features are not visible in standard JavaScript code—there is no API to control hidden
classes or trigger JIT compilation—but understanding them can inform best practices: define all
properties in your constructor to avoid creating multiple hidden classes, avoid adding new properties
to objects on the fly and refrain from mixing types in arrays.
Because V8 is embedded in different hosts, it exposes different global objects depending on where it
runs:
These APIs are not part of the ECMAScript spec; they are provided by the host to interact with the
environment (DOM, file system, network). This explains why code that uses
`[Link]()` fails in [Link] and why Node's `require()` is unavailable in browsers.
## Practice questions
1. **Theory:** What aspects of JavaScript are defined by ECMAScript, and what aspects are left to
host environments? Give examples of each.
2. **Theory:** Explain how V8's hidden classes and inline caching improve property access
performance. How can code structure influence hidden class generation?
3. **Theory:** Why is there no standard API to control the JIT compiler or garbage collector in
JavaScript? What would be potential issues if such control existed?
4. **Coding:** Write a constructor function that sets all properties on the instance in the same
order each time. Explain why this approach benefits V8's hidden class optimisation.
5. **Theory:** Compare the global objects available in the browser and [Link]. Why are some APIs
like `fetch()` available in both, while others like `require()` are environment-specific?
362
How does just-in-time (JIT) compilation work in
JavaScript engines?
# How just-in-time (JIT) compilation works in JavaScript engines
JavaScript began life as an interpreted scripting language. Early engines read source code and
executed it directly. To improve performance, modern engines use **just-in-time (JIT)
compilation**: they compile frequently executed code to machine instructions at runtime. JIT
compilers combine the flexibility of dynamic languages with the speed of compiled languages.
1. **Parser and interpreter:** Source code is parsed and compiled into bytecode for a lightweight
interpreter (Ignition). This allows quick start-up and supports dynamic features like `eval()`.
2. **Profiler:** As code runs, the engine records how often functions are called, what types
arguments and return values have and which branches are taken. This data is called _type feedback_.
3. **Optimising compiler:** When a function becomes "hot" (executed many times), the engine
compiles it with an optimising compiler (TurboFan in V8, IonMonkey in SpiderMonkey). The compiler
uses type feedback to specialise the code: it may assume that a variable is always a number and
generate faster machine code for that case.
The optimising compiler performs aggressive transformations: inlining small functions, eliminating
bounds checks, constant folding and removing dead code. If the assumptions prove true, the code
runs much faster than interpreted code.
## Deoptimisation
JavaScript is dynamic. A variable that was always a number may suddenly become a string. If that
happens, the assumptions in the compiled code no longer hold. Engines handle this by
**deoptimising**: they bail back to the interpreter, patching up the program state, and collect new
feedback. The function may be recompiled with updated assumptions or continue in the interpreter.
Deoptimisation makes JIT compilation invisible to developers. You don't need to think about types
ahead of time, but writing code with consistent types helps the compiler stay in optimized code
paths.
363
## Benefits and trade-offs
- **Speed:** JIT-compiled code can be as fast as native code when the compiler's assumptions hold.
Hot loops and heavy calculations benefit the most.
- **Startup cost:** Compiling code takes time. That's why engines interpret code first and only
compile hot functions. Very short scripts may not get JIT-compiled at all.
- **Memory:** JIT compilers store multiple versions of compiled code (baseline and optimized) and
metadata. On devices with limited memory, engines may limit JIT usage.
- **Security:** Generating executable memory at runtime has security implications (spectre
mitigations, write-execute permissions). Browsers implement safeguards like memory page
protection and pointer authentication.
- **JavaScriptCore (Safari):** LLInt (low-level interpreter), Baseline JIT (DFG), and the C Loop
interpreter.
Some environments, such as older mobile browsers or embedded systems, may disable JIT for
security or resource reasons. In such cases, code runs entirely in the interpreter.
## Practice questions
1. **Theory:** Describe the roles of the interpreter, profiler and optimising compiler in a modern JS
engine's pipeline. Why is a two-tier strategy used instead of immediately compiling everything?
2. **Theory:** Explain how type feedback enables faster machine code. What happens when an
assumption is violated?
3. **Theory:** List some optimisations that an optimising JIT compiler might perform on JavaScript
code. How do they improve performance?
364
4. **Coding:** Write a function that performs a numeric calculation in a tight loop. Run it multiple
times and observe the difference in performance with and without type changes (e.g., switching a
number to a string midway). Explain why the engine might deoptimise and reoptimise your function.
5. **Theory:** Discuss the security considerations of JIT compilation. What measures do browser
vendors take to mitigate risks associated with generating executable code at runtime?
365
Explain hidden classes and inline caching in V8
# Hidden classes and inline caching in V8
JavaScript is a dynamic language: you can add or remove properties from objects at any time. While
this flexibility is powerful, it presents challenges for performance. The V8 engine uses two key
techniques—**hidden classes** and **inline caching**—to make property access fast without
sacrificing dynamism.
In languages like C++ each class has a fixed layout that tells the runtime where to find fields.
JavaScript objects do not have classes in the same way; each object's properties could be different.
V8 bridges this gap by creating an internal _hidden class_ for each distinct object shape. A hidden
class records the names and order of an object's properties and maps them to fixed offsets in
memory.
When you create an object, V8 assigns it a hidden class based on the properties you define. Adding a
new property transitions the object to a new hidden class. If two objects add the same properties in
the same order, V8 reuses the same hidden class, enabling them to share inline caches and compiled
code.
Because hidden classes track the order in which properties are added, defining properties
consistently yields fewer class transitions:
```js
function Point(x, y) {
this.x = x;
this.y = y;
366
const p2 = new Point(3, 4);
// Avoid adding properties later, which would create a new hidden class:
```
By declaring all properties in the constructor, you help V8 reuse hidden classes, enabling better
optimisation and inline caching. Adding properties outside the constructor or in different orders
causes V8 to create new hidden classes, preventing code sharing.
## Inline caching
When V8 executes a property access like `obj.x`, it must find where `x` lives in memory. Without
optimisation, it would perform a dynamic lookup every time. Inline caching (IC) speeds this up by
remembering the hidden class of the object and the offset of the requested property.
- The first time `obj.x` is executed, V8 does the full lookup and stores the resulting hidden class and
offset in a tiny cache attached to the instruction.
- The next time the same instruction runs and the object has the same hidden class, V8 can skip the
lookup and jump straight to the stored offset.
- If the object has a different hidden class, V8 falls back to a slower path and may update the inline
cache to reflect the new shape (polymorphic inline caching).
Inline caches dramatically speed up property reads and writes when objects are consistently shaped.
They also collect type feedback for the JIT compiler; if the cache sees many different shapes, the
compiler may treat the operation as polymorphic and generate a more general (slower) code path.
- **Initialise all instance properties in the constructor**. Avoid adding properties later.
- **Add properties in the same order** for all instances of a given "class".
- **Avoid mixing unrelated types** in arrays and object fields. Homogeneous arrays enable better
optimised code.
367
- **Delete properties sparingly**, as deletion also changes the hidden class and can deoptimise
code.
## Practice questions
1. **Theory:** What is a hidden class in V8 and how does it differ from a traditional class in
languages like Java or C++?
2. **Theory:** Describe how adding properties in different orders affects hidden classes. Why is it
beneficial to initialise all properties in the constructor?
3. **Theory:** Explain the concept of inline caching. How does it speed up repeated property
accesses?
4. **Coding:** Create a constructor that defines three properties. Instantiate two objects and
demonstrate how adding a property to one instance can lead to different hidden classes and
potentially slower code.
5. **Theory:** What are the potential downsides of relying on inline caching? Under what
circumstances might an inline cache become polymorphic or megamorphic, and how does that affect
performance?
368
What are WeakRefs and FinalizationRegistry?
# Weak references and the `FinalizationRegistry`
Modern JavaScript engines use garbage collection to free memory when objects are no longer
reachable. Most of the time you can ignore memory management, but some patterns—such as
caches—require you to hold references to objects without preventing their collection. **Weak
references (WeakRef)** and **`FinalizationRegistry`** provide mechanisms for that.
## Weak references
A normal (strong) reference prevents an object from being garbage-collected. A _weak reference_
allows you to refer to an object without affecting its liveness. If the object becomes unreachable
from the rest of your code, the garbage collector can reclaim it even though a `WeakRef` still exists.
You create a weak reference with `new WeakRef(target)`. The only operation on a `WeakRef` is
`.deref()`, which returns the original object if it's still alive or `undefined` if it has been collected:
```js
function getUser(id) {
if (!user) {
user = loadUserFromDB(id);
return user;
```
Here, the cache doesn't prevent `user` objects from being freed if nothing else refers to them. When
you call `[Link]()`, you either get a live object or `undefined`, in which case you reload it.
369
Weak references are primarily useful for caches, memoisation and other data structures where stale
values can be recomputed. You should not use them to manage critical resources; rely on strong
references and proper cleanup instead.
## The `FinalizationRegistry`
`FinalizationRegistry` lets you register a callback that the engine will call after a particular object is
garbage-collected. The constructor takes a cleanup function; you then call `.register(target,
heldValue)` to associate a target object with a "held" value (often a resource identifier). When
`target` is collected, the registry queues the held value for cleanup:
```js
});
[Link](obj, id);
// later
const resource = {
/* ... */
};
trackResource(resource, "socket:1234");
```
The callback runs at some time _after_ the object has been reclaimed. The specification makes no
guarantee about when or even if the callback will execute. The callback is invoked in a separate task,
so you cannot depend on it for essential finalisation (e.g. closing files or releasing locks). Use it
instead to clean up ancillary caches or to log when objects are collected.
370
## Caveats and warnings
- Weak references and finalisation can expose details of the garbage collector. Overusing them may
make code unpredictable or brittle.
- There is no guarantee that a finalizer will run in a timely manner; it may never run if the process
exits before collection. Always provide explicit cleanup methods when working with critical
resources.
- Only use `FinalizationRegistry` when you have no other way to detect object lifecycle events. For
example, a `Map` of objects keyed by ID should use explicit `delete()` instead of relying on
finalisation.
## Practice questions
1. **Theory:** Why might you use a `WeakRef` rather than a normal reference in a cache? What
happens when the object referenced by a `WeakRef` is garbage-collected?
2. **Theory:** Describe the lifecycle of a finalizer registered via `FinalizationRegistry`. Why is it not
safe to put critical cleanup in a finalizer callback?
3. **Coding:** Implement a memoisation function that caches results using `WeakRef` so that
entries can be reclaimed when their keys are no longer used.
4. **Theory:** List potential pitfalls of using `WeakRef` and `FinalizationRegistry`. How would you
design your code to avoid these pitfalls?
5. **Coding:** Demonstrate how you can register an object with `FinalizationRegistry` and observe
when the cleanup callback runs by creating and discarding objects inside a loop. (Hint: You may need
to force garbage collection in a controlled environment for testing.)
371
How does debounce–throttle combo optimize
performance?
# How a debounce-throttle combination optimises performance
Many user interactions fire events rapidly: resize, scroll, keyup or window resize can trigger dozens or
hundreds of callbacks per second. If each event performs expensive work (rendering, network
requests), performance suffers. **Debouncing** and **throttling** are two techniques for limiting
how often a function runs. Combining them allows you to tailor responsiveness and resource use.
## Quick recap
- **Debouncing** delays execution of a function until a certain time has passed without another
trigger. Each new call resets the timer. Debounce is useful when you only care about the final event
in a burst—such as submitting a search after the user stops typing.
- **Throttling** allows a function to run at most once every `N` milliseconds. Additional calls within
the interval are ignored. Throttle is ideal when you want periodic updates, like repositioning
elements while the user scrolls.
Sometimes you need the best of both worlds: provide an immediate response, update periodically
during a burst and ensure a final update after activity stops. A **debounce-throttle combo** does
exactly that. A common implementation runs the function at the beginning and end of a burst, but
throttles intermediate calls.
Here is a simple implementation with options for leading (immediate) and trailing (final) calls:
```js
let lastCall = 0;
let timerId;
372
clearTimeout(timerId);
if (remaining <= 0) {
lastCall = now;
[Link](this, args);
} else {
lastCall = [Link]();
[Link](this, args);
}, remaining);
};
}, 200);
[Link]("resize", handleResize);
```
In this example, the function runs immediately on the first resize event and then at most once every
`delay` milliseconds. If events stop, a final trailing call ensures the latest state is processed. This
pattern keeps the UI responsive while avoiding unnecessary work.
- **Responsive UI updates.** For scroll or mousemove handlers, you may want to update layout on
the first event and periodically thereafter, but still handle the final state.
- **Search suggestions.** Provide an instant suggestion as the user types but avoid hitting the API
on every keystroke; throttle intermediate calls and debounce the final call.
373
- **Window resize recalculations.** Immediately adjust layout, then throttle continuous updates
and finally apply finishing touches when resizing stops.
## Practice questions
1. **Theory:** Explain the difference between debouncing and throttling. Give an example use case
for each.
2. **Theory:** Why might you combine debouncing and throttling rather than use only one?
Describe a scenario where a combo is beneficial.
3. **Coding:** Implement a `debounceThrottle` function that accepts options `{ delay, leading,
trailing }` to control whether the wrapped function runs at the start of the burst, end of the burst or
both.
5. **Theory:** What risks arise if you debounce a function that handles essential state updates (e.g.
resizing a canvas)? How can a throttle or combo alleviate those risks?
374
What are ArrayBuffer and TypedArray?
# ArrayBuffer and TypedArray
JavaScript strings and objects are convenient for text and structured data, but they are not suitable
for handling raw binary data such as images, audio streams or network packets. The **ArrayBuffer**
and **TypedArray** APIs provide a way to work with binary data in memory, enabling you to read
and write bytes directly.
## ArrayBuffer
```js
[Link]([Link]); // 16
```
You cannot read or write individual bytes directly through the ArrayBuffer. Instead, you create a
view—either a typed array or a `DataView`—that provides typed access to its contents.
## Typed arrays
Typed arrays are array-like objects that view and manipulate binary data in an ArrayBuffer. Each
typed array type corresponds to a specific numeric format (e.g., `Uint8Array` for 8-bit unsigned
integers, `Int32Array` for 32-bit signed integers, `Float64Array` for double-precision floats). Creating a
typed array allocates or attaches to a buffer:
```js
bytes[0] = 255;
375
[Link]([1, 2, 3], 1);
ints[0] = 42;
ints[1] = -1;
```
Typed arrays share many methods with normal arrays (`map`, `forEach`, `filter`) but lack some (like
`push`, because their length is fixed). They are not true `Array` objects; `[Link]()` returns `false`
for typed arrays. All typed arrays have a `buffer` property referencing their underlying ArrayBuffer,
and they view data starting at an **offset** for a **length**. Multiple views can reference the same
buffer with different offsets and element types, allowing you to interpret the same bytes in various
ways.
## DataView
While typed arrays interpret data in fixed element sizes, `DataView` lets you read and write arbitrary
numbers of bytes with specific endianness. It is useful when the data format doesn't align neatly with
typed array element sizes or when working with network protocols that require big-endian order:
```js
```
## Use cases
376
- **Binary network protocols:** WebSockets and WebRTC can send ArrayBuffers directly, allowing
efficient binary transmission.
- **Multimedia:** Audio and video APIs use typed arrays for PCM data and pixel buffers.
- **WebGL:** Vertex buffers and textures are supplied as typed arrays to WebGL for rendering.
## Practice questions
1. **Theory:** What is the relationship between an ArrayBuffer and a typed array? Why can't you
read or write bytes directly on an ArrayBuffer?
2. **Theory:** Describe how typed arrays differ from regular JavaScript arrays. What methods do
they share and which do they lack?
3. **Coding:** Create an `ArrayBuffer` of length 12 bytes. View it as both `Uint8Array` and
`Float32Array` and demonstrate how changing one view affects the other.
4. **Coding:** Use a `DataView` to write a 32-bit big-endian integer at offset 0 and then read it back
as two 16-bit unsigned integers. Explain what happens.
5. **Theory:** Why might you choose a typed array over a regular array when working with WebGL
or other low-level APIs?
377
Explain SharedArrayBuffer and Atomics API
# SharedArrayBuffer and the `Atomics` API
Web workers allow JavaScript to run in parallel threads, but normally each worker has its own
heap. Data is copied between threads via structured cloning. For high-performance scenarios like
games, scientific simulations or shared caches, copying large amounts of data becomes a
bottleneck. **SharedArrayBuffer** and the **`Atomics`** API solve this by enabling shared
memory and synchronised access.
## SharedArrayBuffer
A **SharedArrayBuffer** is like an `ArrayBuffer` but its memory can be shared between multiple
execution contexts (the main thread and workers). Any view (typed array or `DataView`) created
from the same SharedArrayBuffer sees the same underlying bytes. Changing a byte in one view
immediately affects all views.
```js
// [Link]
sharedInts[0] = 42;
[Link](sab);
// [Link]
shared[0] = shared[0] + 1;
378
// The main thread sees this change instantly
};
```
Because `SharedArrayBuffer` allows threads to modify the same memory, it introduces race
conditions. Without coordination, updates can interleave unpredictably. To coordinate reads and
writes, you must use **atomic operations**.
The `Atomics` object provides atomic operations on shared typed arrays. Atomic operations are
indivisible: no other thread can observe a partial update. `Atomics` includes functions for reading
and writing (`load`, `store`), arithmetic (`add`, `sub`, `and`, `or`, `xor`), compare-and-swap
(`compareExchange`) and waiting/notification (`wait`, `notify`). These functions operate on integer
typed arrays (`Int8Array`, `Uint16Array`, `Uint32Array`, etc.) backed by a SharedArrayBuffer.
`[Link]()` causes the calling thread to block until another thread calls `[Link]()` on the
same location or a timeout expires. This provides a simple building block for implementing mutexes,
semaphores and other synchronisation primitives. For example, a shared ring buffer can coordinate
producer and consumer threads:
```js
function produce(value) {
[Link](queue, i + 2, value);
379
[Link](queue, readIndex);
function consume() {
while (true) {
} else {
// process value
```
This example shows how `Atomics` can implement a circular buffer without busy waiting.
## Security considerations
Because SharedArrayBuffers expose new side-channel attacks (Spectre), browsers require pages to
opt in to **cross-origin isolation** and run in a secure context (HTTPS) before allowing
SharedArrayBuffer usage. You must set the `Cross-Origin-Opener-Policy: same-origin` and `Cross-
Origin-Embedder-Policy: require-corp` headers. Without these headers, `SharedArrayBuffer` will
throw a `SecurityError`.
## Practice questions
1. **Theory:** Explain why copying data between workers can be inefficient. How does a
SharedArrayBuffer solve this problem?
380
2. **Theory:** Why are atomic operations necessary when using SharedArrayBuffer across
threads? What could go wrong if you access the buffer without `Atomics`?
3. **Coding:** Create a main thread and a worker that share a `SharedArrayBuffer` of four 32-bit
integers. Have the worker increment the first value using `[Link]()` and notify the main thread
using `[Link]()`. The main thread should wait for the update with `[Link]()` and then
log the new value.
381
What is structured concurrency (upcoming spec)?
# Structured concurrency: an upcoming JavaScript proposal
Asynchronous programming allows a program to perform multiple tasks seemingly at once, but
unstructured concurrency can lead to "zombie" tasks that continue running after the function that
spawned them has returned. **Structured concurrency** is a design principle and proposed
specification that aims to tame asynchronous code by ensuring that tasks have a well-defined
lifetime and that errors propagate predictably.
In JavaScript today you can fire off a promise or `setTimeout()` without awaiting it. For example:
```js
```
The `fetchAndLog()` function returns immediately, and the caller has no control over or awareness of
the ongoing fetch. If an error occurs, it may go unhandled. If the caller is cancelled or times out, the
fetch keeps running. In larger systems, these orphaned tasks can accumulate and cause resource
leaks.
Structured concurrency proposes that asynchronous operations should be scoped to their parent
function or block. When the parent completes, any child tasks should automatically be awaited or
cancelled. The key ideas are:
382
- **Task scoping:** Spawned tasks are tied to a context. They cannot outlive it.
- **Failure propagation:** If a child task errors, the error bubbles up to the parent, allowing a single
place to catch exceptions.
Other languages implement structured concurrency through constructs like Go's `[Link]`,
Python's `asyncio` task groups or Kotlin's coroutines. The JavaScript proposal aims to provide similar
capabilities via native APIs.
As of this writing, structured concurrency is an early-stage proposal. One experimental API uses
**task functions** and **cancellation tokens**. A simplified sketch might look like this:
```js
// hypothetical API
try {
} finally {
```
In this sketch, `startTask()` spawns a task tied to a token. If one child throws, the token is cancelled
and all tasks clean up. The parent waits on children before returning, ensuring no task leaks.
383
## Current alternatives
Until the proposal is standardised, you can achieve similar structure by manually tracking promises
and cancellations:
- **`AbortController`** and **`AbortSignal`**: Many web APIs support abort signals for
cancellation. Pass the same signal to all concurrent operations and call `abort()` in a `finally` block.
- **Helper libraries**: Libraries like `p-limit` and `taskgroup` implement structured concurrency
patterns for [Link].
- **Error handling wrappers**: Wrap asynchronous functions so that thrown errors or rejections
propagate to a common error handler instead of being lost.
## Practice questions
1. **Theory:** What problems arise when asynchronous tasks outlive their spawning function? Why
is this problematic in long-running applications?
2. **Theory:** Explain the main principles of structured concurrency. How do they differ from
ad-hoc spawning of promises?
3. **Coding:** Using `AbortController`, write an `async` function that starts two fetches in parallel
and cancels both if either rejects or if the parent function returns early.
4. **Theory:** How do other languages (e.g. Go, Python, Kotlin) implement structured concurrency?
Identify similarities and differences with the proposed JavaScript approach.
5. **Theory:** Discuss potential challenges in adding structured concurrency to the existing event
loop and promise model. How might backward compatibility and cancellation semantics be handled?
384
How does the ECMAScript spec define execution order?
# How the ECMAScript specification defines execution order
Within a single script, ECMAScript defines evaluation rules that determine the order in which
expressions are executed. Key points include:
- **Left-to-right evaluation**: In most binary expressions (`a + b`, `f(x, y)`), the left operand is
evaluated before the right. Function arguments are evaluated from left to right. This means side
effects occur in a predictable order:
```js
function log(val) {
[Link](val);
return val;
```
- **Execution context stack**: Each time a function is called, a new execution context is pushed onto
the call stack. The context holds local variables, the value of `this` and the point to return to. When a
function returns, its context is popped and control resumes at the previous context.
- **Hoisting**: Declarations of variables and functions are processed before code execution begins.
Function declarations are hoisted with their body; `var` declarations are hoisted but initialised with
`undefined`; `let` and `const` declarations are hoisted but remain in the "temporal dead zone" until
initialised.
385
These rules ensure that synchronous code executes deterministically.
JavaScript environments are single-threaded, but they handle asynchronous operations by deferring
work to the environment (e.g. the browser or Node's libuv) and then queueing callbacks. The
ECMAScript spec doesn't define the event loop itself but references it for tasks like promises and
modules. Hosts specify the details, but common behaviour is:
2. **Task queue**: When asynchronous operations (timers, I/O, user events) complete, their
callbacks are queued as **tasks** (macrotasks). Examples include `setTimeout`, `setInterval`, DOM
events and `requestAnimationFrame`.
3. **Microtask queue**: Promise reaction jobs and `queueMicrotask()` callbacks are queued as
**microtasks**. At the end of each task, the environment processes the microtask queue until it is
empty.
4. **Rendering**: In browsers, after microtasks, the rendering engine may update the UI. Then the
event loop proceeds to the next task.
This order explains why `[Link]()` runs before `setTimeout()` callbacks, and why microtasks
added within microtasks run before the next macrotask.
The specification also defines the order in which object properties are iterated. `[Link]()`,
`for...in` and `[Link]()` list:
## Module evaluation
386
ECMAScript modules are loaded and executed in dependency order. The spec describes how module
records are created, linked and evaluated. The host ensures that modules are fetched and parsed;
the spec ensures that imported bindings are initialised before executing module code. Modules
execute in top-level scope with their own module context; evaluation is asynchronous when using
dynamic `import()`.
## Practice questions
1. **Theory:** In what order are function arguments evaluated in JavaScript? Give an example
where the evaluation order affects the result.
2. **Theory:** Describe what happens when a function calls itself recursively. How does the
execution context stack handle nested calls?
3. **Coding:** Write a script that logs numbers using `setTimeout()`, promises and synchronous logs
to demonstrate the order in which tasks and microtasks run. Explain the observed output.
4. **Theory:** Explain the property enumeration order for `for...in` loops. How does it differ for
numeric keys versus string keys?
5. **Theory:** How does the event loop ensure that the UI remains responsive while JavaScript
code executes? What happens if you queue too many microtasks without yielding control?
387
Explain [Link] and environment
internal slots
# `[Link]()` and internal slots
JavaScript functions are objects with special behaviour defined by the ECMAScript specification. They
provide useful methods and internal state. Two important aspects are the
`[Link]()` method—used to obtain a function's source code—and the
**internal slots** that hold information like the environment where the function was created.
Every function instance inherits a `toString()` method that returns a string representation of its
source code. For user-defined functions, this is the exact source text used to define the function,
including whitespace and comments. If the function was created via the `Function` constructor, the
returned string synthesises a function declaration named `anonymous`. For built-in functions (those
implemented by the host), it returns a generic `[native code]` string.
Examples:
```js
function add(a, b) {
return a + b;
[Link]([Link]());
/* empty */
};
388
[Link]([Link]());
[Link]([Link]());
```
The ECMAScript specification defines objects using **internal slots**—hidden properties that
cannot be accessed from user code. Function objects have several internal slots, including:
- **[[Environment]]** - the lexical environment (scope) where the function was created. This slot
enables closures: the function remembers variables from its defining scope.
- **[[Realm]]** - the realm (global object and intrinsics) where the function was created. Different
realms have different copies of built-in constructors and methods.
These slots are conceptual, not properties you can read or write. They explain how the language
works under the hood. When you call a function, the engine creates a new execution context and
binds the function's `[[Environment]]` as its outer scope. This allows inner functions to access
variables defined outside their body.
A **realm** is an execution context containing a global object and a set of intrinsic objects (`Array`,
`Object`, etc.). When you evaluate code in a different realm (for example, in an iframe), functions
created there have their `[[Realm]]` pointing to that realm. Their prototypes and constructors come
from that realm's intrinsics. This explains why `instanceof` checks can fail across frames: each realm
has distinct constructor functions.
389
## Practice questions
2. **Theory:** Why does calling `eval()` on the result of `[Link]()` throw a syntax error?
3. **Theory:** Describe the purpose of the `[[Environment]]` internal slot. How does it enable
closures?
4. **Coding:** Write a function that captures a variable from its outer scope and use `toString()` to
show the function's source. Explain why the function still has access to the captured variable when
invoked later.
5. **Theory:** Explain what a realm is in JavaScript and how the `[[Realm]]` internal slot affects
things like `instanceof` across iframes.
390
What is the Realms API and why might it matter for
sandboxing?
# The Realms API and sandboxing
JavaScript code normally runs in a single **realm**—an environment consisting of a global object, a
global scope and a set of intrinsic constructors like `Array` and `Object`. In the browser, the default
realm is the top-level `window`. If you import code into this realm, it shares your globals and
intrinsics. For some applications, this sharing is undesirable: you might want to run third-party scripts
in isolation, without giving them access to your global state. That's where the **Realms API** comes
in.
## What is a realm?
A realm is a separate instance of the JavaScript execution environment. Each realm has its own global
object and its own copies of built-ins. Two objects from different realms have different constructors;
for example, `[Link]` in one realm is not the same object as `[Link]` in another.
Realms already exist implicitly: every browser iframe or Web Worker is a distinct realm. However, you
can't easily create a new realm from code today; you must rely on iframes or workers.
The **ShadowRealm** API is a TC39 proposal that allows you to create a new realm
programmatically. Its purpose is to enable sandboxing and to execute code with a fresh set of
intrinsics. A `ShadowRealm` instance exposes a method `evaluate(code)`, which runs the given string
of JavaScript in that realm and returns the result. Functions and values can be passed into and
returned from a `ShadowRealm`, but the objects themselves remain in their originating realm. This
prevents code in the shadow realm from mutating the host's global object or prototypes.
Example usage:
```js
391
const result = [Link]("1 + 2");
[Link](result); // 3
function greet(name) {
```
In this example, the code executed in the shadow realm cannot access the main global object or
modify built-ins. `[Link]()` produces a callable proxy that marshals arguments and return values
between realms.
- **Isolation:** Code executed in a new realm gets its own global scope and intrinsic constructors,
preventing prototype pollution of the host environment.
- **Controlled communication:** Only values explicitly passed in or returned by the realm boundary
are shared. Objects remain in their original realm unless explicitly wrapped.
392
- **No DOM access:** In the browser, a shadow realm has no access to the DOM or Web APIs unless
you pass in proxies. This makes it suitable for running untrusted scripts safely.
- **Portability:** Unlike `iframe` tricks, realms work consistently in any JavaScript host (browsers,
[Link]). They require no markup or cross-origin restrictions.
As of this writing, the Realms API (specifically the `ShadowRealm` proposal) is still under
development. It may evolve before standardisation, but the core idea—creating and isolating
execution contexts programmatically—remains the same. When supported, realms could simplify
sandbox implementations, plugin systems and testing environments.
## Practice questions
1. **Theory:** What is a realm in JavaScript? How do realms in different iframes differ from each
other?
2. **Theory:** Explain how the Realms API enables sandboxing. Why is separate global state
important when running untrusted code?
3. **Coding:** Use an iframe or Web Worker to create a separate realm in a browser today.
Demonstrate how objects created in one realm fail `instanceof` checks against constructors from
another realm.
4. **Theory:** What limitations remain even when using a ShadowRealm for sandboxing? Consider
things like network requests or CPU usage.
393
How does the module resolution algorithm work in
ESM?
# How module resolution works in ECMAScript modules
When you write an `import` statement in JavaScript, the runtime must locate the referenced module.
This process is called **module resolution**. The ECMAScript module (ESM) specification defines
how module specifiers map to module files, but leaves room for host environments to implement
details. Let's explore how browsers and [Link] resolve modules.
## Specifier types
- **Relative specifiers** start with `./` or `../` and resolve relative to the importing module's
location. For example, `import util from './[Link]';` loads a file named `[Link]` in the same folder as
the current module.
- **Absolute specifiers** begin with `/` and resolve from the origin (in browsers) or file system root
(in [Link]). For example, `import x from '/lib/[Link]';` refers to `/lib/[Link]` on your server.
- **Bare specifiers** are bare names like `react` or `lodash`. They reference packages installed in
`node_modules` or provided by the host. Browsers do not natively resolve bare specifiers; bundlers
or import maps are required.
- **Package import specifiers** start with `#` and resolve according to the package's `imports` field
in its `[Link]`. They are used for internal package modules.
## Resolution in browsers
In browsers, relative and absolute specifiers are resolved straightforwardly using URLs. The imported
path must include an explicit filename and extension (e.g., `.js`, `.mjs`, `.json`). If you omit the
extension, the browser doesn't guess—it will throw an error. To use packages from npm in the
browser, you need an **import map** or a bundler that rewrites bare specifiers to full URLs.
Example:
```html
394
<script type="module">
[Link](sum(2, 3));
</script>
```
An **import map** lets you specify how bare specifiers should be resolved:
```html
<script type="importmap">
"imports": {
"react": "[Link]
</script>
<script type="module">
[Link]([Link]);
</script>
```
## Resolution in [Link]
[Link] implements a more complex resolution algorithm for ES modules because it must support
local files, packages and package exports. At a high level:
1. If the specifier is a file URL or a relative specifier, Node resolves it to a file on disk relative to the
importing module's URL. If the file lacks an extension, Node searches for `.js`, `.json` and `.node`
(native addon) in that order.
395
2. If the specifier starts with `#`, Node looks at the importing package's `imports` field in its
`[Link]` to find a matching entry. These package import specifiers allow defining internal
module names that point to specific files.
3. Otherwise, the specifier is considered a bare specifier. Node resolves it as a package name in
`node_modules`. It finds the package directory, reads its `[Link]` and checks the `exports` field
to determine which file to load. If no `exports` field exists, Node falls back to CommonJS resolution
rules: looking for the `main` field, `[Link]` and so on. Node also honours `type: "module"` in
`[Link]` to know whether to treat `.js` files as ESM or CommonJS.
If none of these rules resolve the specifier, Node throws a "Module not found" error. Additionally,
Node does not allow implicit directory imports: `import x from './dir';` fails unless `./dir/[Link]` is
specified or exported via `[Link]`.
## Resolution errors
- **Invalid module specifier:** The specifier doesn't conform to valid URL or package name syntax.
- **Module not found:** The file or package does not exist at the resolved location.
- **Package path not exported:** When using package exports, the requested subpath isn't defined
in the `exports` field.
Understanding the resolution algorithm helps avoid surprises—especially when publishing packages.
Always specify your package's exports and imports fields, and avoid relying on implicit resolution
rules that may change.
## Practice questions
1. **Theory:** Explain the difference between relative, absolute and bare module specifiers. Give
examples of each and describe how they are resolved in the browser.
2. **Theory:** Why do browsers require file extensions on import specifiers while [Link] can
search for `.js` and `.json`? How do import maps help with bare specifiers in browsers?
3. **Coding:** Create a simple [Link] project with an `[Link]` file and a `[Link]` module.
Import functions using relative and bare specifiers and observe how Node resolves them.
396
4. **Theory:** What purpose do the `exports` and `imports` fields in `[Link]` serve? How do
they influence module resolution?
5. **Coding:** Define an import map that maps `'@utils'` to `/scripts/utils/`. Use it in an HTML file to
import a module with a bare specifier.
397
What are decorators and how do they extend class
behavior?
# Decorators and extending class behaviour
**Decorators** are a proposed JavaScript feature that allows you to modify classes and their
members (methods, fields, accessors) declaratively. A decorator is a function that runs at definition
time and can observe, replace or extend the thing it decorates. Decorators are widely used in
TypeScript and other languages (like Python) for metaprogramming. The ECMAScript decorators
proposal is currently at stage 3 and may change before final standardisation.
## Basic idea
A decorator is applied using the `@` syntax preceding a class or class member. For example:
```js
@sealed
class Person {
@logged
greet(name) {
```
Here, `sealed` and `logged` are decorator functions. They are invoked when the class definition is
evaluated, **before** any instances are created. The decorators receive metadata about the target
(the class or method) and can modify its behaviour.
## Class decorators
A **class decorator** is a function that takes the class constructor and context metadata. It can
replace or extend the class by returning a new constructor or adding static properties.
398
```js
[Link]([Link]);
[Link](target);
@sealed
```
The decorator seals both the class and its prototype, preventing new properties from being added. If
a decorator returns a new class, that class replaces the original in the scope where it was declared.
## Method decorators
Decorators on methods can wrap or modify the method. A method decorator receives the method
(as a function), its kind (e.g. `'method'`), the name, and a context object with utilities. It can return a
replacement function or access the original via `[Link]`.
```js
return result;
};
class Calculator {
399
@logged
add(a, b) { return a + b; }
```
Decorators can also define accessors (`get`/`set`) or fields. Field decorators can modify initial values
or create reactive properties.
Decorators are powerful because they run at definition time, allowing you to:
* **Wrap methods for cross-cutting concerns.** Logging, memoisation, validation and deprecation
warnings can be applied uniformly.
* **Register metadata.** Decorators can store metadata about classes and members, enabling
frameworks to perform dependency injection or routing.
* **Enforce invariants.** Class decorators can freeze or seal classes, preventing accidental
modifications.
* **Implement mixins.** A decorator can augment a class with additional methods or properties,
similar to a mixin, without explicitly inheriting from another class.
Because decorators modify definitions rather than runtime instances, they offer better ergonomics
than manually wrapping each method. However, they should be used judiciously; excessive
metaprogramming can make code harder to understand.
## Practice questions
1. **Theory:** What is the difference between a class decorator and a method decorator? When
does each run, and what arguments do they receive?
400
3. **Theory:** How can decorators be used to implement dependency injection or routing in a web
framework? Describe the mechanism at a high level.
4. **Coding:** Create a decorator `@memoize` that caches the result of a method based on its
arguments. Apply it to a method that performs an expensive calculation.
5. **Theory:** What are some potential downsides of using decorators extensively? How might
they affect readability and debugging?
401
Explain WeakMap-based private fields vs native private
fields () in classes
# WeakMap-based private fields vs native private fields in classes
Before JavaScript introduced **private class fields**, developers used patterns like closures and
**WeakMap** to emulate encapsulation. With the `#` syntax now available in modern JavaScript,
it's worth comparing the two approaches.
A WeakMap is a collection that maps objects to values without preventing garbage collection. To
emulate private data, you create a WeakMap outside the class and use the instance as the key and
the private data as the value. Because only code that has access to the WeakMap can retrieve the
data, it isn't directly exposed on the instance.
Example:
```js
class Account {
constructor(initial) {
_balance.set(this, initial);
deposit(amount) {
getBalance() {
return _balance.get(this);
402
const acc = new Account(100);
[Link](50);
[Link]([Link]()); // 150
```
Key characteristics:
- Privacy is enforced by closure scope: only the module or function that holds the WeakMap can
access the data.
- WeakMap entries are removed when the object (key) is garbage-collected, avoiding memory leaks.
- Accessing the private data requires a `[Link]()` call, which adds some overhead.
- The private data lives outside the instance; it cannot be inspected via reflection APIs like
`[Link]()`.
ES2019 introduced **private class fields** using the `#` prefix. These fields are declared directly in
the class body and are truly private: they are not properties on the object and cannot be accessed
outside the class.
```js
class Account {
#balance;
constructor(initial) {
this.#balance = initial;
deposit(amount) {
this.#balance += amount;
getBalance() {
return this.#balance;
403
}
```
- **Syntax level privacy:** Attempting to access a private field outside its class results in a syntax
error. There is no way to circumvent this without modifying the class definition.
- **Performance:** Access to `#balance` compiles down to fast property lookups. There is no need
for map lookups, so it is faster than using a WeakMap.
- **Encapsulation:** Private fields live on the instance itself (in an internal slot) rather than in an
external structure. They are not enumerable, cannot be deleted and are not visible to reflection APIs.
- **Interop with inheritance:** Private fields are not inherited by subclasses. Each class defines its
own private fields. If you need to share private state with subclasses, use `protected` patterns with
symbols or methods.
## Comparison
| Definition location | Outside the class (closure or module) | Inside the class using `#`
syntax |
| Garbage collection | Entries automatically removed when key dies | Stored on instance internal
slots |
| Inheritance | Data not directly accessible to subclasses | Private fields not inherited |
| Encapsulation strength | Depends on closure and module scoping | Enforced by the language
(syntax error) |
In modern code, prefer native private fields for clarity, performance and stronger encapsulation.
WeakMap patterns remain useful when you need private data associated with objects you don't
404
control (for example, augmenting DOM elements), or when targeting environments without private
field support.
## Practice questions
1. **Theory:** How does using a WeakMap provide privacy for instance data? What happens to the
WeakMap entry when the instance is garbage-collected?
2. **Coding:** Rewrite the WeakMap example above using native private fields. Compare the syntax
and readability.
3. **Theory:** Why are private fields not inherited in subclasses? How could you share state
between a superclass and subclass while keeping it encapsulated?
4. **Coding:** Show how you might store additional data for a DOM element using a WeakMap.
Explain why private fields cannot be used in that context.
5. **Theory:** What are potential downsides to using WeakMap for privacy when compared to
native private fields? Consider discoverability, performance and maintenance.
405
What are import assertions and why are they used?
# Import assertions and why they are used
ES modules allow you to import code and data. In addition to JavaScript, runtimes like [Link] and
browsers can import JSON, WebAssembly and other resources. Historically, support for these module
types has varied and often required non-standard loaders or bundlers. **Import assertions** (also
called **import attributes**) provide a standard way to convey metadata alongside the import
specifier, ensuring that modules are interpreted correctly.
An import assertion is a syntax that attaches an **attributes object** to an `import` statement. The
attributes specify how the module should be treated. The most common use is to assert the module
type for JSON imports.
Syntax:
```js
```
In this example, the `assert { type: 'json' }` clause informs the host that the imported file should be
parsed as JSON. If the host does not support JSON modules or if the assertion doesn't match the
file's actual type, it throws an error.
```js
```
The attributes are available as the second argument to `import()` in environments that implement
import attributes. Note that the syntax is still evolving: [Link] 20 and browsers such as Chrome 91
406
support import assertions, but some environments may require flags or may remove them in favour
of the more general **import attributes** proposal.
1. **Unambiguous module formats:** Without assertions, importing a file like `[Link]` might
accidentally be interpreted as JavaScript or cause conflicts with packages that also export JSON.
Import assertions let the developer specify the expected format explicitly.
2. **Security and reliability:** By asserting the expected type, the runtime can reject mismatches
early. For example, an attacker cannot trick your program into executing a script disguised as a `.json`
file.
3. **Extensibility:** As new module types emerge (translation files, configs, images), import
assertions provide a uniform mechanism to attach metadata. Tools and bundlers can use the
assertions to choose appropriate loaders.
4. **Interoperability:** [Link] and browsers can unify behaviour around non-JavaScript modules.
For example, Node's experimental JSON modules require `assert { type: 'json' }` to avoid breaking
existing CommonJS `require('./[Link]')` semantics.
In [Link] 17 and later (with `type: 'module'` in `[Link]`), you can import JSON modules if you
provide a type assertion:
```js
[Link]([Link]); // Alice
```
407
As of Node 22, import assertions have been replaced by **import attributes** with a slightly
different syntax. The concept remains the same: attach metadata to imports to ensure correct
loading.
## Practice questions
1. **Theory:** What problems do import assertions solve when importing non-JavaScript modules?
Why is it not enough to rely on file extensions alone?
2. **Theory:** Describe how import assertions improve security when loading JSON or other data
files. What happens if the assertion does not match the actual module type?
3. **Coding:** Create a JSON file and import it in a [Link] module using an import assertion. Log its
contents and observe what happens if you omit the assertion.
4. **Theory:** Compare import assertions in static `import` statements and dynamic `import()`.
How is the syntax different?
5. **Theory:** Discuss how the import attributes proposal generalises import assertions. What
advantages does it have over the earlier syntax?
408
What is structuredClone’s difference from deep copy via
JSON?
# `structuredClone()` vs deep copy via JSON
Copying objects in JavaScript can be tricky. A **shallow copy** duplicates only the top level of an
object, while nested objects are shared. A **deep copy** recursively duplicates all nested
structures. Two common approaches are using `[Link]()`/`[Link]()` and using the built-in
`structuredClone()` function. Although they both produce independent copies, they differ
significantly in what they support and how they handle data.
The simplest way to create a deep copy is to serialise an object to JSON and then parse it back:
```js
const original = { a: 1, b: { c: 2 } };
```
This technique works for plain objects containing numbers, strings, booleans, `null` and arrays.
However, it has major limitations:
- **Loss of metadata:** Property descriptors, prototypes and non-enumerable properties are lost.
Instances of classes become plain objects.
- **Loss of type fidelity:** Special numeric values like `NaN` and `Infinity` are converted to `null`.
BigInt values cause a `TypeError`.
Because of these constraints, JSON cloning is suitable for simple data structures but not for complex
objects.
## `structuredClone()`
409
The `structuredClone()` function is a standard API that deeply copies most built-in types using the
structured clone algorithm (the same algorithm used for posting messages to Web Workers). It can
clone many kinds of data that JSON cannot, including:
Example:
```js
const original = {
nested: {},
};
[Link]([Link]("key")); // 42
[Link]([Link](2)); // true
```
410
## When to use each
- **Use JSON cloning** for simple, serialisable data where you don't care about dates, methods or
prototypes. It's fast, widely supported and easy to understand.
- **Use `structuredClone()`** for complex data structures, objects with circular references or built-in
types beyond basic JSON. It preserves more fidelity and has explicit error handling for unsupported
types.
- **Avoid cloning functions and DOM nodes.** If you need to duplicate behaviour, explicitly copy or
recreate the function. DOM nodes are tied to their document; create new nodes instead.
## Practice questions
2. **Theory:** Explain how `structuredClone()` handles circular references and built-in types like
`Map` and `Set` compared to JSON cloning.
3. **Coding:** Write a function `deepCloneJSON(value)` that clones an object via JSON. Test it on an
object containing a `Date` and observe how the date is transformed.
4. **Coding:** Use `structuredClone()` to clone an object with a Map and a circular reference. Verify
that the clone retains the Map entries and that the circular reference points to the clone.
5. **Theory:** When would you choose JSON cloning over `structuredClone()` even though the
latter is more capable? Consider browser support and performance.
411
Explain lazy vs eager evaluation in iterables
# Lazy vs. eager evaluation in iterables
Iterables are JavaScript objects you can loop over with `for...of`. Arrays, strings and Maps are
examples of **eager** iterables: they compute their values immediately and hold them in memory.
Generator functions and other iterator-based constructs are **lazy**: they produce values one at a
time as they're requested. Understanding the difference between these strategies helps you choose
the right tool for large datasets, infinite sequences or performance-sensitive tasks.
## Eager evaluation
- **Definition** - In an eager iterable all values are computed up front. For example, when you call
`[Link]()` or the `map()` method, JavaScript creates a new array containing every transformed
value immediately.
- **Memory implications** - Because the entire result is realized at once, eager evaluation can
consume significant memory if the collection is large. An array of a million numbers occupies
memory for all million elements even if you only ever use the first few.
- **When it shines** - Eager evaluation is straightforward and performant when you need the entire
result and the dataset is reasonably small. Operations like sorting, reducing and random access
(`arr[i]`) are trivial on an array because all values are available.
```js
const evens = [Link]((n) => n % 2 === 0); // [2,4,6,8,10] (all are even)
[Link](evens);
```
Here `map()` and `filter()` each produce a new array. Even if you ultimately only need the first few
values, every intermediate result is computed.
412
## Lazy evaluation
- **Definition** - Lazy evaluation postpones computing a value until it's actually needed. In
JavaScript, you implement laziness by returning an iterator: an object with a `next()` method that
yields the next value and remembers its state between calls. Generators (`function*`) are the most
common way to build lazy iterables.
- **Benefits** - Because lazy iterables produce values on demand, they avoid creating large
intermediate arrays. This can reduce memory usage and improve responsiveness when working with
streams, infinite sequences or expensive calculations. Lazy evaluation also allows you to compose
operations without paying the cost until you consume the result.
- **Trade-offs** - Lazy iterables cannot be randomly indexed; they must be consumed sequentially.
Each call to `next()` may involve computation, so repeated consumption of the same sequence
requires either caching (memoization) or starting over.
```js
function* naturalNumbers() {
let n = 0;
while (true) {
yield n++;
[Link]([Link]().value); // 0
[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
```
413
The generator function `naturalNumbers` does not build an array of numbers. Each call to `next()`
computes the next value and pauses until the next call. Because of this, you can represent potentially
unbounded sequences without exhausting memory.
You can create helper functions that accept an iterable and return a new lazy iterable. Each helper
yields values on demand, allowing you to build complex pipelines without intermediate arrays:
```js
yield fn(value);
[Link]([Link]().value);
414
}
```
Here the mapping and filtering operations are lazy; they don't compute anything until `next()` is
called. The loop stops after five values without computing the rest of the infinite sequence.
Imagine a bakery that sells loaves of bread. An **eager** bakery bakes every loaf at 5 a.m. even if
only a few customers will buy them, wasting ingredients and shelf space. A **lazy** bakery bakes a
loaf only when a customer orders it. If nobody orders bread, no loaves are baked. Similarly, lazy
evaluation produces values only when requested, saving resources when you don't need the whole
batch.
- **"Lazy is always better"** - Lazy evaluation can save memory, but it's not free. Each call to
`next()` involves overhead and may trigger a complex calculation. If you need all results anyway, the
overhead of laziness can outweigh the benefits.
- **Single-use iterators** - Once you consume a lazy iterable with `for...of` or by calling `next()`
repeatedly, it's exhausted. You must create a new iterator if you need to iterate again.
- **Error handling** - Lazy iterables can throw errors during iteration, not at creation time. Make
sure to handle errors where you consume the iterator.
## Practice questions
1. **Theory:** Explain the difference between eager and lazy evaluation in your own words. What
kinds of operations in JavaScript are eager by default, and what constructs enable lazy evaluation?
2. **Coding:** Write a generator function `primes()` that lazily yields prime numbers on demand.
Then use it to print the first ten primes.
3. **Theory:** Discuss situations where lazy evaluation might hurt performance compared to eager
evaluation. Give an example of an operation where an eager array method is simpler and more
efficient than using generators.
4. **Coding:** Implement a lazy version of the `take(n, iterable)` function that returns an iterator
yielding the first `n` values from any iterable without forcing the rest of the iterable to compute.
415
5. **Theory:** What happens if you try to iterate over a generator twice? How could you design an
iterable that supports multiple passes over the same lazy sequence?
416
What is monkey-patching and why is it discouraged?
# Monkey patching in JavaScript and why it's discouraged
**Monkey patching** is the practice of dynamically modifying or extending existing code at run
time. In JavaScript this usually means adding, overriding or deleting properties on built-in objects or
modules after they have been loaded. While this flexibility can be powerful for shims, polyfills and
test doubles, it also introduces hidden dependencies and fragile code. Understanding how monkey
patching works will help you decide when (and when not) to use it.
At its core, JavaScript treats functions and objects as mutable. You can add methods to prototypes,
replace existing functions or change modules on the fly:
```js
[Link] = function () {
};
[Link]([Link]()); // 6
[Link]([Link]()); // 42
[Link] = originalDateNow;
```
417
In testing frameworks like Jest or Sinon, monkey patching (also called **stubbing** or **spying**) is
used to replace network calls or timers with deterministic mocks. Libraries like [core-js] provide
polyfills by monkey patching global objects when native implementations are missing.
## Why is it discouraged?
Although monkey patching can solve short-term problems, it carries significant risks:
- **Namespace pollution** - Adding methods to built-in prototypes affects all code in the
environment. If different libraries define the same method with different semantics, they may
conflict with each other.
- **Fragile upgrades** - When the JavaScript engine or library authors update their
implementations, your patched code may break. A patched method may rely on internal behavior
that changes between versions, leading to subtle bugs.
- **Debugging difficulty** - Monkey patches change behavior behind the scenes. Future maintainers
may not realize that an object has been patched and will spend time chasing unexpected behaviour.
- **Security concerns** - Malicious scripts can monkey patch critical functions (e.g., intercepting
`fetch()`) to steal data. Browsers and [Link] treat such modifications as part of the page, so there is
no sandboxing of patched code.
- **Performance overhead** - Patching prototypes can de-optimize JavaScript engines. V8 and other
engines optimize property lookups based on hidden classes and inline caches; adding unexpected
properties invalidates these optimizations, causing slower property access.
- **Polyfills** - Before ECMAScript 2015, developers used shims to add missing methods like
`[Link]` or `[Link]`. These patches detect native support and
only modify the prototype when necessary.
- **Testing and mocking** - During tests you may override functions like `fetch()`, timers or logging
to provide predictable results. Frameworks that monkey patch do so within a controlled scope and
restore original behaviour after tests.
418
## Best practices and safer alternatives
- **Composition and wrappers** - Rather than modifying a function in place, write a wrapper that
adds functionality and delegates to the original:
```js
function sumArray(arr) {
[Link](sumArray([1, 2, 3])); // 6
```
- **Subclassing** - For custom collections, derive your own class from `Array` or compose with
existing objects instead of changing global prototypes.
- **Dependency injection** - Pass dependencies into functions so they can be replaced in tests
without altering global state.
- **Import modifications explicitly** - If you must alter a module's behavior, create a module that
wraps and re-exports the modified version rather than patching the original.
## Practice questions
1. **Theory:** Define monkey patching in the context of JavaScript. What are some of the potential
problems it introduces?
2. **Coding:** Write a polyfill for `[Link]` that adds the method only if it does not
already exist. Explain why this pattern is safer than unconditionally overwriting the method.
3. **Theory:** Describe a scenario in which monkey patching a native object could lead to a security
vulnerability.
4. **Coding:** In a testing environment, override `[Link]()` so that it collects log messages into
an array instead of printing them. After the test, restore the original implementation. Discuss why
this approach is preferable to patching `[Link]()` globally in production code.
419
5. **Theory:** How can dependency injection or higher-order functions eliminate the need for
monkey patching when writing unit tests?
420
How do JavaScript engines optimize tail calls (TCO)?
# How JavaScript engines optimize tail calls (TCO)
Recursive functions are elegant but can blow up the call stack if they recurse deeply. **Tail call
optimization** (TCO) is a technique used by compilers and interpreters to reuse stack frames when a
function call occurs in a _tail position_—the last thing the function does before returning. When a
call is tail-recursive, the caller doesn't need to do any further work after the callee returns, so the
engine can replace the caller's frame rather than creating a new one. This effectively turns recursion
into iteration and prevents stack overflow.
## Tail positions
A call is in **tail position** if its result is immediately returned by its caller, with no further
computation. In JavaScript:
```js
function sum(n) {
if (n === 0) return 0;
return n + sum(n - 1); // not a tail call - the addition happens after the call
return sumTail(n - 1, acc + n); // tail call - no pending work after the call
```
In `sumTail()`, the recursive call is the last operation; there is no multiplication or addition after it.
This makes it eligible for TCO.
421
ECMAScript 2015 introduced the concept of **proper tail calls** (PTC). The specification describes
how compliant engines _may_ reuse the current call frame when a tail call occurs, thus limiting stack
growth. However, the spec does not require tail call optimization, and most mainstream engines (V8
in Chrome/Node, SpiderMonkey in Firefox) have not implemented PTC. Safari's JavaScriptCore briefly
supported it but later removed support. As a result, relying on TCO in cross-platform JavaScript is
unsafe.
Implementing tail call optimization in dynamic languages like JavaScript is challenging for several
reasons:
- **Debugging semantics** - Reusing call frames changes the observable call stack. Developers
expect stack traces to show each function in a recursion chain; TCO would hide those frames, making
debugging harder.
- **Security and backwards compatibility** - Some existing code relies on detecting a call stack's
depth or using `[Link]`/`[Link]` to inspect callers. Removing frames
could break such code.
- **Engine complexity** - JavaScript engines perform many optimizations (inline caching, JIT
compilation, de-optimisations). Supporting full TCO requires changes across interpreter and JIT
pipelines.
For these reasons, engines have prioritized other optimizations. The ES committee has since
reclassified proper tail calls as optional.
To avoid stack overflow in recursive algorithms, you can refactor code to be iterative or use a
_trampoline_ to repeatedly call functions without growing the stack:
```js
function trampoline(fn) {
result = result();
422
}
return result;
```
The `factorialThunk` function returns another function instead of making a direct recursive call. The
`trampoline` repeatedly invokes these thunks until it gets a number. This pattern keeps the call stack
flat.
## Practice questions
1. **Theory:** What is a tail call? Give an example of a recursive function that is _not_ in tail form
and explain why it cannot be optimized.
2. **Coding:** Rewrite a recursive function to compute the nth Fibonacci number using an
accumulator so that the recursive call is in tail position.
3. **Theory:** Why do most JavaScript engines not implement proper tail calls? Describe at least
two challenges.
4. **Coding:** Implement a trampoline function that can take a self-recursive thunk and produce a
result without growing the call stack. Use it to compute a large factorial (e.g., `factorial(1_000)`
without a stack overflow).
5. **Theory:** Aside from TCO, what other techniques can you use in JavaScript to avoid stack
overflow in recursive algorithms?
423
What is generator delegation (yield) and how does it
work?
# Generator Delegation (`yield*`): How It Works
JavaScript's generator functions allow you to produce sequences of values on demand using the
`yield` keyword. A generator is paused when it yields and resumes when the caller invokes its `next()`
method. In complex scenarios you may want one generator to "hand off" iteration to another
generator or iterable. The **delegating yield** (`yield*`) provides that capability: it lets a generator
transparently pass through values from another iterable.
```js
function* numbers() {
yield 1;
yield 2;
yield 3;
const it = numbers();
```
If you need to yield all values from another generator or iterable without writing a loop, use the
`yield*` expression. The syntax is similar to a normal yield, but the asterisk signals delegation:
```js
function* g1() {
yield 2;
424
yield 3;
yield 4;
function* g2() {
yield 1;
yield 5;
[Link](x);
// 1 2 3 4 5
```
`yield* g1()` iterates over `g1()` and yields each value as if `g2()` had yielded them directly. When the
delegated generator finishes, control returns to `g2()` and it continues yielding its own values. The
`yield*` expression itself evaluates to whatever value the delegated iterator's final `return` produced,
which can be captured if needed.
`yield*` works with any iterable object, not just generators. Arrays, strings, Sets, Maps and even the
`arguments` object can be delegated:
```js
function* g3() {
425
const it = g3(5, 6);
```
A delegated generator can return a value using `return`. The `yield*` expression will evaluate to that
returned value, allowing the delegating generator to capture it:
```js
function* inner() {
yield 1;
yield 2;
return "done";
function* outer() {
[...outer()];
```
`yield*` lets you build complex sequences by composing smaller generators. Instead of manually
looping over a subgenerator and yielding each value, you can delegate and let the language handle
426
iteration. This is particularly helpful when you need to flatten nested generators or forward values
from helper functions.
Consider a tree traversal generator that needs to walk child nodes. Without delegation you would
write an explicit loop inside the parent generator. With `yield*`, your generator reads more
declaratively:
```js
function* traverse(node) {
yield [Link];
yield* traverse(child);
[Link](value);
```
Imagine you are telling a story but need to include a detailed anecdote told by your friend. Instead of
retelling the anecdote yourself, you hand the microphone to your friend (delegate) so they can speak
directly. When they finish, you take back the microphone and continue your story. That is what
`yield*` does for generators: it hands control to another iterator and then resumes when it's finished.
427
- **Using `yield` instead of `yield*`** - If you write `yield g1()` instead of `yield* g1()`, you will yield
the generator object itself, not the values produced by it. Always use the asterisk when delegating.
- **Delegating to non-iterables** - The operand to `yield*` must be iterable (it must have a
`[Link]` method). Passing a plain object without an iterator will cause a runtime error.
- **Ignoring the return value** - The value of a `yield*` expression is the return value of the
delegated iterator. If the subgenerator returns a value you care about, store it; otherwise you can
ignore it.
## Practice questions
1. **Theory:** In your own words, explain the difference between `yield` and `yield*`. When would
you choose one over the other?
2. **Coding:** Write a generator `flatten` that takes a nested array of numbers (e.g., `[1, [2, 3], 4,
[5]]`) and yields the numbers in a flat sequence using generator delegation.
3. **Theory:** What will the `yield*` expression evaluate to if the delegated generator does not
explicitly return a value? How can you capture this value?
4. **Coding:** Using `yield*`, implement a generator that yields the Fibonacci sequence up to a
given count by delegating to a helper generator that yields successive values.
428
What are async generators and how are they used with
for-await-of?
# Async Generators and `for await...of`
An async generator is defined with `async function*`. It looks like a regular generator (`function*`),
but it can contain `await` expressions and can yield promises. When you call an async generator, it
returns an **async iterator**—an object with an asynchronous `next()` method that returns a
promise resolving to `{ value, done }` objects. Each `yield` in the generator produces a value, and
`await` pauses until the awaited promise settles.
Here's a simple async generator that waits one second between numbers:
```js
yield i;
```
Calling `countWithDelay(3)` returns an object whose `next()` method returns promises. Each call to
`next()` waits for the delay before resolving with the next number. Without dedicated syntax,
consuming such an async iterator requires promise chaining:
429
```js
const it = countWithDelay(3);
[Link]()
[Link](value); // 1 after 1 s
return [Link]();
})
[Link](value); // 2 after 2 s
});
```
ES2018 introduced the `for await...of` loop to simplify consumption of **async iterable** objects. An
object is async iterable if it has a `[Link]` method that returns an async iterator. All
async generators are async iterables by default. Inside an async function (or at top level of a module
in some environments), you can iterate over them using `for await...of`:
```js
[Link](value);
[Link]("Done");
demo();
```
430
The loop pauses after each iteration until the promise returned by `next()` resolves. Unlike
`[Link]()`, which runs operations concurrently, `for await...of` processes values sequentially,
making it ideal for streaming APIs or when order matters.
Apart from async generators, many browser and [Link] APIs expose async iterables:
- **File handles:** In [Link], the `fs` module provides asynchronous iteration over directory entries
(`[Link]()`) and file contents.
`for await...of` can also iterate over synchronous iterables. The specification says that if an object
does not have a `[Link]` method but has a `[Link]`, the loop will use the
synchronous iterator. This means you can write loops that consume values regardless of whether the
source is synchronous or asynchronous.
## Use cases
- **Streaming data** - reading lines from a network socket or file one by one without loading the
entire content into memory.
- **Paginated APIs** - iterating through pages of API results by awaiting each page's promise.
## Real-world analogy
Imagine you are waiting for deliveries from an online store. Each package arrives at different times.
Instead of standing by the door constantly checking for deliveries, you set up a system where you
wait for the courier to ring and then take each package as it comes. `for await...of` behaves like that
system: it waits for each promise to resolve (the courier rings) and then yields the value (the
package) before moving to the next.
431
## Common pitfalls and misconceptions
- **Only inside async contexts** - You can use `for await...of` only inside an `async` function or at
the top level of an ES module. Using it in a normal function causes a syntax error.
- **Sequential execution** - The loop waits for each iteration to finish before proceeding. If you
want concurrency, collect promises in an array and await them with `[Link]()`.
- **Error handling** - Wrap your `for await...of` loop in a `try...catch` to handle rejections. If the
async iterator's `next()` method rejects, the rejection will propagate and exit the loop unless caught.
## Practice questions
1. **Theory:** What is the difference between a synchronous generator and an async generator?
How do their `next()` methods behave?
2. **Coding:** Write an async generator `readLines(url)` that fetches a text file via `fetch()`, splits it
by newline, and yields each line after a short delay using `await`.
3. **Theory:** Why must `for await...of` be used inside an async function? What happens if you try
to use it in a regular function?
4. **Coding:** Create a custom object with a `[Link]` method that yields three values
with different delays. Iterate over it with `for await...of` and log the order of completion.
432
Explain top-level await in ES modules
# Top-Level `await` in ES Modules
`await` normally appears inside `async` functions. Until recently, if you needed to perform
asynchronous initialization at the top of a module, you had to wrap your logic in an async function or
chain promises. **Top-level `await`** changes that: it allows the `await` keyword at the top level of
an ES module, turning the module into an implicitly asynchronous task. This capability simplifies
module initialization and dynamic imports but comes with important semantics to understand.
In a traditional script or CommonJS module, using `await` outside of an `async` function results in a
syntax error. Top-level `await` is a feature of ES modules that relaxes this rule. When a module
contains a bare `await`, the JavaScript engine treats the module's execution as if it were wrapped in
an async function. The module will pause at the `await` expression until the operand settles, then
resume execution. Modules that import the awaiting module will wait for its evaluation to complete
before continuing.
```js
```
When another module imports `[Link]`, it must wait for `connectToDatabase()` to resolve before it can
use the default export. The top-level await ensures that consumers see a fully initialized connection.
ES modules are evaluated in dependency order. Normally, a module's code runs synchronously once
its dependencies are linked. When a module contains a top-level await, evaluation becomes
asynchronous:
433
- **Execution pauses at `await`.** The module's evaluation returns a promise that resolves when all
awaited promises within the module have settled.
- **Importing modules wait.** A module that imports a top-level-awaiting module must wait until
that module's promise resolves before running its own module body. This ensures dependent
modules see the awaited exports in their final state.
- **Sibling modules continue.** If two modules import the awaiting module, they both pause at
their import statements until the awaited module resolves. Once resolved, each module continues
evaluation independently.
This mechanism is similar to treating the entire module as an `async function`—all imports of the
module implicitly `await` its promise.
## Use cases
```js
// [Link]
```
Without top-level await you'd need to wrap this in an async function and export a promise. Now the
module can return the actual data.
Modules often need to read configuration files, open database connections or prefetch data before
exporting functions. Top-level await lets you perform those actions inline:
```js
434
// [Link]
return cache[key];
```
Consumers of `[Link]` know that `getItem()` will always read from an initialized cache.
You can attempt to import a preferred module and fall back gracefully if it fails:
```js
let parser;
try {
} catch {
```
- **Modules only.** Top-level await is allowed only in ES modules. It cannot be used in classic
`<script>` tags without `type="module"`, and it's not allowed inside regular functions unless they are
marked `async`.
- **Potential deadlocks.** Combining top-level await with circular dependencies can cause
deadlocks if modules await each other in a cycle. Design your modules to avoid cycles or perform
awaits in functions instead of the top level.
435
- **Unsupported environments.** Some older browsers and [Link] versions do not support
top-level await. Node added support in v14.8.0 behind a flag and in v16 as a default; always check
your runtime environment.
## Real-world analogy
Picture a cooking show where the host needs to marinate meat before continuing with the recipe. In
earlier seasons, the show cut away to a pre-prepared dish while the meat marinated off camera.
With top-level await, the host simply waits in real time, and the rest of the kitchen staff (importing
modules) pauses until the marination finishes. Once done, everyone proceeds with fully prepared
ingredients.
## Practice questions
1. **Theory:** Why can't you use `await` at the top level of a classic script? What must you do to
enable it?
2. **Coding:** Create an ES module that fetches JSON data at the top level using `await` and exports
a function that returns a property from the fetched object.
3. **Theory:** Describe how top-level await affects the evaluation order of modules that import a
module containing it. What happens if multiple modules import it simultaneously?
4. **Coding:** Write two ES modules, `[Link]` and `[Link]`, where `[Link]` awaits a promise at the top level
and `[Link]` imports and uses `[Link]`. Show how `[Link]` waits for `[Link]` to finish evaluation before
executing.
436
How does module caching work in ES modules and
CommonJS?
# How does module caching work in ES modules and CommonJS?
JavaScript has two primary module systems: **CommonJS (CJS)** used historically in [Link], and
**ECMAScript modules (ESM)** which are part of the language spec and supported in modern
browsers and Node. Both systems cache modules after they have been loaded, but the caching
mechanisms and semantics differ. Understanding how caching works helps avoid subtle bugs when
modules depend on each other or when you want to reload code dynamically.
In [Link], when you `require()` a module for the first time the runtime:
1. **Resolves the specifier** to an absolute file path (e.g. `./[Link]` → `/full/path/[Link]`). File
extensions like `.js`, `.json` and `.node` are tried automatically if none is provided.
2. **Loads and executes** the module code in its own wrapper function. During execution the
module can populate `[Link]` or `exports` with values to expose.
3. **Caches the result** in `[Link]` keyed by the resolved filename. Subsequent `require()`
calls for the same file return the same `[Link]` object without re-executing the module
code.
Because the module code runs only once, any side effects (like logging, reading files, connecting to a
database) happen a single time. Mutating the exported object also changes the instance that other
modules receive because everyone references the same cached object. You can manually clear the
cache entry by deleting it from `[Link]`, but this is rarely done in production because it may
lead to inconsistent state.
The caching system also enables **cyclic dependencies**. When two modules require each other,
Node will return a partially constructed `exports` object for the module still loading. This allows both
modules to finish executing, but any values referenced before they are assigned will be `undefined`.
See the circular dependencies section for details.
437
ESM was designed with static structure and live bindings. When a module is imported the first time
using `import` or `import()`:
1. **Resolving and loading**: The module specifier is resolved using browser rules or Node's ESM
resolution algorithm. ESM specifiers do not attempt file extensions unless you configure import maps
or package exports; you must specify the full path (`./[Link]`) or rely on a package's `exports` field.
2. **Instantiation**: The module code is parsed, declarations are created and live bindings are set
up. Import statements create read-only references to exported values; if the exporting module later
changes the value, all importers see the new value.
3. **Evaluation**: The top-level code runs once. Unlike CJS, ESM does not wrap code in a function
scope; variables declared at the top level belong to the module. After evaluation, the module is
placed in the host's internal module cache.
Like CommonJS, subsequent imports of the same ESM module return the same module namespace
object; evaluation never repeats. However, ESM does not expose a public cache that you can modify.
Module namespace objects are immutable and there is no standard way to clear or reload a module.
Dynamic `import()` returns a promise that resolves to the module namespace; if the module has
already been evaluated it resolves immediately. Re-loading code usually requires changing the
module's URL (e.g. adding a query string or hash) or restarting the environment.
## Key differences
438
## Example: demonstrating CommonJS caching
```js
// [Link] (CommonJS)
[Link]("logger loaded");
// [Link]
setTimeout(() => {
}, 1000);
```
When `[Link]` is first required, it logs "logger loaded" and exports the current timestamp. Even
after one second the second `require()` returns the same object, so comparing the two times yields
`true`. The module isn't re-executed because it is retrieved from the cache.
```js
// [Link] (ESM)
counter++;
// [Link]
[Link](counter); // 0
increment();
439
[Link](counter); // 1 (live binding updates)
```
`[Link]` is executed only once. The `counter` variable is exported as a live binding. When
`increment()` is called, the change is visible to all importers. Using dynamic `import()` later returns
the same module namespace object; the module isn't re-run.
- **Don't mutate imported bindings** - In ESM, imported variables are read-only. To change shared
state, export functions or objects that encapsulate state.
- **Avoid relying on module side effects** - Because modules run once, any side effects occur only
during initial load. Avoid modules that perform critical actions at import time; instead, provide
explicit functions to call.
- **Be mindful of cycles** - Cyclic dependencies can return incomplete exports or cause deadlocks
(especially with top-level await). Refactor to remove cycles or defer access until after modules have
finished initializing.
- **Hot reloading** - There's no portable way to unload and reload ESM modules. Development
tooling often works by spinning up new workers or altering import URLs with cache-busting query
strings.
## Practice questions
1. **Theory:** What happens if you require the same CommonJS module multiple times in different
files? Explain why the module's code is not executed again.
2. **Theory:** How do live bindings in ES modules differ from the values exported by CommonJS
modules?
3. **Coding:** Create two CommonJS modules (`[Link]` and `[Link]`) that require each other. Show how
partial exports are returned during loading and how you can safely use the values after both modules
finish executing.
4. **Coding:** Write a small ESM program where one module exports a counter and another
imports it twice—once statically and once via dynamic `import()`. Demonstrate that both imports
refer to the same counter value.
5. **Theory:** Why is there no standard API to clear the ESM module cache, and what strategies
exist for reloading code in an ESM environment?
440
441
What happens in circular module dependencies in
JavaScript?
# What happens in circular module dependencies in JavaScript?
A **circular dependency** occurs when two or more modules depend on each other, directly or
indirectly. For example, `module A` imports something from `module B`, and `module B` imports
something back from `module A`. Circular dependencies are legal in both CommonJS and ECMAScript
modules, but they behave differently. Understanding these differences helps you avoid `undefined`
values, deadlocks and other subtle bugs.
In Node's CommonJS system, modules execute synchronously when first `require()`d. When a
module encounters a `require()` call to another module, it pauses execution, resolves and loads the
other module, and then resumes. If two modules require each other, Node breaks the cycle by
providing a **partially filled `exports` object** to the dependent module while the first module
continues loading.
### Example
```js
// [Link]
[Link]("a starting");
const b = require("./b");
[Link] = {
bName: [Link],
};
[Link]("a loaded");
// [Link]
[Link]("b starting");
const a = require("./a");
442
[Link] = {
aName: [Link],
};
[Link]("b loaded");
// [Link]
const a = require("./a");
const b = require("./b");
[Link](a, b);
```
When `[Link]` requires `[Link]`, Node starts evaluating `[Link]`. In turn, `[Link]` requires `[Link]`, but `[Link]` hasn't
finished executing. Node returns the current incomplete `exports` object for `[Link]` (which only
contains `name: 'module A'`). Once both files finish executing, the exports are fully populated.
Running `[Link]` logs:
```text
a starting
b starting
b loaded
a loaded
{ name: 'module A', bName: 'module B' } { name: 'module B', aName: 'module A' }
```
Notice that both modules eventually receive the correct names, but if you tried to access `[Link]`
inside `[Link]` before `[Link]` finished executing, it would be `undefined`. To avoid issues, design modules
so that any values imported from other modules are not needed during initial execution. Typically
you should export functions or classes rather than direct values that need to exist immediately.
443
ES modules handle cycles differently. Import statements are hoisted and evaluated before any code
runs; the module loader sets up **live bindings** for all imported and exported names. When a
module imports another module that imports it back, both modules initialise their export bindings to
`undefined` and then execute their top-level code. As exports are assigned, the imported bindings
update automatically. Because of this design, most cycles resolve correctly as long as you don't use
imported variables before they are initialised.
### Example
```js
// [Link]
// [Link]
// [Link]
```
This example works because the imported functions are called after both modules assign their
exports. Each module exports a function (`getName`) and uses the other module's function to define
a constant. When `getName()` is called, the function refers to its own module's code, which has
already been defined. The output is:
444
```text
```
If you instead imported a variable that hasn't been assigned yet, you'd get `undefined`. So it's
important to export functions or objects and defer calling them until after module initialisation.
Starting with modern JavaScript, ES modules can use top-level `await`. This allows asynchronous
code at the top level of a module but also introduces a new form of deadlock. When a module uses
top-level `await`, importing modules must **wait** for it to resolve before continuing execution. If
two modules with top-level `await` import each other and each awaits a promise that depends on
the other module's completion, neither will ever resolve. The environment throws a `TypeError:
Circular dependency detected while resolving promise` in these cases.
To avoid deadlocks, avoid using top-level `await` in mutually dependent modules. Instead, move
asynchronous operations into functions that the importer can call after initialisation or restructure
the module dependencies to remove the cycle.
## Best practices
- **Minimise cycles** - Circular dependencies make code harder to reason about and can lead to
undefined values or deadlocks. Refactor code into smaller modules or move shared code into a third
module both depend on.
- **Export functions and classes** - Avoid exporting computed values that depend on the other
module's exports. Use functions to provide values when needed.
- **Be cautious with top-level code** - In CommonJS, any code at the top level runs immediately; in
ES modules it runs once at import time. Don't perform heavy work or rely on imported values before
they exist.
- **Prefer dynamic import in asynchronous cycles** - If you must load a module lazily to break a
cycle, use `import()` within a function rather than a static `import`. This defers loading until runtime
and avoids the static cycle.
## Practice questions
445
1. **Theory:** How does [Link] handle a circular dependency between two CommonJS modules?
What will be returned when one module requires the other before it has finished executing?
2. **Theory:** In ES modules, why does importing a function from a module involved in a cycle
work, while importing a yet-to-be-initialised variable often results in `undefined`?
3. **Coding:** Write two CommonJS modules that depend on each other and demonstrate a case
where accessing a value too early results in `undefined`. Then refactor the code to avoid the issue.
4. **Coding:** Create a pair of ES modules with top-level `await` that import each other and
intentionally cause a deadlock. Observe the error and then modify the code to remove the deadlock
by moving the awaits into a function.
5. **Theory:** Explain how top-level `await` can lead to deadlocks in circular dependencies and list
strategies to prevent such situations.
446
Explain bare imports and import maps in browsers
# Explain bare imports and import maps in browsers
When writing modular JavaScript in browsers, you typically use **relative** or **absolute** URLs
for your module specifiers:
```js
```
These specifiers tell the browser exactly where to fetch a module. However, many [Link] or
bundler-based codebases use **bare import specifiers** like `'react'` or `'lodash'` that do not
contain `./`, `../`, or a URL. Historically, browsers could not resolve these names on their own because
there was no mapping from the bare name to a network path. **Import maps** solve this problem
by letting developers tell the browser how to resolve bare specifiers to actual URLs.
A **bare import** is an import specifier that is not relative or absolute. Examples include:
```js
```
Browsers don't know where to fetch `'react'`; they need a URL. Tools like webpack, Vite or Rollup
handle this during bundling by resolving module names to files in `node_modules`. For
browser-native modules, import maps provide a declarative way to achieve similar resolution.
447
An **import map** is a JSON object embedded in a `<script type="importmap">` tag. It defines one
or more maps—`imports` and optionally `scopes`—that associate bare specifiers with URLs. At page
load, the browser reads the import map and uses it whenever it resolves module specifiers in
subsequent `import` statements or `import()` calls.
```html
<!DOCTYPE html>
<html>
<head>
<!-- Define the import map before any modules load -->
<script type="importmap">
"imports": {
"react": "[Link]
"react-dom": "[Link]
"app/": "/static/app/"
</script>
</head>
<body></body>
</html>
```
In this example, any `import 'react'` in `[Link]` or its dependencies will resolve to
`[Link] Imports starting with `app/` are resolved relative to
`/static/app/`. The import map must be declared **before** the module scripts that depend on it;
otherwise the browser won't apply it.
448
You can define different resolutions depending on which module imports the specifier using the
optional `scopes` property. A scope is keyed by a parent module URL and contains its own `imports`
mapping. This is useful when two parts of your application need different versions of the same
dependency.
```html
<script type="importmap">
"imports": {
"lit": "[Link]
},
"scopes": {
"/admin/": {
"lit": "[Link]
</script>
```
Modules under `/admin/` will receive version 1 of Lit, while others receive version 2.
- **Experiment with CDNs** or new package versions without changing source code.
449
They work well for small or medium projects, demos and quick prototypes. For large applications
with hundreds of modules, bundlers still offer performance benefits (tree shaking, bundling multiple
files into a single HTTP request).
- **Single import map:** Only the first import map is processed. Additional import maps are
ignored.
- **Must appear before modules:** The import map must be defined before any `<script
type="module">` that uses bare specifiers; otherwise the specifiers won't resolve.
- **Spec applies only to module imports:** Import maps affect `import` statements and dynamic
`import()`. They do not apply to `<script src="...">`, worker `new Worker()`, or other non-module
resource loads.
- **Browser support:** As of 2025, major Chromium-based browsers support import maps. Firefox
has partial support behind a flag, while Safari support is experimental. Use a feature detection or a
polyfill for wider compatibility.
## Practice questions
1. **Theory:** What is a bare import specifier, and why can't browsers resolve it without help?
2. **Theory:** Describe the purpose of an import map's `imports` and `scopes` sections. When
might you use a scoped import map?
3. **Coding:** Write a small HTML page that defines an import map mapping `'lodash'` to a CDN
URL. Then write a module that imports `lodash` and uses it to merge two objects.
4. **Coding:** Modify the previous example to use a scoped import map so that
`/admin/[Link]` imports a different version of `lodash` than the rest of your site.
5. **Theory:** List some limitations of import maps and explain why a bundler might still be
preferred for large applications.
450
What are custom error classes and how do you create
them?
# What are custom error classes and how do you create them?
- **Clarity:** A specific error class conveys intent better than a generic `Error`. It's easier to catch
and handle `ValidationError` or `PermissionError` separately from unrelated issues.
- **Pattern matching:** You can use `instanceof` to check for a particular error type and recover
accordingly.
- **Additional context:** Custom errors can carry extra properties (like an error code or user ID) to
help debugging or return more information to callers.
To define a custom error, create a class that extends the built-in `Error` and call `super(message)` in
the constructor. Set the `name` property to the class name so that stack traces show the correct
type. You can also define additional properties.
```js
constructor(message, code) {
[Link] = "ValidationError";
451
function processUser(user) {
if (![Link]) {
// process user...
try {
} catch (err) {
} else {
```
In the example above, throwing a `ValidationError` allows the caller to distinguish input validation
problems from other errors. The custom property `code` holds a machine-readable error code.
ES2022 introduced an optional `cause` property on errors. You can provide another error as the
cause when constructing your custom error:
```js
constructor(message, options) {
super(message, options);
[Link] = "DatabaseError";
452
}
try {
await [Link](record);
} catch (err) {
```
Now, `DatabaseError` instances include a `cause` property referencing the original error, preserving
the error chain for debugging.
- **Always extend `Error`:** This ensures your custom error has a proper stack trace and can be
caught using `instanceof Error`.
- **Set the `name` property:** By default the `Error` constructor sets `name` to `'Error'`. Overwrite
it with your class name for clearer stack traces.
- **Add context carefully:** Include only relevant information (like HTTP status codes or identifiers).
Don't put large objects on errors because they may be logged or sent over the wire.
- **Document expected errors:** Make it clear which error types a function can throw so callers
know what to handle.
## Practice questions
1. **Theory:** Why might you prefer throwing a `NotFoundError` instead of a generic `Error` when a
database record is missing?
2. **Theory:** What is the purpose of the `cause` property on an error, and how can it help
debugging?
3. **Coding:** Create a `PermissionError` class that extends `Error`. Have it take a `userId` and
required `role` in its constructor and include them on the instance. Demonstrate how you would
catch and log this error differently from other errors.
453
4. **Coding:** Rewrite a function that throws generic errors to instead use specific custom error
classes (`ValidationError`, `NetworkError`, etc.). Show how the caller can distinguish between them
using `instanceof`.
5. **Theory:** When defining a custom error class, why is it important to call `super(message)` and
set the `name` property?
454
How do try–catch–finally blocks behave with
async/await?
# How do try-catch-finally blocks behave with async/await?
`async/await` lets you write asynchronous code that looks synchronous. But asynchronous
operations can still fail, and errors must be handled carefully. The familiar `try...catch...finally`
construct works with `await` just like it does with synchronous code, but there are some nuances to
be aware of.
`await` pauses the execution of an async function until the awaited promise settles (fulfills or
rejects). If the promise fulfills, the value becomes the result of the `await` expression. If it rejects, the
`await` expression throws the rejection reason as an exception. This means you can wrap an `await`
in a `try...catch` block to handle promise rejections:
```js
try {
if (![Link]) {
} catch (err) {
throw err;
```
455
In this example, any error thrown by `fetch()` or by manual checks inside the `try` block is caught in
the `catch` block. If the error is rethrown, callers can catch it further up the chain.
The `finally` block executes **after** the `try` and `catch`, regardless of whether an exception
occurred. This is useful for cleanup operations that must run no matter what (closing files, releasing
locks, showing or hiding loaders). If the function returns or throws inside the `try` or `catch`, the
`finally` block still executes before control leaves the function:
```js
await [Link]();
try {
} catch (err) {
throw err;
} finally {
```
A single `try` block can contain multiple `await` statements. If any awaited promise rejects, control
jumps to the nearest enclosing `catch` block. To handle errors from individual awaits separately,
place each in its own `try...catch` or use `[Link]()` to collect results without throwing:
```js
456
async function fetchMany(urls) {
try {
[Link](await [Link]());
} catch (err) {
[Link](null);
return results;
```
If an awaited promise rejects outside of any `try...catch`, the error will propagate to the next
enclosing async function or to the global environment. In [Link], an unhandled rejection triggers
the `unhandledRejection` event and may terminate the process. In browsers, it triggers an
`unhandledrejection` event on `window`. Always either catch or return errors.
457
If you return a value from inside `try` or `catch`, the `finally` block still runs **before** the return. If
you return a value in `finally`, that value overrides any earlier return or thrown error. Overwriting
return values in `finally` is discouraged because it makes control flow hard to understand.
```js
try {
return 1;
} finally {
// tricky().then([Link]); // logs 2
```
## Practice questions
1. **Theory:** How does `await` interact with `try...catch` when a promise rejects? What happens if
the rejection is not caught?
2. **Theory:** Why is the `finally` block useful in asynchronous code? Give two scenarios where
you'd put code in a `finally` clause.
3. **Coding:** Write an async function that reads from two APIs sequentially using `await`. Use one
`try...catch` to handle errors from both calls, and then modify the code to handle errors from each
call separately.
4. **Coding:** Demonstrate what happens when a `finally` block returns a value different from the
one returned in the `try` block. Explain why doing this can be confusing.
5. **Theory:** In [Link], what global event is emitted when a promise rejection has no handler,
and how can you listen for it?
458
What is unhandledrejection and how can it crash your
app?
# What is `unhandledrejection` and how can it crash your app?
Promises allow you to chain asynchronous operations and handle errors with `.catch()` or `try...catch`
and `await`. If a promise is rejected and **no rejection handler** is attached, it is considered
**unhandled**. Different environments react to unhandled rejections in different ways, and failing
to handle them can cause your application to behave unpredictably or even crash.
In browsers, when a promise rejection is not handled at the time it occurs, the JavaScript engine
dispatches a global `unhandledrejection` event on the `window` object. You can listen for this event
to log or handle unexpected rejections:
```js
});
```
If you don't listen for `unhandledrejection`, the browser typically logs the error to the console.
Unhandled rejections do not crash the page by default, but they can leave your application in an
inconsistent state.
If a rejection is later handled (e.g. by attaching a `.catch()`), some browsers also emit a
`rejectionhandled` event. However, relying on late handlers is discouraged; always attach `.catch()` on
promises or wrap `await` in `try...catch`.
459
[Link] emits an `unhandledRejection` event on the `process` object when a promise rejection is not
handled during the current turn of the event loop. By default, Node prints a stack trace and,
depending on the `--unhandled-rejections` flag, may terminate the process. Unhandled rejections
can therefore crash your application if left unchecked.
```js
[Link](1);
});
run();
```
In recent versions of Node, the default behaviour is configurable. You can set `--unhandled-
rejections=strict` to make any unhandled rejection terminate the process, `warn` to log a warning but
keep running, or `none` to ignore. The default is `warn`.
- **Silent failures:** If errors are not caught, parts of your app may silently fail and leave
inconsistent state. For example, a failed API call might leave UI in a loading state forever.
- **Crashes in Node:** Leaving a promise rejection unhandled in Node can crash your server
process, resulting in downtime.
- **Debugging difficulty:** Without catching errors near where they occur, stack traces may be
harder to follow.
460
## Best practices
- **Always handle promise rejections:** Use `.catch()` when chaining promises or wrap `await` calls
in `try...catch`.
- **Use `[Link]()` for multiple operations:** When running many asynchronous tasks
concurrently, `[Link]()` fails fast on the first rejection. If you want to handle all rejections
gracefully, use `[Link]()` or handle errors individually.
- **Fail fast in critical services:** In backend services it is often preferable to exit on unhandled
rejections so that a process manager can restart the service in a clean state.
## Practice questions
1. **Theory:** What is an "unhandled promise rejection," and how do browsers and [Link] differ
in their default handling?
2. **Theory:** Describe the dangers of leaving promise rejections unhandled in a [Link] server
application.
3. **Coding:** Write a piece of code in the browser that rejects a promise without a `.catch()`. Add
an `unhandledrejection` listener to log the error message and prevent the default behaviour.
5. **Theory:** Why is it good practice to attach `.catch()` to every promise or handle errors with
`try...catch` when using `await`?
461
What are tagged template literals used for in libraries
like styled-components?
# What are tagged template literals used for in libraries like styled-components?
JavaScript's **tagged template literals** combine template strings with a function. When you place
a function name immediately before a template literal—`` tag`...` ``—JavaScript doesn't create a
string directly. Instead, it calls the tag function with the literal's text fragments and substitution
values. The function can return any value. Tagged templates provide powerful metaprogramming
capabilities for parsing, transforming and contextualising string data.
A tag function is called with two parameters: an array of **string segments** and a list of
**interpolated values**. The first argument is an array of the literal portions of the template, and
subsequent arguments correspond to the embedded expressions. For example:
```js
```
The tag function can transform or even ignore the template parts. Because tags receive raw strings,
they can perform advanced processing like syntax highlighting, sanitisation, or constructing data
structures.
462
## How styled-components uses tags
Styled-components is a popular library for styling React components. You create a styled component
by calling `[Link]` followed by a template literal containing CSS. The `styled` object is a
function that returns another function (the tag). When you write:
```js
font-size: 2rem;
`;
function App() {
```
1. **The tag function processes the template:** The styled-components tag function receives the
static CSS strings and the interpolation functions (here `(props) => ([Link] ? 'hotpink' :
'black')`).
3. **Generates a unique class name:** Styled-components compiles the resulting CSS and injects it
into a `<style>` tag. It generates a unique class name (e.g. `sc-a1234`) and applies it to the rendered
component so that styles are encapsulated.
4. **Returns a React component:** The result of the tagged template call is a React component
(`Title`) that accepts props. You can use it like any other component in your JSX.
463
Because tagged template literals allow styled-components to capture both static CSS and dynamic
JavaScript expressions in a single construct, they are ideal for CSS-in-JS libraries. Without tags, you'd
have to build CSS strings manually and apply them to elements, losing readability and syntax
highlighting.
- **Translation (i18n):** Tags can implement string interpolation for translations, handling
pluralisation and locale-specific formatting.
- **Safe HTML escaping:** A tag function can escape user input to prevent XSS attacks when
constructing HTML.
- **Keep tag functions pure:** A tag should be a pure function of its arguments; avoid side effects
like DOM manipulation within the tag.
- **Performance considerations:** Tags are executed each time the template literal is evaluated. In
performance-critical code paths, avoid complex computation in tag functions.
- **Beware of injection:** When using tagged templates to generate CSS or HTML, make sure to
sanitise interpolated values to avoid injecting malicious content.
## Practice questions
1. **Theory:** How do tagged template literals differ from normal template literals, and what are
their parameters?
4. **Coding:** Create a basic React component using styled-components that sets the background
color based on a `danger` prop. Explain how the interpolation function uses props.
464
5. **Theory:** Besides styling, name two other domains where tagged template literals are useful
and explain how they help.
465
Explain the difference between lazy evaluation and
eager evaluation in iterables
# Explain the difference between lazy evaluation and eager evaluation in iterables
In programming, **evaluation strategy** describes when expressions are executed. Two common
strategies are **eager (strict) evaluation** and **lazy (deferred) evaluation**. Although JavaScript
is an eagerly evaluated language by default, it provides constructs—like generator functions—that
allow lazy evaluation. Understanding the difference helps you write more efficient code, especially
when dealing with large data sets or infinite sequences.
## Eager evaluation
With eager evaluation, expressions are evaluated as soon as they are bound to a variable or passed
as arguments. Arrays in JavaScript are **eager collections**. When you map, filter or reduce an
array, the entire array is traversed and a new array is created immediately, even if you ultimately only
need a few values.
```js
```
Eager evaluation is simple and predictable. However, it can be wasteful when operating on huge
collections or when chaining many operations, because intermediate arrays are created and every
element is processed regardless of whether you use them.
## Lazy evaluation
Lazy evaluation (also called deferred or call-by-need) delays computing a value until it is actually
needed. In JavaScript, **generator functions** and **iterators** enable lazy evaluation. A
generator is defined with `function*` and yields values one at a time. Values are produced on
demand when you iterate over the generator with `for...of` or call `.next()`.
466
```js
function* countUpTo(n) {
[Link]("generating", i);
yield i;
[Link](num);
```
The generator produces each value only when requested by the loop. If you break out of the loop
early, unused values are never generated. This makes lazy evaluation efficient for potentially large or
infinite sequences, and for pipelines where intermediate results might be filtered out.
Suppose you want to take the first 3 even numbers greater than 10 from a list of numbers. Using
eager arrays:
```js
467
.filter((n) => n > 10)
.slice(0, 3);
```
All filters run on the entire array before slicing the first three results, even if the list is huge. With a
lazy pipeline using generators you can short-circuit:
```js
function* take(iterable, n) {
let count = 0;
else return;
filter(
),
);
468
```
- **Performance:** Lazy sequences can reduce CPU and memory usage because they avoid creating
intermediate collections and stop generating values once you have enough. However, for small data
sets or when you need all values, the overhead of generator functions can make eager evaluation
faster.
- **Composability:** You can build pipelines of generator functions (`map`, `filter`, etc.) that mirror
functional programming libraries. Libraries like [Iterables]([Link]
US/docs/Web/JavaScript/Reference/Iteration_protocols) or third-party packages provide helper
functions.
- **Infinite sequences:** Lazy evaluation makes it trivial to represent infinite sequences (e.g. the
Fibonacci sequence) since values are generated on demand.
- **Statefulness:** Generators maintain internal state between iterations. If you need random
access or multiple passes over data, you must recreate the generator or collect its output into an
array.
## Practice questions
1. **Theory:** Define lazy evaluation in your own words. How does it differ from eager evaluation?
2. **Theory:** What advantages does a generator offer when processing large data sets compared
to using array methods like `map` and `filter`?
3. **Coding:** Write a generator function `range(start, end)` that lazily yields all integers from `start`
(inclusive) up to `end` (exclusive). Use it to sum the first ten positive integers without creating an
array.
4. **Coding:** Refactor a chain of `Array` methods (`filter`, `map`, `reduce`) into a lazy pipeline using
generator functions. Measure performance differences with a large array.
5. **Theory:** When might eager evaluation be preferable to lazy evaluation? Give an example
where laziness does not provide a benefit.
469
How does tail-call optimization (TCO) work, and is it
supported in JavaScript engines?
# How does tail-call optimization (TCO) work and is it supported in JavaScript engines?
**Tail-call optimization (TCO)** is a compiler or interpreter feature that allows certain kinds of
recursive functions to execute without growing the call stack. In languages that implement TCO, a call
to a function in **tail position**—meaning the call is the final action in the function—can reuse the
current stack frame instead of allocating a new one. This enables deep recursion without causing
stack overflows and can improve performance.
A **tail call** is a function call that happens as the last operation of another function. There is no
additional work to do after the call returns. For example, in this function the recursive call to
`factorial` is in tail position:
```js
factorialTail(5); // 120
```
Because nothing happens after `factorialTail(n - 1, ...)` returns, the function could, in theory, replace
its stack frame with that of the new call. In contrast, this naive implementation has work to do after
the recursive call (multiplying by `n`), so it is **not** in tail position:
```js
function factorial(n) {
if (n === 0) return 1;
470
}
```
When TCO is implemented, the runtime recognises that the last action of a function is calling another
function (possibly itself). Instead of pushing a new stack frame, it reuses the current one by updating
parameters and jumping to the start of the called function. This eliminates stack growth for
tail-recursive functions and allows them to run as efficiently as loops.
Languages like Scheme, some implementations of Python, and functional languages like Haskell
support TCO. In JavaScript, proper tail calls (PTC) were specified in ECMAScript 2015, but support has
been spotty.
- **Safari / WebKit:** Safari's JavaScriptCore engine implements proper tail calls in strict mode. If
you write a tail-recursive function and run it in Safari with `'use strict'`, deep recursion won't
overflow the stack. This makes Safari the only major browser with native TCO support as of 2025.
- **V8 (Chrome, [Link]), SpiderMonkey (Firefox) and JSC (other modes):** V8 and SpiderMonkey
removed their experimental TCO implementations due to debugging complexity and compatibility
concerns. As a result, tail-recursive functions still consume stack frames and may overflow on deep
recursion. Developers must convert recursive algorithms to loops or manually maintain their own
stacks.
- **Embedded engines:** Smaller engines like Kinoma XS6 and Duktape support TCO. These engines
are used in embedded contexts and emphasise memory efficiency.
Because TCO is not universally supported, it's best to avoid relying on it in production code. Use
iterative constructs or self-managed stacks when writing algorithms that could recurse deeply.
If you need recursion without stack growth in environments that lack TCO, you can employ the
**trampoline** pattern. A trampoline repeatedly calls a function that returns either a value or
another function to call. This turns recursion into iteration:
471
```js
function trampoline(fn) {
result = result();
return result;
```
The `sumRange` function returns a new function instead of making a direct recursive call. The
trampoline repeatedly invokes functions until a final value is returned, effectively turning recursion
into a loop.
## Practice questions
1. **Theory:** Explain what a tail call is and why it allows tail-call optimization. Contrast a tail call
with a non-tail call in the context of recursion.
2. **Theory:** Describe why proper tail call support has not been widely implemented in JavaScript
engines despite being part of the ECMAScript specification.
3. **Coding:** Write a tail-recursive function to compute the nth Fibonacci number. Test it in a
browser that supports TCO (if available) and in [Link]. Observe whether stack overflow occurs.
4. **Coding:** Implement the trampoline pattern to compute the factorial of a large number (e.g.
50 000) without causing a stack overflow. Explain how the trampoline avoids stack growth.
472
5. **Theory:** In which environments might you safely rely on native TCO? How would you handle
deep recursion in environments without TCO?
473
What are Record and Tuple proposals, and how do they
differ from Objects and Arrays?
# What are record and tuple proposals and how do they differ from objects and arrays?
The ECMAScript **Record & Tuple** proposal introduces two new compound primitive types to
JavaScript: **records** and **tuples**. These types aim to provide deeply immutable, value-based
data structures as alternatives to mutable objects and arrays. As of 2025 the proposal sits at Stage 2
of the TC39 process, meaning it is still experimental. Even though the proposal may change, its core
ideas are worth understanding.
## Motivation
JavaScript objects and arrays are mutable and compared by reference. To manage state immutably,
developers often use libraries (like [Link] or Immer) that create read-only proxies or wrapper
objects. These libraries add overhead and do not integrate seamlessly with the language. Records
and tuples would provide **built-in immutable data structures** with predictable behaviour:
- **Deep immutability:** Once created, records and tuples cannot be changed. Nested data is also
immutable.
- **Value equality:** Two records or tuples are considered equal (`===`) if their contents are
identical, unlike objects and arrays which compare by reference.
- **Contain only primitives:** Records and tuples may only contain primitives (including other
records and tuples). They cannot contain objects, functions or mutable arrays.
## Syntax
Although syntax is still under discussion, the proposal uses a `#` prefix to distinguish records and
tuples from objects and arrays. For example:
```js
474
const coords = #[1, 2, 3];
const shape = #{
name: "triangle",
vertices: #[
[0, 0],
[1, 0],
[0, 1],
],
};
// Equality by value
#{ x: 1, y: 2 } === #{ x: 1, y: 2 }; // true
```
In these examples, the `#` prefix signals that the structure is a record or tuple. Because they are
deeply immutable, trying to modify a record or tuple would throw or have no effect.
475
## Interoperability
Records and tuples are designed to interoperate smoothly with existing language features:
- They support destructuring and property access just like objects and arrays:
```js
```
- Records and tuples are intended to be serialisable and interoperable with JSON and structured
cloning. Because they are immutable, transferring them across workers would not require copying
internal state.
- Since they are primitives, records and tuples do not have prototypes. They are not affected by
changes to `[Link]` or `[Link]`.
The Record & Tuple proposal is still evolving. At Stage 2, the details of syntax, semantics, and
interoperability may change. There was an earlier proposal that used `{| ... |}` and `[||]` syntax; the
476
current version uses a `#` prefix but this is not final. Some concerns include complexity of value
equality, limitations of only allowing primitives, and interaction with existing APIs.
If the proposal advances, records and tuples could reduce reliance on external immutability libraries
and make it easier to reason about state. Until then, libraries like Immer remain the best way to work
with immutable data in JavaScript.
## Practice questions
1. **Theory:** Describe the key differences between records/tuples and objects/arrays in JavaScript.
Why are records and tuples compared by value instead of by reference?
2. **Theory:** Why can't records and tuples contain non-primitive values? What benefits does this
restriction provide?
3. **Coding:** Declare a record representing a user with `name` and `age` properties, and a tuple
representing the user's favorite colors. Show how you might destructure these structures.
4. **Coding:** Create two records with the same contents and demonstrate that they are `===`
equal. Then create two plain objects with the same properties and demonstrate that they are not
strictly equal.
5. **Theory:** What challenges might arise when adding records and tuples to JavaScript? Consider
syntax, backward compatibility and performance.
477
What is pattern matching in JavaScript (proposal stage)?
# What is pattern matching in JavaScript (proposal stage)?
## Motivation
JavaScript developers often rely on nested `if...else` statements or verbose `switch` statements to
handle different shapes of data. Pattern matching aims to make such logic concise and expressive. It
builds on existing destructuring patterns, enabling you to match and extract values in one expression
while avoiding fall-through behaviour and improving readability.
## Basic syntax
```js
match (response) {
```
Key features:
478
- **Patterns** can be object patterns (`{ status: 200, data }`), array patterns (`[x, y]`), literal values
(`42`, `'hello'`), or identifier patterns (`_` for a wildcard). Patterns can use nested destructuring,
default values and computed keys.
- **Arrow syntax** (`pattern => expression`) associates each pattern with an expression or block to
evaluate when the match succeeds. Only the first matching branch executes—there is no fall-through
like in `switch`.
- **Exhaustiveness** is encouraged but not enforced. A wildcard `_` at the end acts as a catch-all
case.
Patterns can include **guard conditions** using `if` to add additional checks:
```js
match (value) {
```
In this example, the first branch matches arrays with two elements and then checks the guard `x ===
y`. If the guard fails, execution continues to the next branch.
- **Concise destructuring and matching:** You can match against nested shapes and extract values
without separate destructuring statements.
- **No accidental fall-through:** Each branch is independent; there is no need for `break`
statements as in `switch`.
- **Readability:** For complex conditionals with different data shapes (e.g. discriminated unions,
tagged responses), `match` can be easier to follow than nested `if` statements.
479
## Current status and tooling
The pattern matching proposal is still experimental. Tooling like Babel plugins and TypeScript
transforms allow you to experiment with pattern matching today. Because the feature is not yet
standard, syntax and semantics may change. You should avoid using it in production without
transpilation.
## Practice questions
1. **Theory:** How does the proposed `match` expression improve upon existing `switch`
statements? Discuss fall-through and destructuring.
3. **Coding:** Write a `match` expression that takes a variable which may be a string, number, array
of two numbers or any other value, and logs different messages for each case.
4. **Coding:** Convert a nested `if...else` chain that checks `[Link]` and `[Link]`
into a pattern matching expression.
5. **Theory:** Why is the pattern matching proposal still at Stage 3, and what considerations should
developers keep in mind before using experimental syntax?
480
How does the pipeline operator improve function
chaining?
# How does the pipeline operator improve function chaining?
Modern JavaScript encourages composition: building complex behaviour by combining small, pure
functions. When you compose functions using nested calls, however, the code often reads
**inside-out**. Consider calculating a result by doubling a number, adding three and then negating
it:
```js
```
To understand this expression you have to start in the middle (`double(5)`), then move to the left
(`add(3, ...)`), and finally apply `negate`. The proposed **pipeline operator** (`|>`) flips this around.
It takes the value on its left and feeds it as an argument into the function on its right, allowing the
chain to be read **left-to-right**:
```js
const result = 5 |> double |> ((n) => add(3, n)) |> negate;
```
Each pipeline stage receives the output of the previous stage. The above code is easier to scan
because the order of operations matches the order of the lines.
At the time of writing the pipeline operator is an ECMAScript proposal (still under development), so
its exact syntax may change. The most widely discussed variant, sometimes called the "smart
pipeline", allows two forms:
1. **Bare function form** - When the right-hand side is an identifier, it must refer to a function. The
pipeline operator calls that function with the value on the left as its only argument:
481
```js
```
2. **Topic reference form** - For more complex expressions, a special placeholder (often `#`)
represents the value being piped. This lets you call functions that take more than one argument or
access methods:
```js
const result = 10
|> # + 1 // increment
```
Each pipeline stage is evaluated one after the other. Unlike Unix pipes, the JavaScript pipeline
operator does not automatically read from or write to streams; it simply passes values between
functions.
## Benefits of pipelines
- **Readability:** Pipelines allow code to read from top to bottom, matching the order in which
data flows through the functions. This reduces the cognitive overhead of nested parentheses.
- **Composability:** Small functions become building blocks that can be chained together. This style
is common in functional programming languages (F#, Elm) and libraries like Ramda or RxJS.
- **Reduced boilerplate:** Without pipelines, you often create temporary variables to break up
complex expressions. Piping reduces the need for temporary names and clarifies the transformation
steps.
- **Consistency with other features:** Pipelines pair well with arrow functions, optional chaining
and pattern matching proposals, promoting a declarative style.
## Practical examples
482
### Transforming values
Suppose you need to normalise a string by trimming whitespace, converting it to lowercase and
replacing spaces with hyphens. Without pipelines, you might write:
```js
```
With the pipeline operator, each step becomes its own line:
```js
str
|> [Link](#)
```
This makes it clear that the input flows through trimming, lower-casing and replacing spaces. The
`call` method is necessary because `trim` is a method, not a standalone function. Future versions of
the proposal may allow a more ergonomic syntax for method calls.
```js
// Without pipeline
483
// With pipeline
|> atob
|> decodeURIComponent
|> (#.split(','))
|> (#.map(Number))
```
Each step is isolated, and you can easily add `[Link]()` calls between stages for debugging.
The pipeline operator is still in the proposal stage and not yet part of the ECMAScript standard.
Different variations are under discussion, so the syntax may change before it is finalised. When using
experimental language features, transpilers like Babel and TypeScript can compile pipelines down to
supported JavaScript.
Be mindful that pipeline operators are **not** magic performance boosters; they simply provide
syntactic sugar. Each stage still creates a function call, so deeply chained pipelines could have a small
overhead compared to a single dedicated function.
## Real-world analogy
Think of a pipeline like an assembly line in a factory. An unfinished product moves along a conveyor
belt and stops at each station. At each stop, a worker performs a specific transformation (paints the
part, attaches a piece, tests functionality) before sending it to the next station. Similarly, the pipeline
operator sends a value through a series of functions, each of which transforms it slightly.
## Practice questions
1. **Theory:** What problem does the pipeline operator aim to solve when composing functions?
How does the code read differently when using pipelines compared to nested function calls?
484
2. **Theory:** Describe the two forms of the proposed pipeline operator. When do you need to use
a topic reference placeholder?
3. **Coding:** Rewrite the following expression using the pipeline operator (assume the operator
and topic reference are supported):
```js
```
4. **Coding:** Use the pipeline operator to compute the mean of an array of numbers by chaining
`[Link]()` and a function that divides by the array length. Compare the pipeline
version to a traditional implementation.
5. **Theory:** The pipeline operator proposal is still evolving. What are some considerations you
should have before using it in production code?
485
What is [Link] and how is it used?
# What is `groupBy` and how is it used?
Grouping items is a common task: you might need to organise products by category, group students
by grade, or partition events by month. Traditionally, you would reach for `[Link]()`
or an external library like Lodash. The **grouping proposal** introduces built-in methods that make
grouping more convenient and expressive.
Although early drafts suggested adding a `groupBy` method directly on arrays, the current design
provides two **static methods**:
Both methods take an iterable (such as an array) and a callback that returns a key for each element.
Elements that return the same key are placed into the same group.
`[Link]()` accepts two arguments: the iterable of values to group and a callback function.
The callback is called once per element and should return a value that can be coerced into a property
key (a string or a symbol). The returned object has one property for each unique key, mapping to an
array of the corresponding elements.
```js
const inventory = [
];
486
const byType = [Link](inventory, (item) => [Link]);
/* byType is:
*/
```
Because the returned object has a `null` prototype, it does not inherit methods like `toString()`—
reducing the risk of name collisions. Note that the grouped elements are the **same objects** as
the ones in the original array; modifying a grouped element also modifies the original.
If the callback returns values that are not strings or symbols (for example, numbers or booleans),
they are coerced to strings when used as property keys. If you need to use arbitrary objects or values
(like dates or complex objects) as keys, use `[Link]()` instead.
`[Link]()` works like `[Link]()`, but instead of an object, it returns a `Map` where
keys can be **any value**. This is useful when grouping by values that should not be coerced to
strings. For example, you can group by a boolean or even a reference:
```js
/* parity is a Map:
*/
487
const mapKey1 = {};
const objects = [
];
```
The keys in a `Map` preserve identity: two objects with the same content but different references will
be treated as different keys.
- **Simpler grouping:** Grouping values used to involve writing a `reduce()` loop or using external
libraries. `[Link]()` and `[Link]()` make the intent explicit and reduce boilerplate.
- **Better type safety:** Group names are determined by the callback; you avoid accidental
collisions with inherited object properties (because the returned object has a null prototype).
- **Flexible keys:** `[Link]()` lets you use any JavaScript value as a key, which is useful when
grouping by booleans, dates or other non-string values.
- **Readable code:** Grouping logic can now be expressed declaratively at the top level of your
code instead of being buried in loops.
Before these methods existed, you might have grouped values like this:
```js
488
return [Link]((groups, item) => {
return groups;
}, {});
```
The built-in `[Link]()` does the same job but improves readability and avoids mutating a
pre-initialised accumulator.
## Practice questions
3. **Coding:** Use `[Link]()` to group an array of words by their first letter. Show the
resulting object.
4. **Coding:** Given an array of transactions with a `date` property, use `[Link]()` to group
them by month (hint: use `new Date([Link]).getMonth()` as the key). How could you achieve the
same result without `[Link]()`?
5. **Theory:** What precautions should you take when grouping objects that may later be
mutated? How do the grouping methods handle deep copies?
489
What is the purpose of [Link] and the using
statement proposal?
# What is the purpose of `[Link]` and the `using` statement proposal?
## Disposable objects
```js
class FileHandle {
constructor(name) {
[Link] = name;
[Link] = true;
read() {
[[Link]]() {
[Link](`Closing ${[Link]}`);
[Link] = false;
```
490
The method must be synchronous and should perform any necessary cleanup. Calling it multiple
times should not throw. For asynchronous resources (e.g. network sockets), define
`[[Link]]()` returning a promise.
The `using` statement automatically calls an object's disposal method when the surrounding block is
exited. It ensures that cleanup happens regardless of whether the block completes normally or due
to an exception. Here is the previous `FileHandle` class used inside a `using` block:
```js
[Link]();
```
Because `[Link]` is looked up on the initializer of the `using` variable, any object that
implements this symbol can participate. If the object also defines `[[Link]]()`, you can
use `await using` to wait for asynchronous cleanup:
```js
class Socket {
/* ... */
async [[Link]]() {
await [Link]();
491
await using sock = new Socket();
await [Link]('hello');
```
`await using` guarantees that disposal of asynchronous resources is awaited before control leaves the
scope.
- **Reliability:** Without automatic disposal, it's easy to forget to close a file or release a lock—
leading to memory leaks, file descriptor exhaustion, or inconsistent state.
- **Error safety:** By tying disposal to scope, even exceptions cannot bypass cleanup. The `using`
syntax ensures deterministic finalisation, similar to `try...finally` but with less boilerplate.
- **`try...finally`:** You can always manage resources with `try...finally`. The `using` statement is
syntactic sugar that calls the appropriate disposal method for you. It reduces the risk of missing
cleanup in error paths.
- **One-time use:** `[Link]` should only be called by the runtime. Calling it manually will
not prevent it from being called again at the end of the scope.
- **Cannot await synchronous dispose:** The `[Link]` method must be synchronous. Use
`[[Link]]()` and `await using` for asynchronous cleanup.
## Real-world analogy
Imagine borrowing a library book. When you finish reading, you should return it to avoid late fees
and free the book for others. If you forget, the library eventually contacts you—similar to how
garbage collection eventually frees memory but without releasing the library's copy. The `using`
statement is like a policy that automatically returns the book when you leave the library.
492
## Practice questions
1. **Theory:** What methods must an object implement to participate in the `using` mechanism?
How does `[Link]` differ from `[Link]`?
2. **Theory:** Explain how the `using` statement improves reliability compared to managing
resources with `try...finally` or manually calling cleanup methods.
3. **Coding:** Create a `Timer` class that starts measuring time in its constructor and prints the
elapsed time in its `[[Link]]()` method. Use `using` to measure the duration of a code block.
4. **Coding:** Implement an asynchronous database connection class with
`[[Link]]()` that closes the connection. Use `await using` inside an `async` function to
ensure the connection is closed even if an error occurs.
5. **Theory:** What potential issues might arise if you forget to declare `await using` for an object
with an `[[Link]]()` method?
493
What is [Link] — new error message cause — and
when is it useful?
# What is the `cause` property on errors and when is it useful?
When something goes wrong deep inside your code, the error that bubbles up often lacks context.
You might catch an error from a low-level function, wrap it with a higher-level message, and then
lose the original exception. The `cause` property introduced in ES2022 solves this problem by letting
you **chain errors together**.
All built-in error constructors (like `Error`, `TypeError`, `RangeError`) accept an optional second
argument: an options object with a `cause` property. You can pass the original error (or any other
value) to this property. The new error stores the cause on its `.cause` property but otherwise
behaves like a normal error.
```js
function parseSettings(json) {
try {
return [Link](json);
} catch (err) {
try {
} catch (err) {
```
494
In this example, the `cause` property holds the original `SyntaxError` thrown by `[Link]()`. When
debugging, you can inspect `[Link]` to see what went wrong at the deeper level.
Suppose you have a function that reads a file and parses its contents. If reading fails, you want to
know whether the error came from the file system or from parsing:
```js
const fs = require("fs/promises");
try {
return [Link](data);
} catch (err) {
readConfig("[Link]").catch((err) => {
[Link]([Link]);
while (current) {
current = [Link];
});
```
495
The `cause` property lets you build an error chain. Each error in the chain can store its own message
and metadata while pointing to the lower-level error that triggered it. In [Link], some APIs (such as
`fs/promises`) already include useful causes when they throw.
- **Add context:** When rethrowing an error, wrap it with a message describing what the
higher-level operation was doing. The original error remains available via `.cause`.
- **Preserve stack information:** Although the top-level error has its own stack trace, the cause
retains its own stack trace and properties. This makes debugging easier than logging plain strings.
- **Structured error handling:** Frameworks and logging libraries can inspect the error chain and
present a hierarchical view of failures.
- **Flexibility:** The `cause` property can hold any value—another error, a string, or a structured
object. This allows creative uses such as attaching additional metadata.
Be mindful that the `cause` property is not automatically printed when calling `[Link](err)`.
You need to log it explicitly or use utilities that traverse causes. When creating custom error classes,
pass the `cause` option to `super()` so that the base error stores it:
```js
constructor(message, options) {
super(message, options);
[Link] = "ValidationError";
try {
} catch (e) {
```
496
## Real-world analogy
Imagine a chain of customer service escalations. The frontline agent documents the issue and
escalates it to a supervisor. The supervisor adds context ("customer attempted to reset password")
and escalates further. Each step attaches additional information but references the previous record.
When the issue is resolved, you can trace back through all notes to understand the root cause. The
`cause` property plays a similar role: each layer adds context while preserving the underlying error.
## Practice questions
1. **Theory:** Why is it beneficial to include the original error as the `cause` when rethrowing an
error? How does this improve debugging?
2. **Theory:** What types of values can the `cause` property hold? What happens if you set `cause`
to a non-error value?
3. **Coding:** Write a function `fetchJson(url)` that fetches data from a URL and parses the
response as JSON. If the fetch fails or the JSON is invalid, throw a new error with an appropriate
message and set the original error as the `cause`.
4. **Coding:** Create a custom error class `DatabaseError` that takes a `cause` option. Demonstrate
catching a low-level error (e.g. connection timeout) and wrapping it in a `DatabaseError`. How would
you traverse the chain of causes to log each error?
5. **Theory:** Why doesn't logging an error automatically display its cause? How can you design a
logging utility that prints the entire error chain?
497
What are the phases of the event loop (timers, poll,
check, close)?
# What are the phases of the event loop (timers, poll, check, close)?
JavaScript environments like browsers and [Link] are **single-threaded** at the language level,
yet they can perform non-blocking I/O. This is achieved through an **event loop**: a mechanism
that schedules and runs callbacks in a predictable order. While browsers and Node share the same
high-level idea, Node's event loop exposes more phases. Understanding these phases helps you
reason about callback ordering and avoid surprises when mixing timers, I/O and microtasks.
## High-level overview
The event loop continuously cycles through a set of **queues** and **phases**. In each cycle
(often called a _tick_), it performs work in the following order:
1. **Timers phase** - Executes callbacks scheduled by `setTimeout()` and `setInterval()` whose time
has expired.
2. **Pending callbacks** - Executes I/O callbacks deferred to the next loop iteration.
3. **Idle, prepare** - Internal phases used by [Link] (not exposed to user code).
4. **Poll phase** - Retrieves new I/O events (such as network or file events) and executes their
callbacks. If there are no timers due and the poll queue is empty, this phase can block until I/O
arrives.
6. **Close callbacks** - Executes callbacks for closed resources, such as sockets whose `'close'` event
is emitted.
After the close callbacks phase completes, the event loop checks the **microtask queue**, runs all
queued microtasks (e.g. promise callbacks via `.then()`, `queueMicrotask()`, `[Link]()` in
Node) until it is empty, and then begins the next tick.
The timers phase handles functions scheduled by `setTimeout()` and `setInterval()`. A timer is
executed **once** when its delay has elapsed. If multiple timers expire at the same tick, they are
executed in order of scheduling. Intervals (`setInterval()`) re-queue themselves after each execution.
498
### Pending callbacks
Some operating system operations, such as certain types of TCP errors, run their callbacks here. This
phase is rarely used directly by JavaScript developers.
The poll phase is responsible for retrieving new I/O events (e.g. data arriving on a network socket,
file system events) and executing their callbacks. If the poll queue is empty, [Link] will either:
- Block and wait for incoming I/O if there are no timers scheduled, or
- Immediately proceed to the check phase if timers are due or the poll has been idle for a maximum
timeout.
This behaviour prevents the poll phase from **starving** the rest of the loop. Under the hood, the
libuv library imposes a maximum blocking time so that other phases still get a chance to run.
Callbacks passed to `setImmediate()` execute in the check phase, **after** the poll phase and
before closed resources are handled. Because `setImmediate()` always runs after the poll phase, it
can be used to schedule work to happen after I/O events in the same tick. This distinguishes it from
`setTimeout(fn, 0)`, which runs in the timers phase and may occur earlier.
When a stream or socket closes, Node emits a `'close'` event. Listeners attached to `'close'` fire in this
phase. For example, closing a TCP socket triggers its `'close'` callback here.
Within each phase, after a callback runs, the runtime processes the microtask queue. Microtasks
include:
499
- **Promise callbacks:** `.then()`, `.catch()` and `.finally()` handlers.
- **`[Link]()` callbacks (Node only):** Executed even before other microtasks and can
starve the loop if misused.
After all microtasks are processed, the event loop either continues with the current phase (if there
are more callbacks) or moves on to the next phase. This guarantees that microtasks run _before_ the
next macrotask (e.g. timer or I/O callback).
## Illustrative example
```js
setTimeout(() => {
[Link]("timeout");
}, 0);
setImmediate(() => {
[Link]("immediate");
});
[Link](__filename, () => {
[Link]("file read");
});
[Link]().then(() => {
[Link]("promise");
});
[Link](() => {
500
[Link]("nextTick");
});
```
5. `setTimeout()` with 0 delay runs in the timers phase of the **next** tick (`timeout`).
Actual ordering can vary if the file read finishes before the timer is ready, but microtasks (`nextTick`
and `promise`) always run before any of the macrotasks.
## Practice questions
1. **Theory:** List the main phases of the [Link] event loop and briefly describe what happens in
each phase.
2. **Theory:** How does `setImmediate(fn)` differ from `setTimeout(fn, 0)` in terms of when their
callbacks are executed?
4. **Theory:** What is the purpose of the poll phase's ability to block? How does libuv avoid
starving the event loop when waiting for I/O?
5. **Theory:** Explain the difference between microtasks (e.g. promise handlers,
`queueMicrotask()`, `[Link]()`) and macrotasks (e.g. timer callbacks, I/O events). Why is it
important that microtasks run after each macrotask?
501
What is event-loop starvation and how can you prevent
it?
# What is event loop starvation and how can you prevent it?
JavaScript's event loop allows your code to perform I/O and other asynchronous operations without
blocking execution. However, it's possible for a program to hog the event loop so badly that other
tasks never get a chance to run. This situation is called **event loop starvation** or **starving the
event loop**. Understanding what causes starvation and how to prevent it will help you write
responsive, non-blocking applications.
The event loop cycles through macrotask queues (timers, I/O events, setImmediate, etc.) and the
microtask queue (promise callbacks, `queueMicrotask()`, `[Link]()`). Event loop starvation
occurs when code keeps the loop busy for too long, preventing other queued tasks from executing.
Symptoms include:
1. **Synchronous blocking loops:** A while loop that runs for seconds will block the event loop.
During this time the browser cannot respond to user input, and [Link] cannot process incoming
requests.
```js
function crunchNumbers() {
let sum = 0;
sum += i;
502
return sum;
crunchNumbers();
```
2. **Unbounded microtask queues:** Promise callbacks and `[Link]()` run after the
current macrotask but before the next one. If you schedule more microtasks in each microtask, the
loop can get stuck processing microtasks forever and never return to macrotasks or I/O.
```js
function spinMicrotasks() {
[Link]().then(spinMicrotasks);
spinMicrotasks();
// The above code continually queues microtasks; timers and I/O starve
```
In both cases, the CPU is busy executing JavaScript while other callbacks starve waiting for a chance
to run.
1. **Break up long tasks:** Instead of doing all the work in one synchronous chunk, divide it into
smaller pieces and schedule each piece with `setTimeout()` or `setImmediate()`. This yields control
back to the event loop, allowing other tasks to run.
```js
let sum = 0;
function processChunk(i) {
503
sum += i;
} else {
callback(sum);
processChunk(start);
[Link]("Sum:", result);
});
```
By using `setTimeout(..., 0)`, the loop continues processing timers, I/O and microtasks between
chunks. For [Link]-specific code, you can use `setImmediate()` to schedule the next chunk after the
poll phase.
2. **Limit microtask recursion:** Avoid recursive chains of promises that queue additional promises
inside `.then()` callbacks or `queueMicrotask()`. If you need to process a large number of items with
promises, batch them or use asynchronous loops that yield:
```js
await doAsyncWork(item);
```
504
`await` yields control back to the event loop after each iteration, preventing microtask starvation.
3. **Use workers for CPU-heavy tasks:** Modern browsers support Web Workers, and [Link]
supports worker threads. Offload CPU-intensive computation to a worker so that the main event loop
remains responsive.
4. **Monitor and test:** Use performance profiling tools to detect long tasks and microtask churn.
When debugging, sprinkle `[Link]()` statements or timers to observe if certain callbacks aren't
firing when expected.
## Real-world analogy
Imagine a single-lane bridge controlled by a traffic light. Cars (tasks) can cross only when the light is
green. If one driver parks their car on the bridge and refuses to move, they block everyone behind
them—this is like a blocking loop. Alternatively, if a stream of endless motorcycles (microtasks) keeps
the light green for themselves, cars waiting at the other side can never cross. Preventing event loop
starvation means ensuring everyone gets a fair turn.
## Practice questions
1. **Theory:** What is event loop starvation? Describe two common scenarios that can lead to it.
2. **Theory:** Why does scheduling microtasks recursively cause starvation? How are microtasks
prioritised relative to macrotasks?
3. **Coding:** Modify the `crunchNumbers()` function to compute the sum of 1..1e8 without
blocking the event loop. Use either `setTimeout()` or `setImmediate()` to yield between chunks and
measure how it affects responsiveness.
4. **Coding:** Write code that creates an unbounded microtask chain using promises. Run it and
observe how it affects the execution of a `setTimeout(() => [Link]('timeout'), 0)` call. Then fix
the program by batching work with an asynchronous loop.
5. **Theory:** Explain how Web Workers (in browsers) or worker threads (in [Link]) help prevent
event loop starvation when performing heavy computations.
505
What is requestIdleCallback and when should you use
it?
# What is `requestIdleCallback()` and when should you use it?
Web applications often need to perform work that isn't critical to the next frame: analytics,
preloading data, cleaning caches, or other housekeeping tasks. Running these tasks on the main
thread at the wrong time can cause visible **jank**, resulting in dropped frames or delayed input.
The **`requestIdleCallback()`** API allows you to schedule non-urgent work to run during periods
when the browser is idle.
## How it works
`requestIdleCallback(callback, options?)` tells the browser, "call this function when you have spare
time." The browser schedules the callback at a point when it's not busy handling user input, layout,
or rendering. When the callback runs, it receives an **`IdleDeadline`** object that provides two
methods:
- `timeRemaining()` - Returns the estimated number of milliseconds remaining before the browser
needs to yield to high-priority work. You can check this inside your callback and split your work into
chunks if it runs long.
For example:
```js
function heavyTask(deadline) {
process(task);
if ([Link] > 0) {
506
requestIdleCallback(heavyTask); // schedule next chunk
requestIdleCallback(heavyTask);
```
In this pattern, you repeatedly process items until the deadline runs out. If work remains, you
schedule another idle callback. This keeps the main thread responsive while eventually finishing all
tasks.
## When to use it
- **Can be broken into small chunks:** if your work needs more time than a single idle period, split
it across multiple calls.
- **Don't need to run on every frame:** animation and layout should use
`requestAnimationFrame()` instead.
Avoid using `requestIdleCallback()` for tasks that must run at specific times (e.g. right before paint) or
require deterministic timing. Because idle time is unpredictable, your callback might run sooner or
later depending on user interactions and system load.
```js
const scheduleIdleTask =
[Link] ||
function (cb) {
507
// Run the callback after 200 ms if requestIdleCallback isn't available
return setTimeout(
200
);
};
```
With this fallback, older browsers will execute your idle tasks after a small delay instead of waiting for
a true idle period.
## Real-world analogy
Imagine an office worker who needs to file some paperwork but also answers the phone and greets
visitors. Filing is important but not urgent. The worker does it during lulls between phone calls and
walk-ins, stopping whenever someone needs attention. Likewise, `requestIdleCallback()` lets the
browser handle non-urgent tasks without disrupting critical user interactions.
## Practice questions
1. **Theory:** What kinds of tasks are good candidates for `requestIdleCallback()`? Why shouldn't
you use it for animation or layout work?
2. **Theory:** Describe the purpose of the `IdleDeadline` object. How can you use
`timeRemaining()` and `didTimeout` to split work into chunks?
3. **Coding:** Implement a polyfill for `requestIdleCallback()` using `setTimeout()`. Then write a
function that processes an array of 100,000 items during idle periods without freezing the UI.
4. **Coding:** Use `requestIdleCallback()` to prefetch a list of images when the page is idle. Include
a timeout so the prefetching still occurs if the browser never goes idle.
5. **Theory:** What happens if you schedule an idle callback with no timeout and the tab remains
busy? How might you mitigate that situation?
508
How do garbage collection triggers and mark-and-
sweep impact performance?
# How do garbage collection triggers and mark-and-sweep impact performance?
JavaScript frees memory automatically through **garbage collection** (GC). While automatic
memory management saves developers from calling `free()`, it isn't free of cost. Understanding what
triggers GC and how the **mark-and-sweep** algorithm works can help you write code that
performs better and avoids unexpected pauses.
In most JavaScript engines, you cannot explicitly invoke garbage collection (though some hosts
provide debugging APIs). Instead, the runtime decides when to run GC based on heuristics:
- **Allocation thresholds:** When a certain number of objects have been allocated or a memory
threshold is reached, the engine schedules a GC cycle to reclaim space.
- **Memory pressure:** If there isn't enough free memory to satisfy new allocations, the engine
immediately runs GC. This often happens on devices with limited RAM.
- **Idle periods:** Modern engines run incremental collection during idle times to reduce pauses.
For example, a browser might perform some marking work between frames.
- **Generational heuristics:** Many engines divide objects into "young" and "old" generations.
Short-lived objects (like those inside a function) are collected more frequently (minor GC), while
long-lived objects trigger less frequent but more expensive major collections.
You cannot predict exactly when GC will run, but you can influence how often it runs by controlling
how many objects you create and how long you retain them.
The classic GC algorithm used in JavaScript engines is **mark-and-sweep**. It runs in two phases:
1. **Mark phase:** Starting from a set of _roots_ (global variables, the call stack, closures), the
collector recursively **marks** all objects reachable through references. Reachable objects are
considered "live".
509
2. **Sweep phase:** The collector then scans the heap and **sweeps away** any objects that
weren't marked. These unreachable objects are reclaimed and their memory becomes available for
future allocations.
Modern collectors add variations such as **generational** (separate young and old generations),
**incremental** (break GC into small chunks), and **concurrent** (perform work on a background
thread) collection. These techniques reduce the duration of "stop-the-world" pauses where the
entire JavaScript thread is halted.
## Impact on performance
1. **Pause times (jank):** During a GC cycle, the engine may stop executing JavaScript to safely
traverse and modify memory. Long GC pauses can cause animations to stutter or block user input.
Minor collections are usually fast, but major collections can cause noticeable pauses.
2. **CPU usage:** Marking and sweeping take CPU time. If your code creates many short-lived
objects (for example, allocating new arrays inside a tight loop), the GC may run frequently,
consuming cycles that could be used for application logic.
Creating a large number of objects in a loop can trigger frequent minor collections:
```js
function allocateMany() {
return arr;
// Each object becomes a candidate for GC once arr goes out of scope
```
510
If `allocateMany()` is called repeatedly, the engine may interleave your code with GC cycles, slowing
down the overall throughput.
## Mitigating GC overhead
While you cannot stop GC, you can write code that makes it less intrusive:
- **Reuse objects:** Instead of creating new objects inside loops, reuse existing ones where
possible. This reduces allocation pressure.
- **Avoid retaining unnecessary references:** Let references go out of scope when you're done.
Storing objects in global variables or long-lived closures prevents them from being collected.
- **Be careful with large data structures:** Holding onto large arrays or Maps can delay GC. Clear
them (`[Link] = 0` or `[Link]()`) when you're finished.
- **Prefer primitives and typed arrays:** Plain numbers, strings and typed arrays are simpler for the
engine to manage than nested object graphs.
- **Use `WeakMap` and `WeakSet` for caches:** Weak collections hold references that do not
prevent objects from being collected, which is useful for memoisation caches.
- **Measure memory usage:** Browser developer tools and Node's `--inspect` flag provide memory
profiling to identify leaks and high allocation sites. Use them to verify that your changes reduce GC
overhead.
## Real-world analogy
Imagine your house's cleaning service. The cleaners show up when the place is cluttered, stop
everything, and tidy up. If you accumulate clutter quickly by buying things and never throwing
anything away, the cleaners have to come more often, interrupting your routine. By buying less and
discarding what you don't need, you reduce the frequency and duration of cleanings. Similarly,
controlling allocations and releasing references reduces GC interruptions.
## Practice questions
1. **Theory:** Explain the two phases of the mark-and-sweep algorithm. Why does the collector
need to stop executing JavaScript code during certain parts of the process?
2. **Theory:** What are generational garbage collectors, and how do they improve performance
compared to a single heap?
511
3. **Coding:** Write a function that allocates a large number of objects inside a loop. Use
performance tools to observe how GC frequency changes when you reuse a single object instead.
4. **Theory:** Name three things you can do in your code to reduce the frequency of garbage
collection cycles.
5. **Coding:** Implement a simple cache using `WeakMap` to store the results of an expensive
function. Explain how using `WeakMap` helps with memory management.
512
What causes detached DOM node memory leaks and
how to avoid them?
# What causes detached DOM node memory leaks and how to avoid them?
Memory leaks occur when your application stores references to objects longer than necessary,
preventing the garbage collector from reclaiming them. In web applications, a common source of
leaks is **detached DOM nodes**: elements that have been removed from the page but are still
referenced by JavaScript.
A **detached node** is an element that exists in memory but is no longer part of the document's
active tree. This can happen if you remove an element from the DOM (for example, calling
`[Link]()` or `[Link] = ''`) but keep a reference to it in a variable, array or
closure. Because the garbage collector sees the reference, it considers the node reachable and
doesn't free the memory associated with it.
Over time, detached nodes accumulate and consume memory, leading to degraded performance,
especially on long-running pages like single-page applications.
## Common causes
1. **Storing DOM references globally:** Assigning elements to global variables, object properties,
or arrays and never clearing them can keep nodes alive after they are removed from the DOM.
```js
function addItem() {
[Link] = "Item";
[Link](item);
[Link](item);
513
[Link]();
```
2. **Unremoved event listeners:** Adding an event listener to an element creates a reference from
the event loop to that element via the closure that contains the callback. If you remove the element
but never call `[Link]()`, the reference chain may keep it alive.
3. **Closures capturing DOM nodes:** Functions that enclose DOM variables can keep them alive
even after removal. If you store such functions or pass them around, the enclosed nodes persist.
- **Remove references:** Set your references to `null` or `undefined` when the node is no longer
needed. For collections, call `.splice()` or `.clear()`.
- **Use event delegation:** Attach listeners to a common ancestor instead of individual elements.
This reduces the number of listeners and avoids attaching callbacks directly to soon-to-be-removed
elements.
- **Detach event listeners:** Always call `[Link]()` before removing an
element. For class-based components, clean up listeners in a `destroy` or `unmount` method.
- **Monitor memory:** Use browser developer tools (Performance or Memory panels) to identify
detached nodes. Many tools highlight detached DOM trees and allow you to track their references.
```js
514
function addAndRemove() {
const el = [Link]("div");
[Link] = "Hello";
[Link](el);
setTimeout(() => {
[Link]();
}, 1000);
[Link](el);
```
```js
function addAndRemove() {
const el = [Link]("div");
[Link](el);
[Link](el);
setTimeout(() => {
[Link]();
}, 1000);
```
## Real-world analogy
515
Imagine removing an old piece of furniture from your living room but leaving a note in your home
inventory that it's still there. Every time you move or clean, you think the furniture exists and allocate
space for it. The physical item is gone, but because your records say otherwise, you never free the
space. Clearing or updating your inventory is like clearing references to detached DOM nodes.
## Practice questions
1. **Theory:** What is a detached DOM node? How do references in your JavaScript code prevent it
from being garbage-collected?
2. **Theory:** Why are unremoved event listeners a common source of memory leaks? How does
event delegation help?
3. **Coding:** Write a function that creates a list of 1,000 `<li>` elements and then removes them.
Use browser dev tools to verify whether any detached nodes remain. Modify the function to ensure
that no leaks occur.
4. **Coding:** Refactor a component that caches DOM nodes in an array to instead use a
`WeakMap` keyed by the node's ID. Explain why this prevents memory leaks.
5. **Theory:** Besides detached nodes, what other patterns can cause memory leaks in web
applications? How would you identify them?
516
What is the difference between microtasks, macrotasks,
and animation frames?
# What is the difference between microtasks, macrotasks and animation frames?
JavaScript executes code through an **event loop** that processes different kinds of tasks.
Understanding the distinction between **microtasks**, **macrotasks** and **animation frames**
helps you choose the right scheduling API and predict execution order.
## Macrotasks (tasks)
Macrotasks are the basic units of work in the event loop. They include:
- `setImmediate()` ([Link]).
When the event loop dequeues a macrotask, it executes it from start to finish. After the macrotask
completes, the browser drains the microtask queue (see below) and then may paint a frame. Each
macrotask is also called a **tick**.
## Microtasks
Microtasks are higher-priority callbacks that run **after the current macrotask but before the next
one**. They allow fine-grained scheduling so that state updates can occur before the browser
performs any rendering or the next event is handled. Examples include:
517
Microtasks are executed in order until the queue is empty. If a microtask schedules more microtasks,
they run immediately after, which can starve the event loop if not used carefully.
## Animation frames
`requestAnimationFrame()` is neither a microtask nor a regular macrotask; it's part of the browser's
rendering pipeline. If the page is in the background or hidden, the browser may throttle or stop
calling animation frame callbacks to save resources.
## Execution order
### Example
```js
[Link]("start");
[Link]().then(() => {
518
[Link]("promise");
});
[Link]("end");
```
Possible output (exact order can vary slightly across environments, but the sequence of categories is
consistent):
```
start
end
promise
microtask
animation frame
timeout
```
Explanation:
2. The promise callback is a microtask and runs after the current macrotask.
3. Within the promise, another microtask is queued and runs right away.
519
- **Microtasks (`queueMicrotask`, promises):** Use for short, high-priority work that must run
before the browser paints, such as updating component state after a DOM event handler. Don't
schedule long loops here or you might block rendering.
- **Macrotasks (`setTimeout`, `setInterval`, I/O callbacks):** Use for tasks that can wait until after
rendering, such as logging, analytics, or deferring heavy computations.
- **Animation frames (`requestAnimationFrame`):** Use for work that coordinates with the next
frame—reading layout, updating CSS transforms, performing animations. The callback runs only
when the browser is ready to paint, ensuring smooth motion.
## Practice questions
1. **Theory:** Explain the difference between a microtask and a macrotask. Why do microtasks run
before the next event loop tick?
2. **Theory:** Where does `requestAnimationFrame()` fit in the event loop relative to microtasks
and macrotasks? What happens if the page is hidden?
5. **Theory:** What problems can occur if you queue too many microtasks? How can you prevent
microtask starvation?
520
How do [Link] and PerformanceObserver
help in profiling?
# How do `[Link]()` and `PerformanceObserver` help in profiling?
Measuring the performance of your JavaScript code often requires more precision than `[Link]()`
can provide. The **Performance API** exposes high-resolution timestamps and tools for collecting
timing data, enabling developers to profile code and identify bottlenecks. Two key pieces of this API
are `[Link]()` and the `PerformanceObserver` interface.
```js
const t0 = [Link]();
doHeavyComputation();
const t1 = [Link]();
```
You can use `[Link]()` multiple times to measure different parts of your code. The high
resolution helps detect even small performance regressions.
The Performance API also allows you to create **marks** and **measures**:
521
- `[Link](name)`: records a timestamp with a given name.
```js
[Link]("fetch-start");
[Link]("fetch-end");
```
You can view these entries in the browser's performance tooling or access them programmatically.
`PerformanceObserver` lets you subscribe to performance entry events as they happen. You create
an observer with a callback that receives a list of new entries. The callback runs asynchronously, but
you can specify `buffered: true` to receive past entries as well.
```js
});
522
// Now whenever [Link]() is called, the observer callback logs the entry
```
- **`longtask`**: tasks that block the event loop for more than 50 ms, recorded by the Long Tasks
API.
This makes `PerformanceObserver` a powerful tool for monitoring your application in real time and
integrating performance metrics into your logging or analytics systems.
2. **Monitoring page load:** Observe `navigation` and `paint` entries to see when the page started
loading and when it rendered meaningful content (First Contentful Paint).
3. **Detecting long tasks:** Observe `longtask` entries to find parts of your code that block the
event loop. Breaking long tasks into smaller chunks can improve responsiveness.
4. **Custom metrics:** Define your own marks and measures around user interactions or API calls.
Send these metrics to your analytics endpoint to monitor performance in production.
## Real-world analogy
`[Link]()` is like using a stopwatch with microsecond precision instead of a wall clock. The
`PerformanceObserver` is like having a reporter who notes every milestone during a race—start,
halfway, finish—and hands you a detailed timeline at the end. With these tools, you can confidently
optimise your application.
## Practice questions
523
1. **Theory:** Why is `[Link]()` preferred over `[Link]()` for measuring short time
intervals? What characteristics make it more suitable?
3. **Coding:** Use marks and measures to time how long it takes to sort a large array of random
numbers. Log the duration using a `PerformanceObserver`.
4. **Coding:** Observe `longtask` entries with `PerformanceObserver` and write code that
intentionally blocks the event loop for 200 ms. Verify that a long task entry is recorded.
5. **Theory:** How can you integrate performance metrics collected via `PerformanceObserver`
into your application's analytics or monitoring dashboard?
524
What is the difference between queueMicrotask,
setTimeout, and requestAnimationFrame?
# What is the difference between `queueMicrotask`, `setTimeout` and `requestAnimationFrame`?
JavaScript offers several ways to schedule callbacks. Choosing the right one requires understanding
how they interact with the event loop and rendering pipeline. Here's how `queueMicrotask()`,
`setTimeout()` and `requestAnimationFrame()` differ.
## `queueMicrotask(callback)`
`queueMicrotask()` schedules a function to run **at the end of the current macrotask**, after the
current call stack unwinds but before the browser or Node processes the next event. It adds the
callback to the **microtask queue**, alongside resolved promise callbacks and `MutationObserver`
notifications.
Microtasks run in **FIFO** order and are executed continuously until the queue is empty. Because
they run before the next render, microtasks are ideal for small, immediate updates (e.g. updating
component state) that should happen before the user sees the result. However, you must not queue
heavy loops here or you risk starving the event loop.
Example:
```js
[Link]("start");
[Link]("end");
```
## `setTimeout(callback, delay)`
`setTimeout()` schedules a **macrotask**. The callback runs after at least the specified delay (in
milliseconds) has elapsed. Even `0` doesn't guarantee immediate execution; the callback will run on
the next tick after all current microtasks and rendering are done.
525
Use `setTimeout()` to defer work until after the browser repaints or to break large tasks into smaller
pieces. Timers have a minimum clamping delay (usually 1-4 ms) in modern browsers and may be
throttled in background tabs.
```js
```
## `requestAnimationFrame(callback)`
`requestAnimationFrame()` schedules a callback to run **just before the next repaint**. The
browser passes a timestamp to the callback, which you can use to synchronise animations. The
callback is executed after the microtask queue is empty but before painting. If the page is hidden,
many browsers throttle or suspend rAF callbacks to conserve resources.
Use rAF for any code that updates animations, reads layout, or writes styles. Scheduling animation
code here ensures it runs at the right time for smooth visuals.
```js
requestAnimationFrame((timestamp) => {
});
```
```js
526
queueMicrotask(() => [Link]("microtask"));
```
Possible output:
```
microtask
raf
timeout
```
Explanation:
1. The microtask runs first because it is scheduled at the end of the current task.
2. On the next tick, before painting, the browser invokes the rAF callback.
3. Finally, the timer callback runs. Since its delay is 0, it fires on the next event loop iteration after
microtasks and rAF.
| `queueMicrotask()` | After the current call stack, before rendering | Updating state that must
occur immediately, chaining promises |
| `requestAnimationFrame()` | Before the next repaint, synced with frame rate | Animations,
reading/writing layout, smooth UI updates |
527
Remember that microtasks should be short and non-blocking. If you need to schedule heavy
computation, split it with `setTimeout()` or `setImmediate()`. Use rAF to coordinate with the
browser's rendering loop.
## Practice questions
1. **Theory:** How does `queueMicrotask()` differ from `setTimeout(fn, 0)` in terms of when the
callback runs?
3. **Coding:** Write code that logs messages scheduled with `queueMicrotask()`, a resolved
promise, `setTimeout(fn, 0)`, and `requestAnimationFrame()`. Observe the order of execution in your
browser.
5. **Theory:** What precautions should you take when scheduling microtasks to avoid starving the
event loop? How can you break large microtasks into smaller chunks?
528
How do hidden classes and inline caching affect JS
performance internally?
# How do hidden classes and inline caching affect JavaScript performance internally?
JavaScript is a dynamic language: objects can have properties added and removed at any time, and
property names are strings. Yet modern engines like V8 execute property access at speeds
comparable to statically typed languages. Two key techniques make this possible: **hidden
classes** and **inline caching**.
## Hidden classes
When you create an object, the engine internally assigns it a **hidden class** (also called a _shape_
or _map_). A hidden class describes the layout of the object's properties—what properties it has and
in what order they were added. This allows the engine to determine the memory offsets of
properties and access them quickly.
```js
[Link] = name;
[Link] = age;
```
Both `alice` and `bob` share the same hidden class because the same properties are added in the
same order. The engine can treat them like instances of a "class" with known offsets for `name` and
`age`. Accessing `[Link]` becomes a simple offset lookup instead of a hash table search.
However, if you add a new property after creation, the engine creates a new hidden class:
529
```js
[Link] = "NYC";
```
Now `alice` has a different hidden class from `bob`, and property access on one cannot use the
assumptions made for the other. Similarly, adding properties in different orders (`obj.a = 1; obj.b = 2`
vs `obj.b = 2; obj.a = 1`) yields different shapes. Frequent shape changes inhibit optimisation.
- Initialise all properties in the constructor or object literal to keep objects sharing the same shape.
- Avoid adding or deleting properties after creation. Instead, set unused properties to `null` to
preserve the shape.
- Use classes or factory functions consistently so that similar objects follow the same property order.
## Inline caching
Even with hidden classes, property lookup still requires checking the object's class and then
calculating the offset. **Inline caching (IC)** speeds up repeated property accesses by caching the
location of a property for a given hidden class directly in the machine code.
When the engine first executes `[Link]`, it doesn't know the hidden class of `obj`. It generates a
**monomorphic inline cache** that records the hidden class encountered and the offset of `foo`.
The next time it sees `[Link]` with an object of the same class, it skips lookup and reads the
property directly. If it later encounters an object with a different class, the inline cache becomes
**polymorphic** and stores multiple class-offset pairs. Too many shapes can degrade performance
and cause the engine to fall back to slower generic lookup.
Inline caches are also used for method calls (`[Link]()`), array indexing, and binary operators.
They allow the engine to optimise dynamic code by specialising for the most common types it sees.
## Impact on performance
Hidden classes and inline caching turn dynamic property access into predictable, fast operations.
When your code uses consistent object shapes and avoids shape changes, the engine can generate
optimised machine code that performs at near-native speed. In contrast, code that adds properties
530
dynamically or stores heterogeneous objects in arrays may force the engine into megamorphic inline
caches, reducing optimisation opportunities.
```js
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
if (i % 2 === 0) {
obj.a = i;
obj.b = i;
} else {
obj.b = i;
obj.a = i;
531
[Link](obj);
```
## Real-world analogy
Hidden classes are like blueprints for houses in a new subdivision. If every house follows the same
blueprint, you know where each room is located. Inline caching is like keeping a floor plan in your
pocket so you can walk straight to the kitchen without looking around. If each house is rearranged
differently, the floor plan becomes useless, and you have to explore each time you visit.
## Practice questions
1. **Theory:** What is a hidden class (or shape) in a JavaScript engine? How does property order
affect hidden classes?
2. **Theory:** Describe the difference between monomorphic and polymorphic inline caches. What
causes an inline cache to become megamorphic?
3. **Coding:** Write two constructor functions that add properties in different orders. Create many
objects from each constructor and measure the time it takes to access a common property. Compare
the performance.
4. **Coding:** Refactor code that adds properties to an object after creation so that all properties
are initialised in the constructor. How does this change affect hidden classes and potential
optimisation?
5. **Theory:** Besides property access, what other operations in JavaScript engines use inline
caching? Why do engines cache these operations?
532
What is ResizeObserver and how is it different from
MutationObserver?
# What is `ResizeObserver` and how is it different from `MutationObserver`?
Modern web layouts often need to respond to changes in element size. Responsive components,
grids, and charts should adjust when their containers resize, even if the window itself doesn't
change. The **`ResizeObserver`** API solves this problem by notifying you when an element's size
changes. It complements the existing **`MutationObserver`**, which watches for changes in the
DOM tree but not for size changes. Understanding the difference helps you choose the right observer
for your task.
`ResizeObserver` lets you watch the dimensions of one or more elements. You create an observer
with a callback that receives a list of `ResizeObserverEntry` objects whenever the observed element's
**border box** (including padding and border) or **content box** (excluding padding and border)
changes size. The callback runs asynchronously—after layout but before paint—so repeated changes
are batched.
Example:
```js
});
[Link](box);
```
533
When the `.container` element grows or shrinks, your callback will run with the new dimensions. You
can call `[Link](element)` to stop watching or `[Link]()` to remove all observations.
Important notes:
- The callback may fire multiple times as the element changes (for example, when animating width).
Use throttling or logic inside the callback to avoid expensive recalculations on every pixel change.
- `ResizeObserver` runs before paint, so you should read sizes and apply layout adjustments but avoid
heavy DOM mutations that might trigger additional reflows.
It does **not** fire when an element's size changes due to CSS, flexbox, or grid adjustments. To use
it, you create a `MutationObserver` with a callback and call `observe()` on a target node, specifying
which mutations to watch:
```js
[Link]((mutation) => {
[Link]([Link]);
});
});
```
534
This callback will run whenever nodes are added or removed anywhere in the document body, but it
won't run if a node's size changes due to CSS.
## Key differences
| Callback timing | After layout, before paint | Microtask queue (after the current
macrotask) |
| Typical use cases | Responsive components, chart resizing | Updating UI when nodes are
added/removed, syncing attributes |
- Use **`ResizeObserver`** when you need to react to changes in element dimensions, such as
adjusting a canvas when its container resizes, or repositioning tooltips when their target grows.
- Use **`MutationObserver`** when you need to track changes to the DOM tree or attributes, like
updating a counter when items are added to a list, or running logic when an attribute is toggled.
- You can use both observers together: a mutation could add a new element, which you then observe
with a `ResizeObserver` to watch its size.
## Practice questions
3. **Coding:** Create a resizable panel and use a `ResizeObserver` to update a label showing its
current dimensions. Resize the panel using CSS and verify that the observer fires.
4. **Coding:** Use a `MutationObserver` to log whenever items are added to or removed from a
`<ul>` element. Then add a `ResizeObserver` to each `<li>` to log when a list item's size changes.
535
5. **Theory:** In what scenarios might you combine a `MutationObserver` and a `ResizeObserver`?
Give an example where reacting to both structure and size changes is necessary.
536
How does the Clipboard API work for copying and
pasting programmatically?
# How does the Clipboard API work for copying and pasting programmatically?
Copying and pasting data is an essential part of user interaction. Modern browsers expose the
**Clipboard API** to allow web pages to read from and write to the system clipboard
asynchronously, in a secure and user-friendly way. Using the Clipboard API, you can implement
features such as "Copy to clipboard" buttons, rich text editors and custom paste handlers.
## Basic operations
The Clipboard API lives on the `[Link]` object. It provides four primary methods:
- **`writeText(text)`** - Copies the given string to the clipboard. Returns a promise that resolves
when the text has been written.
- **`readText()`** - Reads plain text from the clipboard. Returns a promise that resolves with a
string.
- **`write(data)`** - Copies arbitrary data (like images or rich text) represented as an array of
`ClipboardItem` objects.
- **`read()`** - Reads arbitrary clipboard data and returns an array of `ClipboardItem` objects.
```js
try {
await [Link]("user@[Link]");
[Link]("Email copied!");
} catch (err) {
537
[Link]("#copy-btn").addEventListener("click", copyEmail);
```
```js
try {
[Link]("Pasted:", text);
} catch (err) {
[Link]("#paste-btn").addEventListener("click", pasteText);
```
To copy rich data, create `ClipboardItem` objects with MIME types and associated `Blob` data:
```js
await [Link]([item]);
```
When reading, iterate over the returned items and call `getType(mime)` to retrieve the blob. Note
that reading arbitrary clipboard data requires additional permissions (see below).
538
## Permissions and security considerations
Because the clipboard can contain sensitive data, browsers restrict access to it:
- **Secure context:** Clipboard API methods are only available on HTTPS pages (or `localhost`).
- **User gesture:** Writing to the clipboard generally requires a user gesture, such as clicking a
button. Reading from the clipboard often prompts the user to grant permission.
```js
```
In many browsers, reading from the clipboard will trigger a permission prompt the first time. Writing
text usually succeeds silently if called in response to a user action.
Some browsers don't support the asynchronous Clipboard API. A common fallback is to create a
temporary `<textarea>`, set its value, select it and use the now-deprecated
`[Link]('copy')` to perform the copy. However, this method doesn't support
images or rich content and is blocked in many contexts. When writing production code, detect
support for `[Link]` and provide a fallback only for text copying when necessary.
## Real-world analogy
The Clipboard API is like having a virtual clipboard in your app. You can politely ask the user to hand
you a note (read), or you can hand them a note to keep (write), but only when they're paying
attention (user gesture) and only if you're in the right room (secure context).
## Practice questions
539
1. **Theory:** What restrictions do browsers impose on clipboard access? Why are these
restrictions necessary?
2. **Theory:** Explain the difference between `writeText()` and `write()`. When would you use
each?
3. **Coding:** Implement a "Copy code" button that writes the contents of a `<pre>` element to the
clipboard and provides visual feedback when the operation succeeds or fails.
4. **Coding:** Write code that reads text from the clipboard and displays it in an input field. Handle
any errors or permission prompts gracefully.
5. **Theory:** How would you handle clipboard copying in a browser that does not support
`[Link]`? Discuss the limitations of your approach.
540
What is the Notification API and how can you request
user permission?
# What is the Notification API and how can you request user permission?
The **Notification API** allows websites to display system-level notifications to the user, even when
the page is not in focus. These notifications can inform users about chat messages, reminders,
updates or other events. Because they can be intrusive, the browser requires explicit user permission
before a site can show notifications.
## Requesting permission
Before creating a notification, check whether permission has been granted. The permission status
can be `'default'`, `'granted'` or `'denied'`.
```js
showNotification();
[Link]().then((permission) => {
showNotification();
});
function showNotification() {
icon: "/images/[Link]",
});
[Link] = () => {
[Link]();
541
[Link]();
};
```
`requestPermission()` returns a promise that resolves with the user's decision. You should call it in
response to a user gesture (like clicking a button) rather than immediately on page load, as browsers
may block unprompted permission requests.
## Notification options
- `badge`: A monochrome symbol for small contexts (like Android status bars).
- `actions`: An array of action buttons, each with a title and an action identifier.
- `tag`: A string that groups notifications. Notifications with the same tag replace each other instead
of stacking.
- `requireInteraction`: If `true`, the notification stays visible until the user dismisses it.
Different platforms support different subsets of options, so test on your target devices.
In Progressive Web Apps, you can show notifications from a **service worker**. This is essential for
push notifications received when the site isn't open. Inside the service worker, use
`[Link](title, options)` instead of creating a `Notification` object directly. A
push event might look like this:
```js
542
const data = [Link]?.json() ?? {};
[Link](
[Link](title, {
body: message,
icon: "/images/[Link]",
})
);
});
```
Service worker notifications behave similarly, but you don't need to request permission inside the
worker; permission is granted or denied globally for your origin.
## Best practices
- **Ask at the right time:** Request permission after the user performs an action that justifies
notifications. Asking on page load often leads to denial.
- **Be respectful:** Don't spam users. Send notifications only when they're useful. Provide clear
ways to manage preferences.
- **Handle denied and default states:** Some users will deny permission or ignore prompts.
Gracefully degrade by updating the UI without notifications.
- **Use tags:** Group notifications with the same tag to avoid cluttering the notification tray.
## Real-world analogy
Requesting notification permission is like asking someone if it's okay to send them text messages. If
they agree, you can send messages when something important happens. If they decline, you respect
their choice and find other ways to communicate.
## Practice questions
543
1. **Theory:** What are the possible values of `[Link]`, and what do they mean?
When is it appropriate to call `[Link]()`?
2. **Theory:** Describe some of the options you can specify when creating a new notification.
Which ones might not be supported on all platforms?
3. **Coding:** Implement a button that asks the user for notification permission and, upon
approval, displays a custom notification with an action button. Handle the action by opening a
specific page.
4. **Coding:** Write service worker code that listens for a push event and shows a notification using
`[Link]()`. Include a `tag` so new notifications replace older ones.
5. **Theory:** Why should you avoid requesting notification permission immediately when the page
loads? Suggest a user flow that leads to higher opt-in rates.
544
Explain the Battery Status and Network Information APIs
# Explain the Battery Status and Network Information APIs
Web applications are becoming more aware of their host environment. Two browser APIs—the
**Battery Status API** and the **Network Information API**—allow your app to adapt its
behaviour based on device battery level and network conditions. While both APIs are subject to
privacy and support limitations, understanding them can help you design more responsive,
energy-efficient applications.
### Overview
The Battery Status API exposes information about the system's battery through the
`[Link]()` method. This method returns a promise that resolves to a `BatteryManager`
object with the following properties:
- **`level`** - Battery charge level as a number between 0 and 1 (e.g. `0.5` for 50%).
- **`chargingTime`** - Approximate time (in seconds) until the battery is fully charged (0 when
already full).
- **`dischargingTime`** - Approximate time (in seconds) until the battery is empty (Infinity if
charging).
The `BatteryManager` object also emits events when these values change: `chargingchange`,
`levelchange`, `chargingtimechange`, and `dischargingtimechange`. Example usage:
```js
function update() {
[Link](`Charging: ${[Link]}`);
545
update();
[Link]("levelchange", update);
[Link]("chargingchange", update);
monitorBattery();
```
With battery information, you can postpone non-essential tasks when power is low, reduce network
usage, or warn the user before a long download.
Due to fingerprinting concerns (battery state could uniquely identify a device), many browsers have
removed or restricted access to this API. Chrome and Edge dropped support, while some mobile
browsers still implement it. Always check browser compatibility and provide fallbacks. If
`[Link]` isn't available, avoid gating core functionality on it.
### Overview
The Network Information API exposes network quality and connection type via
`[Link]` (also known as `[Link]`). The `connection` object
includes properties such as:
- **`effectiveType`** - An estimate of the current connection quality (e.g. `'slow-2g'`, `'2g'`, `'3g'`,
`'4g'`).
- **`saveData`** - `true` if the user has enabled a "reduce data usage" setting.
The API also emits a `change` event when the connection changes. Example:
546
```js
function handleConnectionChange() {
[Link](
);
if ([Link]) {
if ("connection" in navigator) {
handleConnectionChange();
[Link]("change", handleConnectionChange);
```
You can use this information to adjust media quality, defer updates, or choose between offline and
online modes. For example, a video player might automatically lower resolution when the
connection is poor.
Like the Battery API, the Network Information API is not universally supported (mainly available in
Chrome and some Android browsers). Some browsers deliberately provide coarse or randomised
values to prevent fingerprinting. Always check for feature presence and use reasonable defaults
when it's missing.
## Real-world analogy
Think of your application as a restaurant. The Battery Status API is like knowing how much fuel is left
in your delivery truck; you might postpone long deliveries if the tank is nearly empty. The Network
547
Information API is like checking road conditions; you might choose a slower route or defer deliveries
when traffic is heavy. By adapting to energy and network conditions, you provide a better service
without wasting resources.
## Practice questions
1. **Theory:** What information does the `BatteryManager` object provide? Why might a browser
choose to restrict access to this API?
3. **Coding:** Write a function that uses the Battery API (if available) to postpone a large file
download when the battery level is below 20% and the device isn't charging.
4. **Coding:** Use the Network Information API to adjust the quality of images loaded on a page. If
the connection type is `'slow-2g'` or `'2g'`, load low-resolution images; otherwise, load
high-resolution images.
5. **Theory:** What are some potential privacy risks of exposing battery and network information
to websites? How can developers design applications that respect user privacy while still benefiting
from these APIs?
548
What is the Fetch streaming API and how can you
consume a streamed response?
# What is the Fetch Streaming API and how can you consume a streamed response?
The **Fetch API** is the modern way to make network requests in JavaScript. By default, high-level
methods like `[Link]()` or `[Link]()` buffer the entire response into memory before
returning it. For large downloads or real-time data, this can be inefficient or cause jank. The **Fetch
Streaming API** exposes the response body as a **ReadableStream**, allowing you to consume
the data chunk by chunk as it arrives.
- **Large files:** Streaming prevents large responses from blocking memory and allows you to
process parts of the file immediately (e.g. show progress while downloading a video).
- **Progressive rendering:** You can progressively display content (like streaming HTML or logs)
instead of waiting for the full response.
- **Real-time feeds:** Server-sent events or chat messages can be delivered over long-lived HTTP
requests that stream updates.
When you call `fetch()`, the returned `Response` object has a `body` property—an instance of
`ReadableStream`—if the response supports streaming. There are two main ways to consume it:
using a reader or using async iteration.
```js
while (true) {
549
const { value, done } = await [Link]();
if (done) break;
streamText("/[Link]");
```
`[Link]()` returns a promise that resolves with an object containing a `Uint8Array` of bytes and
a `done` flag. Use a `TextDecoder` to convert bytes into strings. By processing each chunk, you can
update progress indicators or parse partial content.
```js
buffer = [Link]();
if (line) {
550
}
if (buffer) {
[Link]("Remaining:", buffer);
```
This example reads a newline-delimited JSON feed. Using `for await...of` yields each `Uint8Array`
chunk. You accumulate partial lines until a newline and then parse JSON objects as they arrive.
Because a response body is a `ReadableStream`, you can pipe it to other streams or build new
`Response` objects from it. For example, you can stream a large file directly into a `WritableStream`
provided by the [File System Access API] or into a custom transform stream that compresses data.
Streaming requests can be cancelled using `AbortController`. If the network connection drops, an
exception will be thrown when reading from the stream. Surround your streaming logic with
`try...catch` and handle partial data accordingly. Always close or cancel the reader if you stop
consuming early.
## Real-world analogy
Imagine downloading a long podcast. Instead of waiting for the entire file to download before you
start listening, streaming lets you begin listening while the rest of the file arrives. Similarly, the Fetch
Streaming API allows you to process data progressively rather than waiting for the entire response.
## Practice questions
551
1. **Theory:** Why is streaming a response more memory-efficient than using `[Link]()` or
`[Link]()` on large files?
2. **Theory:** What are the advantages of processing a streamed response as it arrives? Give two
real-world scenarios where this approach is beneficial.
3. **Coding:** Write a function that downloads a large binary file using `fetch()` and streams it into
a `WritableStream` that writes to the browser's IndexedDB or File System Access API. Show how to
display progress.
5. **Theory:** How can you cancel an ongoing fetch streaming operation? What happens to the
`ReadableStream` when you call `abort()` on the `AbortController`?
552
What is the Web Share API and when is it useful?
# What is the Web Share API and when is it useful?
Sharing content—links, text, images—is a common action on mobile devices. The **Web Share
API** allows websites to trigger the native sharing interface of the operating system, letting users
seamlessly share data to social apps, messaging apps or other targets installed on their device. This
API bridges the gap between web and native apps by providing a simple JavaScript interface for
invoking the system share sheet.
## Basic usage
The API is exposed on `[Link]()`. It accepts an object with up to three properties: `title`,
`text` and `url`.
```js
if ([Link]) {
try {
await [Link]({
url: "[Link]
});
} catch (err) {
} else {
[Link]("#share-button").addEventListener("click", shareArticle);
553
```
Calling `[Link]()` returns a promise that resolves when the user completes or cancels the
share action. The method must be invoked in response to a user gesture (like a button click) and only
works in **secure contexts** (`https` or localhost).
## Sharing files
An extension to the API (Level 2) allows sharing files using the `files` property. This lets you share
images, videos or arbitrary blobs. You create `File` objects and pass them in an array:
```js
[Link](resolve, "image/png")
);
```
File sharing is supported only on certain platforms (primarily Android and Chrome OS) and may
require user gestures.
- **User convenience:** Users are familiar with the system share sheet. Integrating it provides a
native feel and reduces friction compared to copying a link manually.
- **Consistency:** You don't need to implement your own share UI or integrate with each social
platform's SDK. The OS handles the user's preferred apps.
- **Deep linking:** The `url` property encourages sharing canonical links, improving SEO and
discoverability.
- **Progressive enhancement:** You can detect support for `[Link]` and fall back to
copying to clipboard or showing a share modal when unsupported.
554
## Limitations and considerations
- **Platform support:** The Web Share API is primarily supported on mobile browsers (Chrome,
Edge, Samsung Internet). Desktop support is limited and may require experimental flags.
- **User gesture required:** Browsers restrict `[Link]()` to user-initiated events. You can't
auto-trigger sharing on page load.
- **No preview control:** Unlike custom share widgets, you can't customise the appearance of the
share sheet. The OS decides how to present options.
- **Privacy:** The API doesn't provide feedback about which app the user chose or the content of
the share. You only know whether it succeeded or failed.
## Real-world analogy
Imagine you're at a party and want to pass along a photo. You could either ask each friend what
messaging app they use or hand them your phone and let them choose the app themselves. The
Web Share API is like handing over your phone—it uses the person's preferred channel without you
needing to know anything about it.
## Practice questions
1. **Theory:** What security and user interaction requirements must be met for `[Link]()`
to work? Why are these restrictions in place?
2. **Theory:** Describe the differences between Web Share API Levels 1 and 2. When would you
use the `files` property?
4. **Coding:** Create a function that captures a screenshot of a canvas element and shares it via the
Web Share API (if supported). Handle errors gracefully.
5. **Theory:** What are some scenarios where using the Web Share API enhances user experience
compared to a custom in-app share modal?
555
How does the Web Crypto API provide secure
randomness and hashing?
# How Does the Web Crypto API Provide Secure Randomness and Hashing?
The **Web Crypto API** is a set of low-level cryptographic primitives built into modern browsers. Its
goal is to provide secure, performant operations—such as key generation, encryption/decryption,
signing and hashing—without exposing the raw implementation details. Two of the most commonly
used features are _secure random number generation_ and _cryptographic hashing_. This article
explains how the Web Crypto API implements these features and how you can use them safely.
## Secure randomness
- **`[Link]()`** - returns a floating-point number between `0` and `1`. It is _not_ suitable for
security-sensitive applications because the underlying algorithm is designed for speed, not
unpredictability. Attackers can sometimes predict outputs or reconstruct seeds.
- **`[Link]()`** - part of the Web Crypto API. This function fills a typed array (e.g.
`Uint8Array`, `Uint32Array`) with values drawn from the operating system's cryptographically secure
pseudo-random number generator (CSPRNG). These values are unpredictable and suitable for
generating keys, nonces, tokens or identifiers.
```js
[Link](bytes);
[Link](bytes);
.join("");
556
[Link](token);
```
The API guarantees that every call returns new random values. You must provide your own typed
array; the method does not allocate memory for you. Because it relies on the operating system's
CSPRNG, it is unsuitable for use in a deterministic environment such as seeded pseudo-random
simulations.
The Web Crypto API makes it easy to generate [UUID version 4]([Link]
[Link]/rfc/rfc4122) values. A UUID is a 128-bit identifier with specific bits set according to the
standard. Here's a helper function:
```js
function generateUUIDv4() {
const parts = [
[Link](0, 4),
[Link](4, 6),
[Link](6, 8),
[Link](8, 10),
[Link](10, 16),
].map((arr) =>
[Link](arr)
.join("")
);
return [Link]("-");
557
[Link](generateUUIDv4());
```
This example shows how to manipulate the random bytes to comply with the UUID format and then
convert them into a human-readable string. Because the randomness comes from
`[Link]()`, the resulting UUIDs are safe to use as identifiers in security-sensitive
contexts (e.g. session IDs).
## Cryptographic hashing
Hash functions map arbitrary data to fixed-length digests. A secure hash function must be
deterministic, fast to compute, and computationally infeasible to invert or find collisions for. The
Web Crypto API exposes hash functions via the asynchronous `SubtleCrypto` interface:
```js
.join("");
[Link](digestHex);
// Output: a591a6d40bf420404a011733cfb7b190...
```
The `digest` method takes an algorithm name (e.g. `'SHA-256'`, `'SHA-1'`, `'SHA-384'`, `'SHA-512'`)
and an `ArrayBuffer` or typed array. It returns a `Promise` that resolves with an `ArrayBuffer`
containing the digest. Converting this buffer into a hex or base64 string requires manual processing
as shown above.
558
You can compute a hash of a large file without reading it all at once by streaming it through the
`ReadableStream` API and updating a hash incrementally. However, `SubtleCrypto` does not currently
support incremental hashing. To hash large files efficiently you can read chunks into memory, append
them to a `Uint8Array`, and hash the entire buffer when done. For truly incremental hashing you
need a third-party library.
## Important considerations
- **Algorithm support** - Browsers may support different sets of algorithms. Common hashes
(SHA-1/2) and random value generation are widely available, while more advanced algorithms like
Argon2 or BLAKE3 are not supported. Check `[Link]` for available methods.
- **No direct encryption API for randomness** - The Web Crypto API exposes low-level building
blocks. You still need to design protocols correctly (e.g. choose unique nonces, manage keys securely)
when performing encryption or hashing operations.
- **Security of random values** - Always use `[Link]()` rather than
`[Link]()` for security-critical randomness. Do not attempt to seed or re-seed the CSPRNG.
## Practice questions
2. **Coding:** Write a function that generates a 32-byte cryptographically secure random token and
returns it as a base64 string.
3. **Theory:** Describe how the `[Link]()` method works. Why does it return a
Promise? What steps do you need to perform to convert the digest into a hexadecimal string?
4. **Coding:** Implement a function that takes a user-provided password string, salts it with a
random 16-byte salt, hashes it using SHA-256, and returns both the salt and the hash in hex format.
559
What is [Link] and why is it safer
than [Link]?
# What Is `[Link]()` and Why Is It Safer Than `[Link]()`?
When writing client-side JavaScript, you often need random values—for example to generate session
tokens, unique IDs, salts, or keys. JavaScript provides two different randomness sources: the built-in
`[Link]()` function and the `[Link]()` method from the Web Crypto API.
They differ in design, purpose and security. Understanding those differences is essential when
building secure applications.
```js
function generateCode() {
.toString()
.padStart(6, "0");
[Link](generateCode());
```
560
While this code might appear to work, someone with access to the underlying pseudo-random
sequence could predict or reproduce the same codes. Attackers have exploited predictable
randomness in the past to break authentication schemes.
`[Link]()` is part of the Web Crypto API and uses the browser's operating system
to generate cryptographically secure random values. It fills a supplied typed array (e.g. `Uint8Array`,
`Uint32Array`) with random bytes. These values are drawn from a secure source and are
unpredictable by design.
```js
function generateSecureToken() {
[Link](array);
return btoa(binary);
[Link](generateSecureToken());
```
561
2. **Uniform distribution:** Values returned by `[Link]()` are uniformly
distributed across the available range of the typed array. This avoids biases that can occur when
manually mapping random floats to integers.
4. **Independent instances:** Calling `[Link]()` in one tab does not affect the
internal state for other calls or other tabs. By contrast, some implementations of `[Link]()`
maintain a single global state that persists across calls.
## Best practices
- **Always prefer `[Link]()` for any sensitive random data.** Use it when
generating CSRF tokens, password reset codes, salts, encryption keys or unique identifiers.
- **Do not attempt to re-seed or seed the CSPRNG yourself.** The system manages its own entropy
pool; manual seeding can weaken security.
- **Use typed arrays appropriate for your needs.** For example, use a `Uint32Array` to generate
random 32-bit integers, or a `Uint8Array` for byte sequences. Convert to other formats (hex, base64)
as needed.
- **Combine with a cryptographic hash or HMAC when necessary.** Sometimes you'll use
`[Link]()` to create a secret, then derive keys using `[Link]()`
and `[Link]()` for stronger key derivation and management.
## Practice questions
1. **Theory:** Explain why values generated by `[Link]()` can sometimes be predicted. What
kind of problems might this cause?
4. **Coding:** Modify the secure token generator to return a URL-safe base64 string (replace `+`
with `-` and `/` with `_` and remove padding).
562
Explain WebRTC basics — data channels and peer
connections
# WebRTC Basics: Data Channels and Peer Connections
**WebRTC (Web Real-Time Communication)** is a set of browser APIs that enables peer-to-peer
audio, video and data communication without requiring plugins. It allows two (or more) devices to
exchange streams directly, making features like video conferencing, file sharing and collaborative
editing possible in the browser. This article focuses on the core pieces—**`RTCPeerConnection`**
and **`RTCDataChannel`**—and walks through an example of establishing a simple data channel.
## RTCPeerConnection
- **Signaling and negotiation** - exchanging session descriptions (SDP) and network information
(ICE candidates) through an external signalling channel of your choice (WebSocket, HTTP, WebSocket
server). WebRTC does not define signaling; you must implement your own mechanism to transfer
negotiation messages between peers.
- **Network traversal** - using STUN and optionally TURN servers to determine public endpoints
and traverse NAT/firewalls.
- **Media and data streams** - creating and managing media tracks (audio/video) and data
channels over the connection.
```js
iceServers: [
{ urls: "stun:[Link]" },
],
});
// When the browser gathers a new ICE candidate (network info), send it to the other peer
563
[Link] = (event) => {
if ([Link]) {
};
await [Link](offer);
};
```
## RTCDataChannel
While WebRTC is often associated with video and audio, it also includes **data channels**—
bidirectional communication channels for arbitrary data. Data channels use the same underlying
transport as media streams (SRTP over UDP) and can be configured for reliability
(`ordered`/`maxRetransmits`) and congestion control. They are ideal for sending small messages or
file chunks in real time.
```js
564
[Link] = (event) => {
};
```
Once the channel is open, you can send and receive strings or binary data:
```js
// Sending an ArrayBuffer
[Link](buffer);
```
To establish a connection between two peers you need a signalling mechanism to exchange offers,
answers and ICE candidates. A simplified flow:
1. **Create peer connections** on both peers and set up event handlers for `icecandidate`,
`onnegotiationneeded` and `ondatachannel`.
3. **Caller creates offer**: call `createOffer()`, set it locally with `setLocalDescription()`, then send
the offer to the other peer through your signalling server.
4. **Callee receives offer**: call `setRemoteDescription()`, then call `createAnswer()`, set it locally,
and send the answer back.
565
5. **Exchange ICE candidates**: as each peer generates candidates, send them to the other peer
and call `addIceCandidate()` to add them. Once enough candidates are gathered, a direct or relay
connection is established.
6. **Use the data channel**: when the channel's `onopen` event fires on both peers, you can call
`send()` and handle `onmessage` events.
- **Peer-to-peer chat or file sharing** - Users can send messages or transfer files directly without
routing data through a server, reducing latency and bandwidth costs.
- **Signalling is not included** - You must implement or use an external signalling server to
exchange negotiation messages. Without signalling, peers cannot discover each other.
- **Firewall/NAT traversal** - WebRTC attempts a direct connection, but may fall back to relaying
via TURN servers. This can affect latency and requires additional server infrastructure.
- **Security** - Data channels are encrypted. However, you still need to handle authentication and
access control at the application level.
- **Browser support** - All major browsers support WebRTC, but some features differ. Mobile
browsers can have additional limitations. Always feature-detect and provide fallbacks.
## Practice questions
1. **Theory:** What purpose do STUN and TURN servers serve in WebRTC? Why are they
necessary?
2. **Coding:** Implement a simple signalling mechanism using WebSockets that exchanges offers,
answers and ICE candidates between two peers.
3. **Theory:** Explain the difference between a data channel created with `ordered: true` and one
with `ordered: false`. When might you use an unordered channel?
4. **Coding:** Write a function that sends a file over a data channel by splitting it into chunks and
reassembling it on the receiving peer.
566
567
What is AbortController and how do you use it to cancel
fetch requests?
# What Is `AbortController` and How Do You Use It to Cancel Fetch Requests?
Long-running network requests can become unnecessary if the user navigates away, changes input or
performs a different action. Canceling these requests frees up resources and improves
responsiveness. The **AbortController API** provides a standardized way to cancel asynchronous
operations, including `fetch()` calls, by signalling an abort event.
`AbortController` is a built-in interface that creates an **abort signal** (`AbortSignal`). The signal
can be passed to APIs that support abortable operations—most notably `fetch()`, `ReadableStream`,
and Web Crypto methods. When the controller's `abort()` method is called, it triggers an `abort`
event on the signal, allowing the consuming API to cancel the operation.
```js
fetch("[Link] { signal })
.catch((err) => {
[Link]("Fetch aborted");
} else {
568
});
setTimeout(() => {
[Link]();
}, 200);
```
When `[Link]()` is called, the fetch operation rejects with an `AbortError`. Catching this
error allows your application to distinguish an intentional abort from a network failure or other
exception.
Abort controllers are often tied to user actions. For example, cancel a search request when the user
types a new query:
```js
let searchController;
if (searchController) [Link]();
try {
signal: [Link],
});
displayResults(results);
} catch (err) {
569
if ([Link] !== "AbortError") {
showError(err);
search([Link]);
});
```
In this example, each time the user modifies the search box, the previous request is cancelled. Only
the latest request continues, preventing outdated results from racing with newer ones.
You can implement request timeouts using `AbortController` without `setTimeout()` race conditions.
Here's a helper function that wraps `fetch()` with a timeout:
```js
try {
...options,
signal: [Link],
});
clearTimeout(id);
return response;
} catch (err) {
570
clearTimeout(id);
throw err;
// Usage
.catch((err) => {
});
```
This pattern ensures that the fetch is aborted if it takes longer than the specified timeout. You can
also pass an external `AbortSignal` via `[Link]()` (in environments that support it) to
simplify this logic.
- **API support** - Only certain web APIs support abort signals. Many modern ones do (fetch, some
streams, some crypto operations), but older APIs do not. Always refer to documentation.
- **One-time use** - An `AbortController` can only be aborted once. After calling `abort()`, the signal
stays aborted and cannot be reused. Create a new controller for each new operation.
- **Server cooperation** - Canceling a fetch aborts the client-side processing, but the server may
still process the request. For long-running server operations, implement server-side cancellation
mechanisms (e.g. using websockets or custom abort signals).
## Practice questions
571
1. **Theory:** What happens when a `fetch()` with an associated `AbortSignal` is aborted? How can
your code distinguish between an abort and another type of error?
2. **Coding:** Modify the search example so that it shows a spinner while the request is in progress
and hides it when the request finishes or is aborted.
3. **Theory:** Why can a single `AbortController` only cancel once? Describe a situation where you
would use `[Link]()`.
4. **Coding:** Write a wrapper around `fetch()` that accepts an optional `AbortSignal`. It should
return a promise that rejects with a custom error if the fetch is aborted or times out.
572
What is Content Security Policy (CSP) and why is it
important?
# What Is Content Security Policy (CSP) and Why Is It Important?
**Content Security Policy (CSP)** is a security standard that helps prevent a broad class of injection
attacks—such as cross-site scripting (XSS), clickjacking and data injection—by controlling which
resources the browser is allowed to load and execute. CSP is delivered via HTTP headers or `<meta>`
tags and instructs the browser to enforce restrictions on scripts, styles, images, fonts, frames and
other assets. By defining a strict policy, you significantly reduce the attack surface of your application.
Injection attacks occur when an attacker can inject arbitrary HTML or JavaScript into a page. The
browser executes this code with the same privileges as trusted code, allowing attackers to steal
cookies, deface pages or perform actions on behalf of the user. Even with input validation and output
encoding, mistakes can happen. CSP acts as a _second line of defense_ by preventing the browser
from executing untrusted code even if it makes its way into the DOM.
Without CSP, a single unescaped user comment containing `<script>alert('xss')</script>` could trigger
an alert or worse. With a properly configured CSP that disallows inline scripts and only allows scripts
from trusted domains, the browser will block the injection.
## Basic syntax
- `script-src` - allowed sources for JavaScript. Accepts URLs, `'self'`, `'none'`, `'unsafe-inline'`, `'unsafe-
eval'`, hashes, and nonces.
- `img-src`, `font-src`, `frame-src`, etc. - allowed sources for images, fonts, frames.
573
A simple CSP might look like this:
```http
```
This policy allows content to be loaded from the site's own origin (`'self'`). Scripts are permitted from
the same origin and a trusted CDN. Inline styles are allowed (though this weakens protection) and all
plugins (`<object>`, `<embed>`) are disallowed.
To allow specific inline scripts or styles while blocking arbitrary inline code, CSP supports **nonces**
and **hashes**:
- **Nonces** - A nonce is a random string generated on each request and included as a `nonce`
attribute on `<script>` or `<style>` tags. The policy specifies `script-src 'nonce-<value>'`. Only scripts
with the matching nonce are executed; all others are blocked.
- **Hashes** - You can include a hash of the exact script contents in the policy (`script-src 'sha256-
<hash>'`). The browser computes hashes of inline scripts and executes them only if they match an
allowed hash. This prevents script tampering.
Nonces and hashes allow inline scripts without opening up vulnerabilities to arbitrary injection. They
are preferable to `'unsafe-inline'`, which disables inline script protections entirely.
### Reporting
CSP can be configured to report violations via the `report-uri` or `report-to` directives. When a
violation occurs, the browser sends a JSON report to the specified endpoint containing details like
the blocked URI and violated directive. You can enable **report-only mode** by using the `Content-
Security-Policy-Report-Only` header. This is useful for testing a policy before enforcing it.
574
1. **Start in report-only mode.** Apply a strict policy with the `-Report-Only` header to capture
violations in production without blocking users. Review and adjust the policy based on reports, then
switch to enforcement mode.
2. **Avoid broad sources.** Using `*` or allowing entire external domains (e.g.
`[Link] increases risk if those domains are compromised. Permit only trusted,
specific hostnames.
3. **Disable inline execution.** Remove `'unsafe-inline'` and `'unsafe-eval'` from your `script-src`
directive. Use external scripts or nonces/hashes for inline code.
4. **Use per-request nonces.** Generate a new random nonce on each request to prevent reuse
and make it difficult for attackers to guess.
5. **Combine with other defenses.** CSP complements but does not replace input validation,
output encoding and proper authentication/authorization. Use CSP as part of a layered security
strategy.
## Practice questions
1. **Theory:** Describe how CSP can prevent XSS even when an attacker manages to inject a
`<script>` tag into your page.
2. **Coding:** Write an [Link] middleware that sets a CSP header blocking inline scripts but
allowing scripts from `[Link]`. Add a report endpoint that logs violations.
4. **Coding:** Explain how you would use a random nonce to permit a single inline script to run
while still blocking other inline scripts.
575
What is cross-site scripting (XSS) and how can JavaScript
prevent it?
# What Is Cross-Site Scripting (XSS) and How Can JavaScript Prevent It?
**Cross-Site Scripting (XSS)** is a security vulnerability that allows attackers to inject malicious
scripts into web pages viewed by other users. When a vulnerable page displays unsanitised user
input as HTML, an attacker can inject a `<script>` tag, event handler or other code that runs in the
victim's browser. This malicious code can steal cookies, read sensitive data, or perform actions on
behalf of the user. XSS is one of the most common web vulnerabilities and comes in three main
forms: stored, reflected and DOM-based.
## Types of XSS
1. **Stored XSS (persistent)** - The attacker stores malicious code on the server (e.g. in a comment
field). Each time a user loads the page, the stored script runs.
2. **Reflected XSS** - Malicious code is included in a URL or form parameter and immediately
reflected back in the response. Users who click the link trigger the script.
3. **DOM-based XSS** - The client-side JavaScript manipulates the DOM using untrusted input (e.g.
`[Link]`) and inserts it into the page using `innerHTML`, causing the injection to run without a
new HTTP response.
Regardless of type, XSS occurs because **untrusted data is treated as code**. Preventing XSS
requires ensuring that data is safely encoded or sanitised before it reaches the DOM.
## Prevention techniques
When inserting user content into the DOM, avoid APIs that interpret input as HTML. For example,
instead of using `innerHTML`, use `textContent` or `innerText` to insert untrusted strings. These
methods treat the input as plain text and do not parse markup:
```js
// Unsafe: This will parse HTML and execute any embedded scripts
[Link] = `<p>${comment}</p>`;
576
// Safe: Inserts text without interpreting it as markup
[Link] = comment;
[Link] = [Link](userHtml);
```
When generating HTML on the server, ensure you properly escape special characters (`<`, `>`, `&`, `'`,
`"`) before inserting user content. Most templating engines have built-in mechanisms to do this
automatically when using safe interpolation.
CSP can mitigate XSS by blocking inline scripts and restricting the domains from which scripts can be
loaded. Even if an attacker manages to inject markup, the browser will refuse to run it if it violates
the policy. Use nonces or hashes to allow only known inline scripts and disallow `'unsafe-inline'`.
Never trust user input. Validate data on both the client and server for type, length and format. Reject
or sanitise unexpected values. For rich text editors or user-generated HTML, use a robust sanitiser
like [DOMPurify]([Link] to remove dangerous tags and attributes.
577
If you're using client-side templating libraries (e.g. React, Vue, Handlebars), avoid dangerously setting
HTML. React automatically escapes content when using JSX. Only use `dangerouslySetInnerHTML`
when you have sanitised input.
Mark session cookies as `HttpOnly` and `Secure` so that they cannot be accessed via JavaScript and
are transmitted only over HTTPS. This reduces what an attacker can do even if they manage to inject
a script.
## Practice questions
1. **Theory:** Explain the difference between stored and reflected XSS. Give an example of each.
```js
```
3. **Theory:** How does using a CSP header complement other XSS prevention techniques? Can
CSP alone eliminate all XSS vulnerabilities?
4. **Coding:** Implement a function that safely displays user comments, escaping any HTML tags
and attributes before inserting them into the page.
578
What is cross-site request forgery (CSRF) and how can JS
help mitigate it?
# What Is Cross-Site Request Forgery (CSRF) and How Can JavaScript Help Mitigate It?
**Cross-Site Request Forgery (CSRF)** is an attack that tricks a user's browser into making
unwanted requests to a different site where the user is authenticated. Since browsers automatically
include cookies (and sometimes HTTP authentication headers) with each request, an attacker can
cause the victim's browser to perform actions—like changing account settings or transferring
money—without their knowledge. Unlike XSS, CSRF exploits the trust a site has in the user's browser
rather than the trust the user has in the site.
For instance, suppose you're logged into your bank at `[Link]`. If you visit a malicious
page that contains `<img src="[Link]
your browser will send this request with your session cookie included. If the bank's server lacks
proper CSRF protection, it may process the transaction.
The most common defense is to include a **unique, unpredictable token** in each state-changing
request. The server generates this token and associates it with the user's session. The token is
embedded in the page (e.g. in a hidden form field) and must be submitted with the request. Because
an attacker cannot read the token (due to same-origin policy), they cannot construct a valid request.
When using `fetch()` or AJAX requests, your JavaScript can read the token from a meta tag or cookie
and include it in the `X-CSRF-Token` header:
```html
```
```js
579
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": token,
},
credentials: "include",
body: [Link](data),
});
return [Link]();
```
The server validates the `X-CSRF-Token` against the session. If missing or incorrect, the request is
rejected.
Modern browsers support the `SameSite` cookie attribute which controls whether cookies are sent
on cross-site requests. Setting `SameSite=Lax` or `SameSite=Strict` on session cookies restricts the
circumstances in which browsers include cookies for cross-site requests, mitigating CSRF. For
example:
```http
```
With `SameSite=Lax`, cookies are sent for top-level navigations but not for third-party requests like
hidden images or iframes. `SameSite=Strict` prevents cookies from being sent for any cross-site
request. Combined with `Secure` and `HttpOnly`, this greatly reduces CSRF risk.
580
Another technique is the **double submit cookie** pattern: the server sets a CSRF token cookie
(accessible to JavaScript) and requires the same value in a request header or form field. Since
JavaScript must explicitly read the cookie and include its value, an attacker cannot supply it from a
different site.
If your API requires clients to include a custom header (e.g. `X-Requested-With: XMLHttpRequest` or
`Authorization: Bearer <token>`), browsers will automatically trigger a CORS preflight for cross-site
requests. The preflight uses an `OPTIONS` request that does **not** include cookies. The server can
reject such requests based on the `Origin` header. This technique is often used for JSON APIs where
the client uses `fetch()` with `credentials: 'include'` only on same-origin interactions.
Clickjacking attacks can pair with CSRF to trick users into clicking hidden buttons inside iframes. Use
frame busting (`X-Frame-Options: DENY` or `Content-Security-Policy: frame-ancestors 'none'`) to
prevent your site from being embedded in iframes. Provide clear UI feedback and confirm critical
actions with additional credentials (e.g. reenter password).
## Role of JavaScript
While CSRF protections are primarily implemented on the server, **JavaScript plays a supporting
role**:
- **Including tokens in requests** - Client-side code can read the CSRF token from a meta tag or
cookie and attach it to every `fetch()` or AJAX request. Without this, SPA frameworks may
inadvertently omit the token.
- **Avoiding cross-site requests from untrusted pages** - Do not fetch cross-origin resources with
credentials in contexts you cannot control, such as dynamic scripts or untrusted iframes.
## Practice questions
581
1. **Theory:** Explain how a CSRF attack might work against a user who is logged in to a banking
site. What conditions are necessary for the attack to succeed?
2. **Coding:** Write a helper function that reads a CSRF token from a `meta` tag and includes it in a
JSON POST request using `fetch()`.
3. **Theory:** Describe how the `SameSite` cookie attribute helps mitigate CSRF. Compare
`SameSite=Lax` with `SameSite=Strict` in terms of user experience.
4. **Coding:** Implement a server-side anti-CSRF middleware (in [Link] or similar) that verifies
an incoming `X-CSRF-Token` against the value stored in the session. Ensure that the token cannot be
guessed.
582
Explain sandboxed iframes and the same-origin policy in
browsers
# Sandboxed `<iframe>`s and the Same-Origin Policy in Browsers
Embedding content from other sites has always been a core part of the web. `<iframe>` elements
allow you to display another webpage inside your page, but doing so raises security concerns.
Browsers enforce a **Same-Origin Policy (SOP)** that restricts how documents or scripts loaded
from one origin can interact with resources from another origin. Additionally, `<iframe>`s support a
`sandbox` attribute that can further restrict the capabilities of the embedded content. Understanding
these mechanisms helps you embed third-party content safely.
The same-origin policy is a critical security feature implemented by browsers to prevent malicious
sites from interacting with the sensitive data of other sites. Two URLs share the same origin if they
have the **same protocol**, **host** and **port**. Under the SOP:
- A page loaded from `[Link] cannot read or modify the DOM of a page from
`[Link] It also cannot access cookies, localStorage or IndexedDB from another origin.
- Scripts are allowed to send requests to any domain, but they cannot read responses from
cross-origin requests unless the server explicitly allows it via [Cross-Origin Resource Sharing
(CORS)](../[Link]).
- Forms and image tags are not restricted and can submit cross-origin requests, but they cannot read
responses.
This policy prevents a malicious site from reading a user's email or banking details by embedding or
loading the site and accessing its content via JavaScript.
Sometimes you need to communicate with an iframe loaded from a different origin (e.g. a payment
widget). You cannot call its functions directly due to SOP, but you can use the **`postMessage`
API**. Both the parent and iframe can send messages using `[Link](message,
targetOrigin)`, and listen for `message` events with `[Link]('message', handler)`.
Ensure you verify `[Link]` to make sure messages come from a trusted domain.
583
## Sandboxed `<iframe>`s
The `<iframe>` element includes a boolean `sandbox` attribute that places the embedded document
in a **unique, restricted browsing context**, regardless of its origin. The restrictions remove many
of the iframe's capabilities, and you can selectively re-enable some via **sandbox flags**.
- Forces the iframe to be treated as cross-origin—even if it has the same origin as the parent—
meaning it cannot access the parent's DOM or cookies.
- `allow-same-origin` - treats the document as same origin with respect to SOP, enabling DOM access
if the origin matches.
Example:
```html
<!-- Allows scripts but still treats the iframe as cross-origin -->
584
<!-- Allows scripts and same-origin if the iframe is from the same domain -->
```
The `sandbox` attribute is useful when embedding potentially untrusted content, such as
user-generated HTML or third-party widgets. It prevents the embedded page from manipulating your
page or stealing data, even if it comes from the same domain.
Suppose you integrate a third-party comments widget. You want the widget to run its own scripts but
not interfere with your site. You can embed it with a sandbox:
```html
<iframe
src="[Link]
sandbox="allow-scripts allow-forms"
title="Comments Widget"
></iframe>
```
This allows the widget to execute its scripts and submit forms (e.g. comment submissions) but still
isolates it from your origin. Even if the widget is compromised, it cannot access your DOM or cookies.
If you need to receive events from the widget, use `postMessage()` with an explicit `targetOrigin`
check:
```js
});
585
// Send a message to the iframe when ready
[Link]("init", "[Link]
```
## Summary
Sandboxed iframes and the same-origin policy are powerful tools for isolating untrusted content. The
SOP prevents scripts from one origin accessing resources from another, while the `sandbox` attribute
can restrict even same-origin content to a safe environment. Use them to protect your users and
your site when embedding external content.
## Practice questions
1. **Theory:** Describe the difference between the same-origin policy and the `<iframe sandbox>`
attribute. How do they complement each other?
2. **Coding:** Create a page with an embedded iframe that loads a trusted same-origin page. Use
`sandbox` to disable forms and prevent the iframe from navigating the top window but allow scripts.
Demonstrate sending a message from the iframe to the parent using `postMessage()`.
3. **Theory:** What risks could arise if you include `allow-same-origin` in a sandboxed iframe
pointing to an untrusted third-party domain?
4. **Coding:** Write a helper function that listens for `message` events and verifies the origin before
processing the data. Explain why origin checks are important.
586
What is the Trusted Types API and how does it defend
against XSS?
# What Is the Trusted Types API and How Does It Defend Against XSS?
Browser vendors continually evolve security features to mitigate cross-site scripting (XSS). **Trusted
Types** is a new API designed to eliminate a whole class of DOM-based XSS vulnerabilities by forcing
developers to explicitly create and approve HTML, script URLs and other critical DOM values. When
enabled, the browser rejects assignments of raw strings to dangerous sinks (e.g.
`[Link]` or `eval()`) unless the value is a _Trusted Type_ object created through a
registered policy. This approach ensures that only sanitised or vetted content can reach these sinks.
Developers often mitigate XSS using input validation, output encoding and a Content Security Policy
(CSP) that blocks inline scripts. However, client-side code can still inadvertently create DOM-based
XSS vulnerabilities by concatenating untrusted strings and assigning them to properties like
`innerHTML`, `outerHTML`, `insertAdjacentHTML`, `setAttribute('src', ...)`, or by calling `eval()` on
untrusted input. Attackers can exploit these to inject scripts even if the server sanitises input. As
projects grow and involve multiple contributors or dependencies, it becomes hard to audit every
string assignment.
Trusted Types is activated via CSP by adding the directive `require-trusted-types-for 'script'`. You can
also use `trusted-types <policy-names>` to whitelist policies by name. Without a policy, assignments
to dangerous sinks throw a TypeError.
Example header:
587
```http
```
This tells the browser to require Trusted Types for all script-related sinks and defines a single policy
called `default`.
You define a policy using `[Link](name, handlers)`. The handlers are functions
that take a string and return a Trusted Type. For HTML, a common approach is to sanitise or escape
dangerous markup.
```js
return url;
},
});
[Link] = [Link](userComment);
588
[Link] = [Link](
"[Link]
);
```
The `createHTML` handler sanitises or transforms untrusted user input into a safe HTML string, then
wraps it as a `TrustedHTML` object. If the string contains disallowed content, it may remove or
encode it. Similarly, `createScriptURL` ensures that script sources come only from approved domains.
When Trusted Types is enabled and a script attempts to assign a string to a restricted sink without
converting it into a Trusted Type, the browser throws a TypeError. You can capture such violations
using CSP report endpoints and adjust your code accordingly.
- **Enforced sanitisation** - You cannot accidentally introduce DOM XSS via string concatenation.
All assignments to sensitive sinks must go through your policy, centralising sanitisation logic.
- **Integrates with CSP** - Policies are configured via CSP, so they work in tandem with `script-src`
restrictions and other headers.
- **Library support** - Frameworks and libraries can create their own policies to generate Trusted
Types, making it easy to adopt.
- **Fine-grained control** - You can create multiple policies with different behaviours for different
parts of your application.
## Migration considerations
Because Trusted Types is opt-in and can break legacy code, migrate incrementally:
2. Create a default policy that sanitises or escapes user input. Replace assignments to `innerHTML`,
`outerHTML`, `src` attributes and other sinks with calls to `[Link]()` or similar methods.
589
3. Gradually tighten your policy or split it into multiple policies as needed.
## Practice questions
1. **Theory:** Explain why using `innerHTML` with untrusted data can introduce DOM-based XSS.
How does Trusted Types help prevent this?
2. **Coding:** Write a Trusted Types policy that allows only images hosted on `[Link]` to be
assigned to `[Link]`. Show how to apply it when creating an `<img>` element.
4. **Coding:** Suppose your application uses a rich text editor that outputs HTML. Show how to
sanitise the HTML using your policy before inserting it into the DOM.
590
What is the Cache Storage API and how does it relate to
Service Workers?
# What Is the Cache Storage API and How Does It Relate to Service Workers?
Modern web applications often need to operate reliably under poor network conditions or offline.
The **Cache Storage API** provides a way to store and retrieve network request/response pairs in
named caches. It is most commonly used in combination with **service workers** to intercept
network requests and serve cached resources. Understanding how this API works empowers you to
implement robust caching strategies and improve the performance and resilience of your site.
The Cache Storage API is exposed globally through the `caches` object (in window and workers) and
allows you to create and manage separate caches, each identified by a name. Each cache holds
entries mapping requests to responses. A cache is like a persistent key-value store for HTTP requests
and responses:
```js
await [Link](
"/styles/[Link]",
})
);
// Retrieve an entry
if (response) {
591
[Link](text); // 'body { color: red; }'
// Delete an entry
await [Link]("/styles/[Link]");
[Link]([Link]);
```
The `caches` object itself has methods such as `[Link]()` (list cache names),
`[Link](name)` (remove a cache), and `[Link](request)` (search all caches).
- **Entries are opaque** - Responses are stored exactly as returned from the network. If you store a
response for a `GET` request, the response must be _basic_ or _CORS-allowed_; opaque responses
cannot be read due to cross-origin restrictions but can still be cached.
- **Order matters** - `[Link]()` searches caches in reverse order of creation; the first
matching response is returned. Consider the order when layering caches.
- **Storage limits** - Browsers impose storage quotas for caches (often a percentage of available
storage). Manage your caches by removing outdated entries during service worker activation.
A **service worker** is a background script that intercepts network requests via the `fetch` event.
By listening to `fetch`, you can respond with cached resources or fall back to the network. The Cache
Storage API is the primary mechanism for storing those cached responses.
Here's a basic service worker that caches assets during installation and serves them from the cache
on subsequent visits:
592
```js
// [Link]
[Link](
);
});
[Link](
[Link]([Link]).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
[Link]([Link], [Link]());
return networkResponse;
});
});
})
);
});
593
// Activate event: clean up old caches
[Link](
[Link]().then((cacheNames) => {
return [Link](
[Link]((name) => {
if () {
return [Link](name);
})
);
})
);
});
```
This code caches essential assets on install, serves them from the cache if available on fetch, and
removes old caches during activation. You can implement more sophisticated strategies, such as
**network-first** (try the network, fallback to cache), **stale-while-revalidate** (serve from cache
and update in background), or **cache-first** depending on the resource type.
The browser's built-in HTTP cache stores responses based on headers like `Cache-Control` and `ETag`.
You cannot directly control or inspect it from JavaScript. The Cache Storage API is separate; you
decide what to store and how to respond. When using the service worker cache, you should set
appropriate headers on responses you generate (e.g. `Cache-Control: no-store`) to prevent duplicate
caching in the HTTP cache. You can also use both: rely on the HTTP cache for short-term caching and
the service worker cache for offline fallbacks.
## Practice questions
594
1. **Theory:** Explain the difference between the browser's HTTP cache and the Cache Storage API.
Why would you choose to use the latter in a service worker?
2. **Coding:** Write a service worker handler that implements a network-first strategy for API
requests while caching static assets with a cache-first strategy.
3. **Theory:** What happens if you call `[Link]()` with a response whose body has already been
read? How do you avoid this issue?
4. **Coding:** Implement a function that iterates over all stored caches, deletes those whose names
do not start with a given prefix, and logs the number of deleted entries.
595
How do Progressive Web Apps (PWAs) leverage Service
Workers for offline access?
# How Do Progressive Web Apps (PWAs) Leverage Service Workers for Offline Access?
**Progressive Web Apps (PWAs)** are web applications that behave like native apps: they load fast,
work offline, and can be installed on a user's device. A key technology enabling these capabilities is
the **service worker**, a background script that intercepts network requests and can return cached
responses when the network is unavailable or slow. This article explores how service workers
empower PWAs to provide offline access and improved performance.
A service worker is a JavaScript file that runs separately from the main page and is registered by the
application. Once registered and activated, it can:
Because service workers run outside the page context, they do not have direct access to the DOM,
but they can communicate with the page via the `postMessage` API.
To enable offline access, PWAs use service workers to pre-cache resources and serve them when the
network is unavailable. There are several common caching strategies:
### 1. Cache-first
When an asset is requested, the service worker first looks in the cache. If found, it returns the cached
response. If not, it fetches from the network, caches the response and returns it. This strategy
ensures quick loads for static assets like CSS and JavaScript bundles.
596
```js
[Link](
[Link]([Link]).then((cached) => {
return (
cached ||
fetch([Link]).then((response) => {
[Link]([Link], [Link]());
return response;
});
})
);
})
);
});
```
### 2. Network-first
For dynamic content (e.g. API responses), you may prefer the freshest data. The service worker
fetches from the network first and falls back to the cache if the network fails. Optionally it caches the
response for future offline use.
```js
if ([Link]("/api/")) {
[Link](
fetch([Link])
.then((response) => {
597
return response;
})
);
});
```
### 3. Stale-while-revalidate
This hybrid strategy returns the cached response immediately (stale) while simultaneously fetching
an updated version from the network. When the network response arrives, the cache is updated for
next time. Users get instant responses but still see fresh content on subsequent visits.
```js
[Link](
[Link]([Link]).then((cached) => {
caches
.open("dynamic")
return response;
});
})
);
});
```
598
You can provide an offline HTML page as a fallback when the network is unavailable. During
installation, cache an `[Link]` file. In the fetch handler, if both network and cache miss, return
the offline page:
```js
[Link](
fetch([Link])
.then((response) => {
})
);
});
```
Besides service workers, PWAs use a **Web App Manifest** (`[Link]`) to provide metadata—
name, icons, start URL and theme colors—allowing browsers to install the app on the home screen.
When combined with an active service worker, the browser will prompt users to install the PWA.
Installed PWAs launch in a standalone window and use cached assets even when offline.
## Additional capabilities
- **Background sync** - Service workers can queue failed network requests and retry them later
when connectivity returns. This is useful for sending form data or API calls while offline.
- **Push notifications** - Service workers handle push events to display notifications even when the
site isn't open.
- **Periodic sync and triggers** - Emerging APIs allow service workers to periodically update cached
data or react to triggers (e.g. network status changes).
599
- **Storage limits** - Offline storage quotas vary by browser and may be cleared if the user's device
runs low on space. Use caching judiciously and provide mechanisms to clear old caches.
- **Update lifecycle** - Service workers are versioned. Updating a service worker requires careful
handling of the `install` and `activate` events to avoid serving stale assets. Use `skipWaiting()` and
`[Link]()` appropriately to control when the new worker takes over.
- **Testing offline** - Simulate offline conditions using browser dev tools. Ensure that your PWA
behaves gracefully when network requests fail.
## Practice questions
1. **Theory:** Compare and contrast the cache-first and network-first caching strategies. When
would you choose each?
2. **Coding:** Write a service worker that serves a cached image gallery from the cache if available,
fetches from the network otherwise, and updates the cache in the background.
3. **Theory:** Why is an `[Link]` fallback page important in a PWA? How would you cache and
serve it?
4. **Coding:** Describe how you would implement background sync using the service worker to
ensure a form submission succeeds even when the user goes offline after submitting.
600
What is the difference between deep clone and
structuredClone for complex objects?
# What Is the Difference Between Deep Clone and `structuredClone()` for Complex Objects?
Copying objects in JavaScript seems straightforward, but under the hood it can be tricky. Shallow
copies (e.g. using spread syntax `{...obj}`) duplicate only the top level, leaving nested objects shared
between the original and the copy. **Deep cloning** aims to create a complete, independent copy
of the entire data structure. Historically, developers wrote custom deep copy functions or used
`[Link]([Link](obj))` with all its limitations. The `structuredClone()` method, introduced
in modern browsers and [Link], offers a built-in alternative for duplicating complex objects. This
article compares these approaches and highlights their differences.
Deep cloning is usually implemented by recursively copying properties. The simplest approach
serializes to JSON and parses back:
```js
[Link](3);
```
- **Loss of non-JSON types** - `undefined`, functions, symbols, dates, regexes, `Map`, `Set`, `Error`
objects, and custom prototypes are lost or converted to plain objects. For example, dates become
strings and cannot be converted back automatically.
601
- **Performance** - Large objects are serialized into strings, which can be slow and
memory-intensive.
To overcome these limitations, developers often write or use deep-clone utilities that handle special
cases, but these can be error-prone and heavy.
## `structuredClone()`
The global `structuredClone()` method implements the **structured clone algorithm**, which is the
same algorithm used by `postMessage()` to copy data between windows or workers. It supports a
wide range of built-in types, including:
It does _not_ support functions, DOM nodes, WeakMaps/Sets, or objects with private properties.
The cloned object has the same prototype as the original (except where prototypes are not
cloneable).
Example:
```js
const original = {
regex: /hello/gi,
};
602
const clone = structuredClone(original);
```
Notice that the cloned values retain their constructors and behave like the originals. Circular
references are also handled gracefully:
```js
[Link] = obj;
```
```js
[Link]([Link]); // 0
[Link]([Link]); // 8
```
603
This is useful when moving large buffers between workers to avoid expensive copying.
- **Use `structuredClone()` whenever available** - It handles many built-in types, supports cycles,
and preserves prototypes. It is now supported in modern browsers and [Link].
- **Avoid JSON cloning for complex data** - Only use `[Link]()`/`parse()` when the data
consists of simple, JSON-safe values and performance is not critical.
- **Custom deep cloning when necessary** - If you need to clone functions, classes with custom
prototypes, or special objects not supported by `structuredClone()`, you may need a bespoke
solution.
## Practice questions
1. **Theory:** What limitations does `[Link]()` have when used for deep cloning? Provide at
least three examples of data types that are not preserved.
2. **Coding:** Write a function that uses `structuredClone()` to clone an object containing a `Map`
and a `Set`. Verify that the cloned object's values are of the correct types.
3. **Theory:** Explain what happens when you attempt to structured-clone an object containing a
function property. How would you handle cloning such objects?
604
How do custom iterators work and how can you build
your own iterable object?
# How Do Custom Iterators Work and How Can You Build Your Own Iterable Object?
JavaScript's iteration protocols allow objects to define their own iteration behaviour so that they can
be used in `for...of` loops, spread syntax, destructuring and other iterable contexts. Understanding
how these protocols work enables you to create custom iterable data structures such as ranges,
linked lists or streams.
2. **Iterator** - An iterator is an object with a `next()` method that returns an object of the form `{
value, done }`. Each call to `next()` returns the next value in the sequence and sets `done` to `true`
when iteration is complete.
The `for...of` loop calls the iterable's `[[Link]]()` method to get an iterator and then
repeatedly calls `next()` until `done` is `true`.
Consider creating a simple range object that yields numbers from `start` (inclusive) to `end`
(exclusive) in steps of 1:
```js
// Range constructor
[Link] = start;
[Link] = end;
605
// Define the iterator on the prototype
[Link][[Link]] = function () {
return {
next() {
},
};
};
[Link](n); // 3, 4, 5, 6
```
### Explanation
- Inside `next()`, we keep track of the current value. Each call returns an object with the next value
and `done: false`, then increments the value. When we reach the end, `done` becomes `true` and the
value property can be omitted or set to `undefined`.
This pattern allows `range` to be used with any construct that consumes iterables:
```js
606
[Link]([...range]); // [3, 4, 5, 6]
```
Writing iterators by hand can be verbose. **Generator functions** (`function*`) simplify the process
by managing the internal state for you. If a generator function includes a `yield` expression, it
implicitly implements the iterator protocol:
```js
yield i;
[Link](n); // 3, 4, 5, 6
```
The `yield` keyword suspends the generator, preserving its state. Each call to `next()` resumes
execution until the next `yield` and returns the yielded value. When the function completes, the
iterator indicates `done: true`.
607
When designing custom iterables, consider:
- **State management** - Decide how you will track progress (e.g. an index, a pointer to a node,
etc.). Avoid modifying the iterable object itself if you want to allow multiple simultaneous iterators.
- **Reusability** - Objects with a `[[Link]]` method that returns a _new iterator_ each time
allow multiple independent iterations. If you return the same iterator instance, repeated iterations
will pick up where the previous one left off.
- **Infinite sequences** - You can model potentially infinite sequences (e.g. Fibonacci numbers,
random values) with iterators. Consumers decide when to stop iterating (e.g. using `break` in a loop).
- **Error handling and cleanup** - Iterators can implement `return()` and `throw()` methods that
are called when iteration terminates early or encounters an error. These methods let you release
resources or propagate errors properly.
## Practice questions
1. **Theory:** Explain the roles of `[Link]` and `next()` in enabling iteration over an object.
Why does the iterator return an object with `value` and `done` properties?
2. **Coding:** Implement an iterable that yields only the even numbers within a given inclusive
range. Provide both a hand-written iterator and a generator implementation.
3. **Theory:** Discuss the trade-offs between implementing an iterator manually and using a
generator function. When might you choose one approach over the other?
4. **Coding:** Design a custom iterable that walks a binary tree in in-order traversal. Use either an
explicit stack or a generator to implement the traversal.
608
What are ArrayBuffer and TypedArray, and how are they
different from Arrays?
# What Are `ArrayBuffer` and Typed Arrays and How Are They Different from Arrays?
Working with raw binary data in JavaScript is common in applications such as audio/video
processing, cryptography, file parsing and WebGL. Standard JavaScript arrays are designed for
general-purpose use and store references to any type of value. **`ArrayBuffer`** and **typed
array** views provide a way to handle fixed-length binary data efficiently and interoperably with
native APIs.
An `ArrayBuffer` represents a generic, fixed-length block of memory. It contains bytes of data but has
no knowledge of how to interpret them. You cannot read or write bytes directly on the `ArrayBuffer`;
instead, you use one of the typed array classes or a `DataView` to access the bytes:
```js
[Link]([Link]); // 8
int32View[0] = 42;
int32View[1] = -1;
```
609
In this example, writing to the `Int32Array` view affects the underlying buffer, and the `Uint8Array`
view reveals the same data as bytes. This shared memory model makes typed arrays efficient for
processing binary data.
Typed arrays are array-like objects that view an `ArrayBuffer` through a specific numeric type. There
are multiple typed array classes, each corresponding to a C-style numeric type:
- `Int16Array`, `Uint16Array`
- `Int32Array`, `Uint32Array`
- `Float32Array`, `Float64Array`
When you create a typed array, you specify either an existing `ArrayBuffer` and an optional byte
offset/length, or a length in elements (in which case a new buffer is created). Typed arrays:
- Have a fixed length; you cannot change their size after creation.
- Store numbers in a compact binary representation (1, 2, 4 or 8 bytes per element), offering
performance and memory advantages over regular arrays.
- Provide a subset of array methods (`map`, `forEach`, `reduce`, etc.) but do not support methods
that add or remove elements (`push`, `pop`, `shift`, `unshift`, `splice`).
- Are not real arrays: `[Link](typedArray)` returns `false` and they do not inherit from
`[Link]`.
```js
floats[0] = [Link];
floats[1] = Math.E;
floats[2] = 1 / 3;
610
// Create a subarray that views part of the original buffer
```
Because typed arrays interpret binary data as numbers, they are useful for interacting with Web APIs
that require specific binary formats, such as WebGL textures or audio buffers.
1. **Type enforcement and byte length** - Regular arrays can store any type of value, and each
element is a reference. Typed arrays store only numbers of a specific size and type, which allows for
contiguous memory allocation and faster processing by the JS engine.
2. **Fixed size** - Typed arrays have a fixed length and cannot be resized. Arrays can grow and
shrink.
3. **Methods** - Typed arrays support only a subset of array methods and do not allow structural
changes like `push`/`pop`. Many methods return _new_ typed arrays of the same type, not generic
arrays.
4. **Sharing memory** - Multiple typed array views can point to the same `ArrayBuffer`, enabling
you to interpret the same bytes in different ways. Regular arrays do not share underlying storage.
5. **Use cases** - Typed arrays are ideal for binary data, whereas arrays are general-purpose
collections.
If you need to read or write data types not covered by typed arrays (e.g. 64-bit integers, non-aligned
values, or bitfields), use the `DataView` interface. It provides methods like `getUint32()`,
`getFloat64()`, `setUint8()`, etc., with explicit byte offsets and endianness control.
```js
view.setUint8(0, 0xff);
611
view.setInt16(1, -32768, true); // little-endian
```
## Practice questions
1. **Theory:** Explain why typed arrays cannot change size after creation. How does this property
benefit performance?
2. **Coding:** Create a 16-bit PCM audio buffer of length 44100 (1 second at 44.1 kHz) using
`Int16Array`. Fill it with a sine wave at 440 Hz.
3. **Theory:** Why would you use a `DataView` instead of a typed array? Give an example scenario.
4. **Coding:** Write a function that takes a buffer containing little-endian 32-bit floats and returns
an array of the corresponding JavaScript numbers using `DataView`.
612
What are SharedArrayBuffer and Atomics, and how do
they enable thread safety?
# What Are `SharedArrayBuffer` and `Atomics` and How Do They Enable Thread Safety?
JavaScript was historically single-threaded, meaning that web developers didn't need to think about
data races. With the introduction of **Web Workers**, the language gained multi-threading.
However, without shared memory each worker could only communicate via message passing.
**`SharedArrayBuffer`** and the **`Atomics`** API introduce a new model: multiple threads
(workers and the main thread) can share a common block of memory, and atomic operations ensure
safe concurrent access. This enables new patterns, such as shared memory buffers for
high-performance computing, but also requires careful programming to avoid race conditions.
`SharedArrayBuffer` is similar to `ArrayBuffer` but its contents are shared between workers. When
you post a `SharedArrayBuffer` to another thread (e.g. via `postMessage()`), both threads hold a
reference to the same underlying memory; modifying it in one thread immediately reflects in the
other. Unlike ordinary buffers, transferring a `SharedArrayBuffer` does not detach it.
```js
// Main thread
[Link](shared);
int32[0] = 42;
// [Link]
};
```
613
Because memory is shared, two threads can modify the same index concurrently. Without
coordination, this leads to race conditions (lost updates, inconsistent state). To safely synchronise
reads and writes, you must use the `Atomics` API.
The `Atomics` namespace provides low-level functions that perform **atomic** operations on
shared typed arrays. Atomic operations complete as a single, indivisible step: no other thread can
observe an intermediate state. Additionally, these functions perform **memory fencing**, which
ensures that reads and writes occur in the intended order across threads.
- `[Link](typedArray, index)` - Reads a value from a shared typed array with a memory fence.
- `[Link](typedArray, index, value)` - Atomically replaces a value and returns the old
value.
These operations only work on integer typed arrays (`Int8Array`, `Uint8Array`, `Int16Array`,
`Uint16Array`, `Int32Array`, `Uint32Array`) backed by a `SharedArrayBuffer`. They do not work on
floating-point arrays.
Consider two workers incrementing a shared counter. Without atomic operations, increments might
be lost if both read the old value simultaneously. With `[Link]()`, each increment is
thread-safe:
614
```js
// [Link]
// [Link]
[Link](counter, 0, 1);
postMessage("done");
};
let finished = 0;
[Link]((w) => {
[Link] = () => {
finished++;
};
});
```
Here, `[Link](counter, 0, 1)` ensures each worker's increment is applied atomically and no
updates are lost. Without `Atomics`, you might see a value less than two million.
615
### Blocking with `wait` and `notify`
In addition to arithmetic operations, `[Link]()` allows a thread to block until a specific memory
location changes. This is analogous to condition variables or futexes in other languages. Only web
workers can block; the main thread cannot call `[Link]()` because it would freeze the UI.
```js
// [Link]
[Link](arr, 0, 0);
};
// [Link]
[Link](arr, 0, 123);
[Link](arr, 0, 1);
};
```
Due to vulnerabilities like Spectre, browsers restrict the usage of `SharedArrayBuffer` and Atomics. To
use them, your site must be served over HTTPS and set the response headers:
```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
616
```
These headers enable a _cross-origin isolated_ environment, preventing certain side-channel attacks.
Without them, `SharedArrayBuffer` will be unavailable.
## Practice questions
1. **Theory:** Why can race conditions occur when multiple workers modify the same
`SharedArrayBuffer` without atomics? Provide an example scenario.
3. **Theory:** What are the security requirements for using `SharedArrayBuffer` on the web? Why
are they necessary?
617
How do WeakRefs and FinalizationRegistry help manage
memory?
# How Do `WeakRef` and `FinalizationRegistry` Help Manage Memory?
JavaScript's garbage collector automatically reclaims memory occupied by objects that are no longer
reachable. However, certain patterns—such as caches, memoization and object graphs with cycles—
can lead to unintended memory retention. The ECMAScript **`WeakRef`** and
**`FinalizationRegistry`** APIs provide a way to hold "weak" references to objects so that they don't
prevent garbage collection, and to run cleanup code when objects are reclaimed. These features are
advanced and should be used sparingly, but they can help manage memory in long-running
applications.
A **weak reference** does not prevent an object from being collected. If the object is no longer
strongly reachable elsewhere, the garbage collector may reclaim it and the weak reference becomes
invalid. This is where `WeakRef` and `FinalizationRegistry` come into play.
## `WeakRef`
`WeakRef` wraps an object to create a weak reference. You can call `.deref()` to obtain the original
object if it hasn't been collected. If the object has been collected, `.deref()` returns `undefined`.
```js
class Cache {
constructor() {
get(key) {
618
return ref && [Link]();
set(key, value) {
[Link]("exp", obj);
```
In this cache example, the map holds only weak references. If the original strong reference (`obj`) is
dropped, the value may be reclaimed, freeing memory.
### Caveats
- **Timing is unpredictable** - There is no guarantee _when_ or _if_ the garbage collector will run.
You cannot rely on weak references to release resources at a specific time.
- **Use for caches only** - `WeakRef` is intended for caches or memoization, where you can
recompute values if they disappear. Do not use weak references to manage critical resources (e.g.
files or sockets).
- **Check `deref()`** - Always check the return value of `.deref()` for `undefined` to avoid errors.
## `FinalizationRegistry`
`FinalizationRegistry` lets you register a callback to be called **after** an object has been garbage
collected. This allows you to clean up associated resources (e.g. remove entries from a Map) without
preventing the object from being reclaimed.
619
```js
});
function createResource(id) {
const resource = { id };
return resource;
```
- **No deterministic finalization** - The callback may run long after the object is unreachable, or
not at all before the program ends. Never rely on it for critical logic or resource release that must
happen promptly.
- **Potential memory leak** - If you register an object but forget to unregister it, the registry holds
a reference to the cleanup callback and token, which can itself be a source of leaks.
- **Use with WeakRefs** - Pair `WeakRef` and `FinalizationRegistry` to create caches that
automatically purge entries when objects are collected.
```js
class AutoCache {
constructor() {
620
[Link] = new FinalizationRegistry((key) => {
[Link](key);
});
set(key, value) {
get(key) {
[Link]("item", data);
```
This pattern avoids unbounded cache growth. When `data` is collected, the finalization callback
deletes its entry from the map.
## Practice questions
1. **Theory:** What is the difference between a strong reference and a weak reference? How does
this affect garbage collection?
2. **Coding:** Implement a memoization function that uses `WeakMap` internally. What advantages
does it provide over a normal `Map`?
3. **Theory:** Explain why `FinalizationRegistry` callbacks should not contain critical logic or depend
on timely execution.
621
4. **Coding:** Write a small class that holds objects weakly and automatically cleans up its internal
map when objects are collected. Demonstrate its use with a test object.
622
What are transferable objects and how do they improve
performance in Workers?
# What Are Transferable Objects and How Do They Improve Performance in Workers?
When you use Web Workers or the `postMessage()` API to communicate across threads or windows,
data is **structured-cloned**: the browser creates a deep copy of the object and sends it to the
other context. Copying large amounts of data (e.g. huge arrays, images, files) can be slow and
memory-intensive. **Transferable objects** provide a way to _transfer ownership_ of certain data
types between contexts without copying, enabling efficient high-performance applications.
By default, when you call `[Link](data)`, the `data` is cloned using the structured clone
algorithm. This supports most built-in types (objects, arrays, typed arrays, maps, sets, Blobs, etc.),
but the entire contents are copied. For small objects this is fine, but for large buffers copying can
dominate runtime.
Transferable objects avoid this overhead by _moving_ the underlying memory from one context to
another. After a transfer, the sender's reference becomes **detached**—attempting to access its
contents throws an error or yields zero length—while the receiver gains ownership.
- `MessagePort` objects
- `OffscreenCanvas`
- `ImageBitmap`
- `AudioData`, `VideoFrame` and other media objects (in browsers that support them)
623
To transfer, you pass the object in the second argument of `postMessage()`:
```js
// [Link]
[Link]([1, 2, 3, 4, 5, 6, 7, 8]);
[Link](buffer, [buffer]);
[Link]([Link]); // 0
// [Link]
};
```
Because the memory is moved rather than copied, transferring a large array buffer is nearly
instantaneous and does not duplicate data.
The global `structuredClone()` function also accepts a `transfer` option that works similarly:
```js
624
const clone = structuredClone(buf, { transfer: [buf] });
[Link]([Link]); // 0 (detached)
[Link]([Link]); // 3
```
Transferable objects **move** the data from sender to receiver. After transfer, the sender cannot
access it. In contrast, `SharedArrayBuffer` lets multiple contexts share the same memory
simultaneously. Choose transfer when you need to hand off ownership to a worker and avoid
copying, and choose shared memory when multiple threads need concurrent access with
synchronisation via `Atomics`.
- **Large array processing** - Offload heavy computations (e.g. image processing, cryptography) to
a worker by transferring the underlying `ArrayBuffer` of a typed array. The worker modifies the buffer
and optionally transfers it back.
- **Message ports** - Transfer `MessagePort` objects to set up complex messaging topologies (e.g.
one worker communicates with another via a transferred port).
When using transferables, always ensure that the sender does not expect to read the object after
transfer. Trying to access a detached buffer will result in errors or zero lengths.
## Practice questions
1. **Theory:** Compare the effects of posting an `ArrayBuffer` to a worker with and without listing it
in the transfer array. How does this affect `byteLength` on the sender's side?
2. **Coding:** Write a program that creates a large `Float64Array` on the main thread, transfers it to
a worker that multiplies every element by 2 and then transfers it back. Confirm that the main thread
sees the updated values.
625
4. **Coding:** Demonstrate how to transfer a `MessagePort` from the main thread to a worker and
use it to send messages back and forth.
626
What is structured concurrency (upcoming spec) and
how might it change async patterns?
# What Is Structured Concurrency (Upcoming Spec) and How Might It Change Async Patterns?
Current asynchronous patterns allow you to launch operations and forget about them. For example:
```js
return user;
```
The second fetch continues even though its result is never used. This wastes resources and
complicates error handling—if the posts request fails, where should the error go? Structured
concurrency frameworks in other languages (like Kotlin's coroutines or Swift's tasks) solve this by
627
automatically canceling child tasks when the parent task ends and propagating errors in a controlled
manner.
- **Task groups** - A task group represents a set of asynchronous operations that are tied to a
parent function. You create a task group and then start tasks within it. The group waits for all tasks to
complete before it resolves.
- **Cancellation tokens** - A token signals cancellation to any operation that observes it. When a
parent task is canceled, all child tasks receive the cancellation signal and should abort their work.
- **Automatic propagation** - If a child task throws an error, the error is propagated to the parent
group. Other tasks are canceled automatically, so no work continues silently after a failure.
```js
try {
} catch (err) {
628
throw err;
return [Link]();
```
In this pseudo-API:
- `[Link]()` waits for all tasks to complete. If one task rejects, the group cancels other tasks and
throws the error.
Actual API details may differ, but structured concurrency ensures that all tasks launched within a
scope are either completed or canceled when the scope ends. This avoids orphaned asynchronous
work and centralises error handling.
Structured concurrency would streamline patterns that currently require manual tracking and
cleanup:
- **Error propagation** - When using `[Link]()`, if one promise rejects, others continue
executing. With a task group, the remaining tasks would be canceled.
629
- **Resource management** - You can ensure that all spawned tasks finish before returning from an
async function, reducing leaks. This is especially important for operations like file handles, database
connections or sensors.
- **Readability** - Code reflects the logical structure of tasks: tasks are children of their scope,
rather than floating promises that may outlive their parent.
It's important to note that the structured concurrency proposal is still evolving. Adoption will require
changes to browser and [Link] APIs to accept cancellation tokens, and developers will need to learn
new patterns. But the end result promises more predictable, maintainable asynchronous code.
## Practice questions
1. **Theory:** Describe the problem structured concurrency aims to solve. How do current patterns
like `[Link]()` fall short?
2. **Coding:** Given two asynchronous operations (e.g. fetching user and comments), write a
function using existing tools (e.g. `AbortController`, `[Link]`) that cancels the second request if
the first one fails. How might structured concurrency simplify this?
3. **Theory:** What are the benefits of propagating cancellation and errors from child tasks to their
parent? Can you think of situations where you might _not_ want automatic cancellation?
4. **Coding:** Sketch a custom "task group" class in today's JavaScript that runs multiple async
functions concurrently, cancels all on error, and returns their results. Discuss how your
implementation differs from the proposal.
630
Explain monkey patching, why it’s discouraged, and
alternatives
# Explain Monkey Patching, Why It's Discouraged and Alternatives
Monkey patching stems from dynamic languages allowing you to replace methods on objects or
prototypes. For example, you might patch `[Link]()` to log how many times it's called:
```js
let callCount = 0;
callCount++;
};
[3, 1, 2].sort();
[10, 5].sort();
```
631
Another common example is polyfilling methods missing in older environments by defining them on
prototypes. For instance, adding `[Link]()` in browsers that don't support it.
1. **Unpredictable behaviour** - Modifying built-in objects changes behaviour for _all_ code
running in the same environment. Libraries and frameworks that assume standard semantics may
break if you alter prototypes.
2. **Conflicts** - If multiple modules monkey patch the same method in different ways, they may
conflict. The last one to patch wins, potentially breaking the others.
4. **Global side effects** - Even local patches (e.g. temporarily changing `[Link]`) can leak into
other parts of your app if not restored properly.
5. **Security and stability** - Patching might introduce vulnerabilities or degrade performance if not
done carefully.
- **Polyfills/shims** - Adding missing methods according to the ECMAScript specification for older
environments (e.g. `[Link]`). Polyfills should check for the method's existence
before defining it and follow the spec precisely.
- **Bug fixes in dependencies** - If a library has a bug and you cannot modify its source or wait for
an update, a targeted patch may be required. Document the patch clearly and remove it once the
bug is fixed upstream.
632
```js
function countedSort(arr) {
[Link] = ([Link] || 0) + 1;
return [Link]();
countedSort(numbers);
```
4. **Decorators and higher-order functions** - Wrap functions in higher-order functions that add
behaviour (e.g. logging) without changing the original function or its prototype.
## Practice questions
1. **Theory:** Explain how monkey patching could cause two independently developed libraries to
interfere with each other. Provide a hypothetical example.
2. **Coding:** Write a wrapper function that logs calls to `[Link]()` without modifying
the prototype itself. Use your wrapper on an array and verify that other code using `map()` is
unaffected.
4. **Coding:** Suppose a third-party library uses `[Link]()` internally, and you need to test
time-based behaviour. Show how you can replace `[Link]()` with a fake implementation during a
test and restore it afterward without affecting other tests.
633
What is the Realms API and why might it matter for
sandboxing?
# What Is the Realms API and Why Might It Matter for Sandboxing?
Executing untrusted code safely in JavaScript is challenging. Existing techniques include `<iframe>`
sandboxes, `vm` contexts in [Link], or third-party libraries like SES. These solutions each have
limitations and complexities. The **Realms API** is a proposed ECMAScript feature that aims to
provide a built-in mechanism for creating **isolated execution contexts**—called _realms_—that
allow code to run without affecting or being affected by the surrounding environment. This could
make sandboxing more robust, secure and ergonomic.
## What is a realm?
A **realm** is essentially a separate global environment with its own **global object**, **global
scope** and **intrinsic objects** (`Object`, `Array`, etc.). In browsers today, each top-level window
or `<iframe>` has its own realm. When you evaluate code in an iframe, it gets its own global
environment and prototypes distinct from the parent page. However, using iframes for sandboxing
has drawbacks: they require DOM elements, they may be blocked by Content Security Policy (CSP),
and they load an entire browsing context including document and network access.
The proposed Realms API aims to allow developers to create new realms without a visual browsing
context and to control how values are shared across realms. At the time of writing, the API being
championed in TC39 is `ShadowRealm`:
```js
[Link]("[Link] = 'bar';");
// The new realm's globalThis has foo, but the current realm's does not
[Link]([Link]); // undefined
// To import a function from the current realm into the shadow realm
function add(a, b) {
return a + b;
634
}
[Link]("add(1, 2)"); // 3
```
`ShadowRealm` provides:
- **Code evaluation** via `[Link](code)`, which runs code in the new realm's global scope.
It cannot access DOM APIs or other host-specific objects by default.
Because each realm has its own intrinsics, built-in prototypes cannot be tampered with from the
outside. This isolation prevents prototype pollution (modifying `[Link]`) in one realm from
affecting code in another.
2. **No DOM or network access** - Unlike iframes, a `ShadowRealm` does not automatically
include the DOM or fetch APIs. Unless you explicitly import functions, the sandboxed code has no
capabilities. This reduces the attack surface and makes it easier to audit what is exposed.
3. **Controlled interoperability** - You decide which functions or values to share with the sandbox
via `importValue()`. Passing objects across realms uses structured cloning, so there is no shared
memory by default.
4. **Lightweight** - Creating a new realm does not require constructing a full iframe or Node VM. It
runs in the same process and thread, making it efficient for short-lived computations.
- **Not yet standardized** - As of today, `ShadowRealm` is at Stage 3 in TC39 and subject to change.
Browser and Node support is limited, so experiments must include feature detection.
635
- **Limited host API access** - A shadow realm cannot access the DOM, timers or fetch unless you
provide those functions explicitly. This is by design, but may limit use cases.
- **Security still requires care** - Although realms isolate intrinsics, you must carefully vet what you
import or execute. Code can still run infinite loops or exhaust CPU resources unless you add timeouts
or worker isolation.
## Practice questions
1. **Theory:** Describe how a `ShadowRealm` differs from an `<iframe>` sandbox. What advantages
does it offer for sandboxing untrusted code?
2. **Coding:** Write code that creates a `ShadowRealm`, defines a global variable inside it, and
demonstrates that the variable is not visible in the parent realm. Then, use `importValue()` to call a
function defined in the parent realm from the shadow realm.
3. **Theory:** Why do each realm's intrinsics need to be separate? What attacks can occur if two
realms share the same `[Link]`?
4. **Coding:** Imagine you're building a plugin system where plugins run in their own realms. Show
how you would expose only a specific API to the plugin while keeping the rest of your application's
functions inaccessible.
636
How does the ECMAScript spec define execution order
at the spec level?
# How Does ECMAScript Specify Execution Order at the Spec Level?
In ECMAScript, most operators evaluate their operands **left to right**. The specification expresses
this via abstract operations such as `Evaluate` that return **completion records** (containing result,
normal/abrupt completion and value). For example, the grammar for an addition expression is
described as evaluating the left operand, then the right operand, then applying the `+` operator:
```js
function left() {
[Link]("left");
return 1;
function right() {
[Link]("right");
return 2;
[Link](left() + right());
```
Even though `+` has the same precedence for both operands, the left function runs before the right.
Similarly, for `a() && b() && c()`, evaluation stops as soon as one operand yields falsy. The spec
defines this via short-circuit evaluation for logical operators.
637
### Argument evaluation
Function call argument expressions are evaluated from left to right. Consider:
```js
function f(x, y) {
return x + y;
function a() {
[Link]("a");
return 1;
function b() {
[Link]("b");
return 2;
```
Even if the function uses only one argument, the other argument expression is still evaluated before
the call. This matters when expressions have side effects.
Order also applies to property access and assignments. When evaluating `obj[prop] = value`, the spec
evaluates `obj` first, then `prop`, then `value`. If evaluating `prop` or `value` has side effects (e.g.
calling a getter), those occur in that sequence. Example:
```js
const obj = {
get key() {
[Link]("getter called");
638
return "k";
},
};
function value() {
[Link]("value");
return 42;
obj[[Link]] = value();
```
Beyond expression evaluation, the ECMAScript specification describes the event loop and **job
queues** for asynchronous execution. There are two main task types:
- **Macro tasks (tasks)** - Scheduled by events such as timers, I/O, `setTimeout`, `setInterval`, user
interactions and script execution.
- **Microtasks** - Scheduled by promise resolution and `queueMicrotask()`. The spec mandates that
after executing a task, the runtime must empty the microtask queue before running the next task.
Example:
```js
[Link]("script start");
[Link]("script end");
```
639
The specification ensures that the microtask (the promise callback) runs after the current task (the
script) but before the timer callback. This deterministic ordering enables developers to reason about
asynchronous code.
The spec introduces the concept of **completion records** to track how evaluation proceeds. A
completion record can be _normal_ (returning a value), _throw_ (throwing an exception) or _return_
(exiting from a function). The `try...catch...finally` construct uses completion records to determine
how control flow interacts with cleanup code. For example, `finally` clauses always run regardless of
whether an exception was thrown or a return occurred.
```js
function test() {
try {
return "value";
} finally {
[Link]("finally runs");
```
## Practice questions
1. **Theory:** In the expression `foo() || bar() && baz()`, which functions are called and in what
order? Explain using the specification's short-circuit rules.
2. **Coding:** Write code demonstrating the evaluation order of the operands in `obj[prop] = value`
when `prop` and `value` are functions with side effects. Explain the output order.
3. **Theory:** How do microtasks differ from macro tasks in the ECMAScript event loop? Why does
the specification require clearing the microtask queue before moving on to the next macro task?
4. **Coding:** Show that `finally` clauses run even when a `return` statement is executed in the `try`
block. Provide an example with side effects in the `finally` block and explain what happens.
640
641
What are the pitfalls of floating-point arithmetic (0.1 +
0.2 ≠ 0.3)?
# What Are the Pitfalls of Floating-Point Arithmetic (0.1 + 0.2 + 0.3)?
Many developers have encountered surprising results when performing arithmetic with decimal
fractions in JavaScript:
```js
```
Why doesn't `0.1 + 0.2` equal exactly `0.3`? The answer lies in the binary representation of
floating-point numbers. Understanding these pitfalls is essential when dealing with currencies,
measurements or any calculations that require precision.
JavaScript's `Number` type follows the **IEEE-754 double-precision binary format**, which uses 64
bits: 1 for the sign, 11 for the exponent and 52 for the mantissa. Unlike decimal fractions, many
simple decimal values cannot be represented exactly in binary. For example:
- `0.1` (1/10) does **not** have a finite binary representation. Its binary expansion is infinite:
`0.000110011001100...₂`.
When storing `0.1` as a double, the binary representation is truncated to fit into 52 bits of mantissa.
This introduces a **rounding error**. Adding two numbers with rounding errors compounds the
error, which is why `0.1 + 0.2` is slightly more than 0.3.
642
Errors accumulate with repeated operations:
```js
let sum = 0;
sum += 0.1;
```
Each addition introduces a tiny error; ten times that error produces a noticeable difference.
## Comparison pitfalls
```js
```
`[Link]` is the difference between 1 and the smallest floating-point number greater than
1. Using an epsilon allows you to compare numbers within an acceptable margin of error.
643
- **Large and small numbers** - Representable numbers range from approximately ±1.8×10³⁰⁸
down to ±5×10⁻³²⁴. Numbers beyond this range underflow to `0` or overflow to `Infinity`.
- **NaN propagation** - Operations like `0 / 0` produce `NaN` (Not a Number) and propagate
through calculations.
- **Rounding modes** - JavaScript rounds ties to the nearest even value (banker's rounding) in
some operations.
## Practice questions
1. **Theory:** Explain why `0.1` cannot be represented exactly in binary floating-point format.
Illustrate the binary expansion of 1/10.
2. **Coding:** Write a function that sums an array of decimal numbers while minimising
floating-point errors. One approach is to sort the numbers before adding them.
3. **Theory:** What is `[Link]`, and how can it help when comparing floating-point
numbers? Why might a custom epsilon be required in some situations?
4. **Coding:** Demonstrate how repeated subtraction can lead to floating-point drift. Subtract `0.1`
from `1` ten times, print each intermediate value, and discuss why the final result is not exactly `0`.
644
How can you achieve precise decimal arithmetic in
JavaScript?
# How Can You Achieve Precise Decimal Arithmetic in JavaScript?
One common approach is to **scale** decimal values into integers by multiplying by a power of ten.
Perform arithmetic on the integers and then scale back. For currency, working in cents avoids
fractional cents:
```js
function addMoney(a, b) {
```
This method eliminates rounding errors as long as the scaled values are integers. However, you must
choose a scale factor large enough to capture the maximum number of decimal places you expect.
645
For even larger ranges or more precise fractions, you can use **`BigInt`**. `BigInt` can represent
arbitrarily large integers but cannot represent decimals. By storing amounts as _scaled_ integers, you
can perform exact arithmetic without overflow. For example:
```js
function addScaled(a, b) {
return (a + b) / scale;
```
You can encapsulate this logic in a class that stores amounts as scaled `BigInt` and implements
addition, subtraction, multiplication and division with appropriate rounding modes.
## Decimal libraries
- **[Link]** and **[Link]-light** - Provide a `Decimal` type with configurable precision and
rounding modes. You can perform arithmetic using methods like `plus`, `minus`, `times` and `div`.
Example:
```js
[Link]([Link](y).toString()); // '0.3'
646
```
- **[Link]** - Similar to [Link] but with a smaller footprint. You create `Big` objects and call
methods to perform arithmetic.
These libraries track decimal places internally and avoid binary rounding errors. They can be slower
than native numbers, but for finance or scientific work the correctness often outweighs performance
concerns.
```js
const b = 0.2m;
```
## Practice questions
1. **Theory:** Explain why multiplying by 100 and rounding helps when adding two decimal
numbers like 0.1 and 0.2. What limitations does this technique have?
2. **Coding:** Implement a `Money` class that stores amounts as integer cents using `BigInt`.
Provide methods for addition, subtraction and multiplication by a scalar.
4. **Coding:** Use a decimal library (e.g. [Link] or [Link]) to compute `(0.1 + 0.2) * 0.3` exactly.
Compare the result with the native `Number` computation.
647
648
How do [Link] and [Link]
support localization?
# How Do `[Link]` and `[Link]` Support Localization?
Web applications often need to display dates, times and numbers in a way that matches the user's
locale—formats vary widely across cultures. The **`Intl` API** provides built-in internationalisation
services for these tasks. In particular, **`[Link]`** and **`[Link]`**
allow you to format dates, times and numbers according to locale conventions and user preferences.
## `[Link]`
```js
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
year: "numeric",
month: "long",
649
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
```
Key aspects:
- **Locale:** A BCP 47 language tag (e.g. `'en-US'`, `'de-DE'`) influences language and ordering. You
can specify an array of locales; the browser picks the best match.
- **Options:** Select which date/time components to include (`year`, `month`, `day`, `hour`,
`minute`, `second`) and their styles (`numeric`, `2-digit`, `short`, `long`). Additional options include
`timeZone`, `timeZoneName`, `hourCycle` and `calendar`.
- **Time zones:** If you don't specify `timeZone`, the user's local time zone is used. You can set
`timeZone` to convert UTC times into specific zones.
## `[Link]`
```js
650
// Format number in German (comma as decimal separator)
[Link]([Link](num)); // 1.234.567,89
style: "currency",
currency: "JPY",
});
[Link]([Link](1234)); // ¥1,234
style: "percent",
minimumFractionDigits: 2,
});
[Link]([Link](0.1234)); // 12.34%
```
Important options:
- `notation` - `'standard'`, `'scientific'`, `'engineering'` and `'compact'` (for 1K, 1M, etc.).
- `unit` and `unitDisplay` - format measurements like degrees, litres and metres using
[`[Link]`]([Link]
US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
651
You can also use locale-sensitive formatting for other purposes. For example, to format file sizes in a
human-friendly way using the compact notation:
```js
notation: "compact",
style: "unit",
unit: "byte",
unitDisplay: "narrow",
});
[Link]([Link](1500)); // 1.5 KB
```
## Best practices
1. **Always specify a locale** - Avoid relying on the browser's default locale, as it may vary between
users. You can detect the user's locale via `[Link]` or allow user choice.
2. **Use options appropriately** - Choose the right style and fraction digits based on the data
you're formatting. Avoid manual string concatenation for currencies and percentages; let the API
handle it.
3. **Consider international variations** - Be aware that some locales have unusual conventions
(e.g. using non-Western numerals or right-to-left scripts). Test formatting in your target markets.
## Practice questions
1. **Theory:** Describe how `[Link]` handles time zones and how you can format a
UTC timestamp in another time zone.
2. **Coding:** Write a function that formats a given number into a currency string for the user's
locale. It should accept an ISO currency code and use `[Link]` as the locale.
3. **Theory:** What are the differences between formatting a number in `'scientific'` notation
versus `'compact'` notation using `[Link]`? Give examples.
4. **Coding:** Format the current date and time in Japanese using the Japanese calendar
(`calendar: 'japanese'`) and include the era name and day of the week. Explain each option you use.
652
653
What are pluralRules and segmenter in the Intl API?
# What Are `[Link]` and `[Link]` in the `Intl` API?
The **`Intl` API** in JavaScript provides tools for locale-aware formatting. Two lesser-known but
powerful features are **`[Link]`**, which helps decide plural categories for numbers in
different languages, and **`[Link]`**, which breaks text into meaningful units like
graphemes, words or sentences. These APIs enable applications to handle pluralisation and text
segmentation correctly across languages.
## `[Link]`
Pluralisation varies dramatically across languages. English has two plural categories (singular and
plural), but some languages have one, three or more categories, and the rules can be complex.
`[Link]` determines which plural category a given number belongs to for a specific locale
and plural type.
### Usage
```js
[Link]([Link](1)); // 'one'
[Link]([Link](2)); // 'other'
[Link]([Link](0)); // 'zero'
[Link]([Link](1)); // 'one'
[Link]([Link](2)); // 'two'
[Link]([Link](3)); // 'few'
[Link]([Link](11)); // 'many'
```
654
The `select()` method returns a category string such as `'one'`, `'few'`, `'many'`, `'other'` (categories
vary by locale). Using this, you can choose the appropriate translation for the number:
```js
const messages = {
en: {
},
ru: {
},
};
```
You can also specify `type: 'ordinal'` to handle ordinal numbers (1st, 2nd, 3rd) since the plural
categories differ.
655
You can query `[Link]()` to see which locale and plural categories are actually in use.
This is useful when fallback locales are applied.
## `[Link]`
Different languages use different rules for dividing text into characters, words and sentences. Simply
splitting on spaces or letters is insufficient for scripts with complex grapheme clusters (like emoji or
combining characters), or languages like Thai and Japanese where words are not separated by
spaces. `[Link]` provides locale-aware text segmentation.
### Usage
```js
if (isWordLike) [Link](segment);
656
```
- `isWordLike` - boolean indicating if the segment behaves like a word (for `granularity: 'word'`)
Using `[Link]` ensures that text is split according to the conventions of the specified locale,
which is vital for cursor movement, word counting, text wrapping and search indexing.
- **Localising messages** - Use `[Link]` to select the right plural form for quantities in user
interfaces.
- **Searching and indexing** - Segment text into words or sentences before indexing for search to
improve accuracy across languages.
## Practice questions
1. **Theory:** Why is splitting text on spaces insufficient for languages like Thai or Japanese? How
does `[Link]` address this problem?
2. **Coding:** Implement a function that pluralises a message like "You have {n} new messages" in
both English and Russian using `[Link]`.
3. **Theory:** What differences exist between cardinal and ordinal plural rules? Give examples of
locales where they differ.
657
How does Temporal API improve date–time
management compared to Date?
# How Does the Temporal API Improve Date/Time Management Compared to `Date`?
The built-in `Date` object has been part of JavaScript since the beginning, but it comes with
numerous flaws: it is mutable, combines absolute time with local time zones, lacks support for
non-Gregorian calendars and time zones, and has confusing methods for parsing and formatting. The
**Temporal API** is a modern proposal (at Stage 3 as of this writing) that aims to replace `Date`
with a set of explicit, immutable date and time types. By separating concepts like absolute time, civil
time, and time zones, Temporal avoids many pitfalls of `Date` and makes date-time manipulation
easier and less error-prone.
- **Mutability** - `Date` instances are mutable. Methods like `setFullYear()` and `setHours()` modify
the existing object, which can lead to unintended side effects if a reference is shared.
- **Implicit time zone** - A `Date` stores a timestamp in milliseconds since the Unix epoch
internally, but its getter and setter methods interpret that timestamp in the _system's local time
zone_. Converting between time zones requires manual calculations.
- **Limited calendar support** - `Date` supports only the proleptic Gregorian calendar. There's no
way to use other calendars like the Japanese or Islamic calendars.
- **Parsing and formatting** - `[Link]()` accepts many formats, but they are inconsistent across
implementations. `Date` also lacks built-in formatting; you must use libraries or
`[Link]`.
- **Daylight saving time (DST) traps** - Adding 24 hours to a `Date` around a DST transition may
produce a local time off by an hour due to a 23- or 25-hour day.
## Temporal's approach
- **`[Link]`** - Represents an exact point in time on the timeline (like a `Date`'s internal
representation) but immutable.
- **`[Link]`** - Represents a calendar date (year, month, day) without a time or time
zone.
658
- **`[Link]`** - Combines a `PlainDate` and a `PlainTime` without a time zone.
- **`[Link]`** - Represents spans of time (e.g. 3 days, 2 hours) and supports arithmetic
like addition and subtraction.
Temporal objects are immutable—every method returns a new instance rather than mutating the
original. This prevents accidental changes and makes reasoning about time values easier. Moreover,
Temporal forces you to be explicit about calendars and time zones, avoiding hidden assumptions.
```js
year: 2022,
month: 3,
day: 13,
hour: 1,
minute: 30,
timeZone: "America/New_York",
});
[Link]([Link]()); // 2022-03-13T01:30-05:00[America/New_York]
[Link]([Link]()); // 2022-03-14T01:30-04:00[America/New_York]
```
659
The Temporal API automatically adjusts for DST and time zones when adding or subtracting
durations. With `Date`, you would need to manually account for DST boundaries or use a library like
[Link].
```js
[Link]([Link]()); // 2025-12-31T18:45:00
[Link]([Link]());
```
Formatting Temporal objects is still done via `[Link]` by passing `Temporal` objects
directly (or by converting to `Date` if necessary). Temporal focuses on data representation and
arithmetic; for display, you still use `Intl`.
The Temporal API is available in some environments behind flags or polyfills. Eventually it is expected
to be natively supported in browsers and [Link]. Until then, you can experiment with the
[proposal-polyfill]([Link] Once adopted, Temporal could
simplify date/time handling and reduce reliance on heavy third-party libraries.
660
## Practice questions
1. **Theory:** Why does adding 24 hours to a `Date` object around a DST transition sometimes give
unexpected local times? How does Temporal avoid this issue?
4. **Coding:** Write a function that, given a `[Link]` and a number of business days to
add, returns the resulting date, skipping weekends. How would you implement the same with
`Date`?
661
What are the limitations of [Link] and how to
get cryptographically secure randomness?
# What Are the Limitations of `[Link]()` and How to Get Cryptographically Secure
Randomness?
Generating random numbers is a common requirement—from games and simulations to IDs and
cryptography. JavaScript offers the `[Link]()` function as a simple source of randomness, but
it has limitations that make it unsuitable for security-sensitive contexts. This article explores these
limitations and shows how to obtain cryptographically secure random numbers in both browser and
[Link] environments.
## Limitations of `[Link]()`
2. **No seed control** - The API does not allow you to provide or retrieve the generator's seed. This
makes it unsuitable for repeatable random sequences (e.g. in unit tests) and also prevents you from
reseeding the generator after a compromise.
3. **Floating-point output** - `[Link]()` outputs a float in [0, 1), which you often have to
scale and round to obtain integers or bytes. Converting floats to integers can introduce biases if not
done carefully. For example, `[Link]([Link]() * 10)` does not uniformly distribute
numbers because of floating-point rounding.
4. **Engine differences** - Implementations differ slightly between engines, though ECMAScript
specifies statistical requirements. Still, for cryptography you need stronger guarantees.
The Web Crypto API exposes `[Link]()` to fill a typed array with cryptographically
secure random bytes. It uses the underlying operating system's entropy sources. See
662
[file 145](../[Link]) for
details.
Example:
```js
[Link](array);
[Link](result);
```
The `randomUUID()` method generates RFC 4122 version 4 UUIDs using secure random values. It is
widely supported in modern browsers.
```js
663
[Link]([Link]("hex"));
function randomInt10() {
while (true) {
[Link](randomInt10());
// Generate a UUID v4
[Link](randomUUID());
```
When mapping random bytes to a smaller range, naive modulus operations can introduce bias
because the range of the random generator may not divide evenly. To avoid this, use **rejection
sampling**: discard values that would skew the distribution.
```js
function randomInRange(max) {
const range = 256 % max; // values >= 256 - range would skew the result
let val;
do {
val = randomBytes(1)[0];
664
[Link](randomInRange(10));
```
## Best practices
- **Use CSPRNGs** - For anything security-sensitive (tokens, keys, salts, nonces), always use
`[Link]()` in the browser or `[Link]()` in [Link]. Never fall back to
`[Link]()`.
- **Beware of predictability** - Do not seed your own pseudorandom generator unless you fully
understand cryptography. Rely on the system's entropy.
- **Use `randomUUID()` when available** - It simplifies generating UUIDs and ensures correct
version and variant bits.
- **Be cautious about modulo bias** - Use rejection sampling or specialized functions like
`[Link]()` in [Link] 14.10+ to generate integers uniformly.
## Practice questions
2. **Coding:** Write a browser function that returns a random 6-digit numeric code using
`[Link]()`. Ensure that each digit is uniformly random between 0 and 9.
3. **Theory:** What is modulo bias? Why can mapping a byte to a smaller range using `%` introduce
bias? Describe how rejection sampling avoids this problem.
4. **Coding:** In [Link], implement a function that generates a secure random password of length
12 consisting of uppercase letters, lowercase letters and digits. Use `[Link]()` and
rejection sampling.
665
What triggers a reflow vs a repaint and how to minimize
them?
# What triggers a reflow vs a repaint and how to minimize them
Modern browsers break rendering into multiple phases. Two of the most expensive phases are
**reflow** (sometimes called layout) and **repaint**. Understanding what triggers each phase and
how to avoid unnecessary work is key to writing smooth web applications.
## Repaint vs reflow
- **Repaint** happens when an element's _appearance_ changes in a way that does not affect its
size or position. Examples include changing `color`, `background-color`, `visibility` or `opacity`. The
browser does not need to recalculate layout; it simply redraws the affected pixels.
- **Reflow** (layout) occurs when an element's **geometry** changes or when the browser needs
to recalculate positions. Actions such as adding or removing DOM nodes, changing element
`display`/`position`/`float`, modifying dimensions (`width`, `height`, `padding`, `margin`, `border`) or
resizing the window trigger reflow. Reflow can cascade—changing one element's size may require
recalculating its ancestors and descendants.
Reflow is typically much more expensive than repaint because the browser must recompute the
layout tree and recalculate positions before repainting.
## Common triggers
- Changing CSS properties that affect only the look: `color`, `background-image`, `background-color`,
`text-shadow`, `visibility`, etc.
- Adding or removing classes that change only visual styles (e.g. toggling a dark mode class).
- Changing element attributes such as `class`, `id` or `style` if the resulting styles do not affect layout.
666
- Changing styles that influence layout: `display`, `position`, `top/left/right/bottom`, `margin`,
`padding`, `height`, `width`, `font-size`, `line-height`, etc.
- Changing the content of an element (e.g. adding text) which affects its size.
- Querying layout information after a change. Reading properties like `offsetWidth`, `offsetHeight`,
`scrollTop`, `getComputedStyle()` or `clientWidth` after modifying styles forces the browser to flush
pending changes and perform a synchronous reflow so that it can return up-to-date values.
1. **Batch DOM mutations.** Instead of making multiple DOM changes one by one, group them
together. For example, use a document fragment to build a set of elements and append it once, or
toggle a class that encapsulates multiple style changes instead of changing each property separately.
2. **Avoid layout thrashing.** Layout thrashing occurs when your code alternates between reading
layout properties and writing them. Each read forces a reflow, and each write invalidates the layout.
To avoid this, perform all reads first, then all writes. For complex animations, consider
`requestAnimationFrame()` and CSS transitions.
3. **Use transform and opacity.** CSS `transform` and `opacity` properties can animate without
triggering reflow because they operate on a layer composited by the GPU. For example, use
`transform: translateX()` instead of changing `left`/`top`.
4. **Simplify the DOM structure.** Deeply nested elements can make reflow more expensive
because changes propagate through many ancestors. Keep the DOM shallow where possible.
5. **Debounce or throttle resizing.** Window resize events can trigger continuous reflows. Use a
debounce or throttle function to limit the frequency of layout recalculations.
6. **Use `will-change` sparingly.** The `will-change` property hints to the browser that an element
is likely to change. It can promote the element to its own layer, reducing repaint costs during
animations. Use it only when necessary because it increases memory usage.
```js
667
const items = [Link](".item");
[Link]((item) => {
[Link]([Link]);
});
[Link]((item) => {
});
[Link]((item, i) => {
});
```
In the poor example, each call to `offsetWidth` after a write forces a synchronous reflow. In the
improved version, all reads happen before writes, eliminating unnecessary reflows.
## Practice questions
1. **Theory:** What is the difference between a repaint and a reflow? Provide examples of CSS
properties that trigger each.
2. **Theory:** Why does reading `offsetHeight` immediately after changing `[Link]` cause a
performance hit?
3. **Coding:** Write a function that appends 100 list items to a `<ul>` and minimizes reflow. Explain
why your approach is efficient.
668
172. How does compositing work in modern browsers?
# How does compositing work in modern browsers
Rendering a web page involves multiple stages: style calculation, layout, paint and **compositing**.
While layout determines where elements go and paint draws them into bitmaps, compositing is the
process of assembling these painted pieces into the final on-screen image. Understanding
compositing helps explain why certain CSS properties trigger GPU acceleration and how to optimize
animations.
1. **Style calculation.** The browser converts CSS rules into computed styles for each element.
2. **Layout (reflow).** The browser calculates the geometry of each element—its size and
position—based on the computed styles and the DOM tree.
3. **Paint.** The browser paints each element into a bitmap. Many small paint operations may be
combined into larger ones.
4. **Compositing.** The browser merges these bitmaps (often called **layers**) into the final
image displayed on the screen.
Not all elements are painted into the same layer. Some elements—due to CSS properties like
`position: fixed`, `transform`, `filter`, `opacity`, `clip-path` or `will-change`—are promoted to separate
compositor layers. The reasons for layer promotion include:
- **Isolation:** Elements with effects like 3D transforms or filters cannot be easily merged with
other layers during painting. Placing them in their own layer avoids redrawing their neighbors.
- **Performance:** When an element moves or fades, only its layer needs to be repainted and
composited, not the entire page. GPU compositing can combine layers efficiently.
The compositor runs on a separate thread from the main rendering thread in many modern
browsers. This separation allows smooth animations even when JavaScript on the main thread is
busy, provided the animation uses properties that only affect compositing (e.g., `transform`,
`opacity`).
## Compositing steps
669
1. **Layer creation.** During painting, the browser decides which elements should be on their own
layer. Each layer is essentially a texture.
2. **Blending order.** Layers are stacked based on z-index and stacking contexts. The compositor
orders the layers and determines how they overlap.
3. **Effects and clipping.** The compositor applies CSS effects such as `transform`, `opacity` or
`filter` to each layer. Because these operations are GPU-accelerated, they are very fast compared to
reflow or repaint.
4. **Composition.** Finally, the compositor blends all the layers into a single image using the GPU.
This step is usually synchronized with the display's refresh rate.
```html
<style>
.box {
width: 100px;
height: 100px;
background: crimson;
.move {
transform: translateX(300px);
</style>
<div class="box"></div>
<button id="toggle">Animate</button>
<script>
[Link]("toggle").addEventListener("click", () => {
[Link]("move");
});
</script>
670
```
In this example, clicking the button toggles a class that changes `transform`. Because `transform`
creates a separate compositor layer, the main thread does not need to recalculate layout or repaint
the box for each frame. The GPU composites the box's layer at its new position smoothly, leading to a
fluid animation.
## Best practices
- **Monitor layers.** Browser dev tools (Chrome's "Layers" panel, Firefox's "Paint" options) allow
you to inspect which elements are on their own layers. Use these tools to diagnose performance
issues.
- **Minimize paint area.** Even with GPU compositing, large paint areas can cause jank. Keep
moving elements isolated and avoid overdraw by using `contain: paint` where appropriate.
## Practice questions
1. **Theory:** Describe the difference between painting and compositing. Why does the compositor
run on a separate thread?
2. **Theory:** Name three CSS properties that usually cause an element to be promoted to its own
layer. Why might this be beneficial?
3. **Coding:** Create a card stack where each card lifts above others on hover using `transform:
translateZ()`. Explain how layer promotion prevents jank.
4. **Coding:** Using browser developer tools, inspect a page with multiple animations and identify
which elements are on their own layers. Summarize what you observe.
671
What are layout thrashing and forced synchronous
layouts?
# What are layout thrashing and forced synchronous layouts
Writing performant JavaScript often requires an understanding of how browser layout works. Two
common performance pitfalls are **layout thrashing** and **forced synchronous layouts**. Both
arise when code interleaves reads and writes to the DOM in a way that triggers excessive reflows.
When you modify styles, the browser may defer layout recalculation until the next animation frame.
However, if your code subsequently reads a layout property—such as `offsetWidth`, `offsetHeight`,
`scrollTop`, `clientTop`, `getBoundingClientRect()`, or `getComputedStyle()`—the browser is forced to
flush its queued changes and perform a synchronous reflow to return an up-to-date value. This flush
halts the main thread and can lead to jank, especially in loops.
Example:
```js
[Link] = "200px";
[Link](currentHeight);
```
Here, reading `offsetHeight` forces the browser to synchronously recalculate the layout before
returning the value. Doing this repeatedly inside a loop can degrade performance.
## Layout thrashing
Layout thrashing occurs when code repeatedly alternates between reading layout information and
modifying it. Each read triggers a reflow, and each write invalidates the layout, causing the next read
672
to trigger another reflow. This "ping-pong" effect can result in dozens or hundreds of reflows per
frame.
```js
[Link]((item) => {
});
```
For each item, this loop writes to `[Link]` (invalidating layout) and then immediately reads
`offsetHeight` for the next iteration. On a large list, this can produce many reflows.
1. **Separate reads and writes.** Read all required layout values first, store them in variables, then
perform all writes. This batches reflows and reduces thrashing.
2. **Use `requestAnimationFrame()`.** When animating, schedule DOM updates within the same
animation frame. The browser will perform at most one reflow per frame. Avoid reading layout
properties in between writes during the same frame.
3. **Cache layout values.** If possible, compute values once and reuse them instead of repeatedly
querying the DOM.
4. **Use CSS for complex animations.** CSS transitions and animations keep layout calculations on
the browser side and reduce the need for JavaScript layout reads.
5. **Avoid synchronous API calls.** Some APIs like `getComputedStyle()` can trigger reflow. Use
them sparingly and outside loops.
673
## Example: batching reads and writes
```js
// Read phase
[Link]((item) => {
[Link]([Link]);
});
// Write phase
[Link]((item, i) => {
});
```
By separating the read and write phases, this code triggers only a single reflow rather than one per
item.
## Practice questions
1. **Theory:** Explain what causes a forced synchronous layout and give three methods that can
trigger it.
2. **Theory:** Define layout thrashing and describe why it can harm performance.
```js
[Link]((card) => {
const h = [Link];
[Link] = h + 20 + "px";
});
```
674
4. **Coding:** Use `requestAnimationFrame()` to smoothly animate a progress bar's width without
causing layout thrashing. Explain how you schedule reads and writes.
675
676
What is IntersectionObserver and how can it be used for
lazy loading?
# What is IntersectionObserver and how can it be used for lazy loading
`IntersectionObserver` is a browser API that lets you asynchronously observe changes in the
intersection of a target element with an ancestor element or the viewport. It allows you to run code
when an element enters or leaves the viewport without polling on scroll events. This makes it a
perfect tool for **lazy loading** images, infinite scrolling, or triggering animations when content
becomes visible.
## Key concepts
- **Thresholds:** You can specify an array of intersection ratios (0 to 1) at which to trigger callbacks.
For example, a threshold of `0.1` means the callback is invoked when 10 % of the target's area
becomes visible.
- **Root:** By default, intersections are relative to the viewport. You can pass a different root
element to observe intersections within a scrollable container.
- **Entries:** The callback receives a list of `IntersectionObserverEntry` objects. Each entry provides
properties like `isIntersecting`, `intersectionRatio`, `boundingClientRect` and `target`.
## Basic usage
```js
[Link]((entry) => {
if ([Link]) {
[Link]("Visible:", [Link]);
[Link]([Link]);
677
});
},
);
// Observe elements
```
When any `.observe` element is at least 10 % visible, the callback runs. Using `[Link]()` stops
observing that element.
One popular use case is loading images only when they are about to enter the viewport. This saves
bandwidth and improves page load time.
### HTML
```html
<img
data-src="/images/[Link]"
class="lazy-load"
src="[Link]"
/>
```
Here, `src` is a placeholder image (tiny or blank) and `data-src` contains the real image URL.
678
### JavaScript
```js
[Link]((entry) => {
if ([Link]) {
[Link] = [Link];
[Link](img);
});
},
{ threshold: 0.25 }
);
```
This code observes each `.lazy-load` image. When an image is at least 25 % visible, its actual `src` is
assigned, triggering the download. Once loaded, the observer stops observing it. This technique
drastically reduces the number of images loaded initially.
## Infinite scrolling
IntersectionObserver can also implement endless scrolling. You place a sentinel element at the
bottom of the content. When it becomes visible, you fetch more data and append it to the list.
```js
679
const sentinel = [Link]("#sentinel");
if (entries[0].isIntersecting) {
},
{ rootMargin: "200px" }
);
[Link](sentinel);
```
Setting `rootMargin` to 200 px ensures the next page is fetched slightly before the user reaches the
end, avoiding perceived delays.
## Benefits of IntersectionObserver
- **Efficiency:** It operates asynchronously and is optimized by the browser. Unlike scroll event
listeners, it avoids calling JavaScript on every pixel of scroll.
- **Flexibility:** Can observe multiple elements with varying thresholds and root containers.
- **Non-invasive:** You can easily stop observing or add new elements on the fly.
## Practice questions
2. **Theory:** Explain how lazy loading images using IntersectionObserver improves performance
and user experience.
680
3. **Coding:** Implement lazy loading for background images using `data-bg` attributes and
IntersectionObserver.
4. **Coding:** Create an infinite scrolling list that loads more items when the sentinel becomes 50 %
visible. Ensure each new batch attaches its own sentinel for continued loading.
681
How do custom events improve component
communication?
# How do custom events improve component communication
In modern web development, applications are often built from reusable components. Components
should ideally be self-contained and loosely coupled. **Custom events** provide a way for
components to communicate without tight coupling, leading to cleaner architectures.
The browser dispatches many built-in events such as `click`, `input`, `scroll` and `submit`. Sometimes
you need to signal something that doesn't fit any built-in event, such as "todo item completed" or
"user logged in". That's where custom events come in. You can create and dispatch your own events
using the `CustomEvent` constructor:
```js
});
[Link](event);
```
The `detail` property carries any data you want to pass along. The `bubbles` option specifies whether
the event should bubble up through ancestor elements, and `composed` allows crossing shadow
DOM boundaries so that events can propagate out of Web Components.
## Decoupling components
Consider a to-do app built from separate components: a `TodoItem` and a `TodoList`. Instead of the
parent directly calling a method on the child or vice versa, the child can dispatch an event when
something interesting happens. The parent listens and responds.
682
```html
<ul id="todo-list"></ul>
<script type="module">
connectedCallback() {
if ([Link]) {
[Link](
new CustomEvent("complete", {
bubbles: true,
})
);
});
[Link]("todo-item", TodoItem);
[Link]("done");
});
// Add items
683
</script>
```
Here, each `<todo-item>` dispatches a `complete` event when its checkbox is checked. The `todo-list`
listens for the event and reacts. The child doesn't need to know about the parent, and the parent
doesn't need a reference to each child. This loose coupling improves maintainability and testability.
Because custom events can bubble, you can attach a listener on a high-level container and respond
to events from many descendants. This is known as **event delegation**. It reduces the number of
listeners and makes it easy to handle dynamically added elements.
If you dispatch an event from within a Shadow DOM and want it to cross the boundary, set
`composed: true`. Without this, the event stops at the shadow root.
- **Loose coupling:** Components communicate via events instead of direct method calls.
- **Scalability:** Event delegation scales to many child components with minimal listeners.
- **Reusability:** A component can be used in different contexts. It simply emits events; consumers
decide how to handle them.
- **Lifecycle awareness:** Events can signal component lifecycle milestones (mounted, destroyed,
updated) for instrumentation or cleanup.
## Practice questions
1. **Theory:** How does setting the `bubbles` and `composed` options affect a custom event's
propagation? When would you set them to `false`?
2. **Theory:** Explain why custom events promote loose coupling in component architecture.
4. **Coding:** Build a modal component that dispatches a `close` custom event when the user clicks
outside or presses the escape key. Use event bubbling so the parent can remove the modal.
684
685
What is the difference between innerHTML,
outerHTML, and textContent?
# What's the difference between `innerHTML`, `outerHTML` and `textContent`
JavaScript provides several properties for reading and modifying the content of DOM elements:
`innerHTML`, `outerHTML` and `textContent`. Although they seem similar, each serves a different
purpose and has different performance and security implications.
## `innerHTML`
`innerHTML` returns or sets the **HTML markup inside** an element. When you read it, you get a
string containing the serialized contents of the element. When you assign to it, the browser parses
the string as HTML and replaces the element's children.
```js
```
Because `innerHTML` parses HTML, it can be expensive on large fragments. It also poses a security
risk: setting `innerHTML` from untrusted data can introduce cross-site scripting (XSS) vulnerabilities.
Always sanitize untrusted input before assigning to `innerHTML`.
## `outerHTML`
`outerHTML` returns or sets the **HTML markup of the element itself and its contents**. Reading it
serializes the entire element, including its opening and closing tags. Assigning to `outerHTML` parses
the string and replaces the element itself with new content.
```js
[Link]([Link]); // "<h1>Title</h1>"
686
[Link] = "<h2>New Title</h2>"; // replaces <h1> entirely
```
Use `outerHTML` when you need to replace an element altogether. Like `innerHTML`, it should not
be used with untrusted content.
## `textContent`
`textContent` returns or sets the **raw text** contained within an element and its descendants. It
strips any HTML tags. Assigning to it escapes any HTML characters, inserting text exactly as provided.
```js
```
Since `textContent` does not parse HTML, it is much faster than `innerHTML` and safe to use with
untrusted input. It is the preferred way to set plain text.
Because `innerHTML` and `outerHTML` involve parsing and serializing HTML, they are slower than
`textContent`. `innerHTML` may trigger a reflow if the new markup affects layout. `textContent`
simply updates text nodes and is more efficient.
## Practice questions
1. **Theory:** Explain why assigning user input directly to `innerHTML` can be dangerous. How can
you mitigate this risk?
687
2. **Theory:** What happens when you set `textContent = "<strong>hi</strong>"`? Why is this
useful?
688
What is the difference between CORS preflight and
simple requests?
# What's the difference between CORS preflight and simple requests
**Cross-Origin Resource Sharing (CORS)** allows a web application on one origin to request
resources from a different origin. Because cross-origin requests can pose security risks, the browser
follows strict rules before sending them. Requests fall into two categories: **simple requests** and
**preflighted requests**.
## Simple requests
2. **Headers** are only simple request headers (e.g. `Accept`, `Accept-Language`, `Content-
Language`, `Content-Type` with a value of `text/plain`, `application/x-www-form-urlencoded` or
`multipart/form-data`).
3. **No custom headers**: It does not include authorization headers like `Authorization` or `X-
Custom-Header`.
4. **No `content-type` other than the three permitted values** for POST.
For simple requests, the browser directly sends the HTTP request with the requested method and
headers. If the server's response includes appropriate CORS headers (e.g. `Access-Control-Allow-
Origin`), the browser makes the response available to the JavaScript code. There is **no extra
network round trip** before the actual request.
## Preflighted requests
If a request uses methods other than `GET`, `HEAD` or `POST`, or it includes custom headers or uses a
disallowed `Content-Type`, the browser must perform a **preflight**. A preflight is an HTTP
`OPTIONS` request sent to the target server with the following headers:
689
- `Origin`: The origin of the calling page.
The server must respond to the preflight with `Access-Control-Allow-Methods` and, if necessary,
`Access-Control-Allow-Headers` and `Access-Control-Allow-Origin`. Only if the server allows the
requested method and headers will the browser proceed to send the actual request. If the preflight
fails (e.g. the method is not allowed), the browser aborts the request and your JavaScript code
receives an error.
Preflights protect users and servers by requiring explicit permission before potentially dangerous or
unexpected requests. For example, a malicious site might try to send a `PUT` request with a custom
`Authorization` header to an API. Without preflight, the browser would allow the request, potentially
leaking sensitive information. With preflight, the API must explicitly opt in, preventing unauthorized
access.
- Stick to simple methods (`GET` and `POST`) with permitted content types.
- Avoid custom headers unless absolutely necessary. Use standard headers or encode data in the URL
or request body.
- Configure the server to respond with the right CORS headers. If you control the server, you can
allow specific methods and headers so preflight responses succeed.
## Example
```js
fetch("[Link] {
method: "PUT",
headers: {
690
"Content-Type": "application/json",
"X-Auth-Token": "abc123",
},
});
```
Because the method is `PUT` and includes a custom header, the browser will:
## Practice questions
1. **Theory:** What are the conditions for a request to be considered simple? Why do these
requests skip the preflight?
3. **Coding:** Write a fetch request that triggers a CORS preflight and describe the sequence of
network requests.
4. **Coding:** Modify the request to avoid a preflight if possible. Explain what changes you made.
691
How does sandbox attribute in iframes affect script
execution?
# How does the `sandbox` attribute in iframes affect script execution
The `<iframe>` element allows you to embed another page inside your site. Without restrictions, the
embedded page can run scripts, submit forms or navigate the parent. The `sandbox` attribute
provides a way to constrain what an embedded page can do. By default, applying `sandbox` creates a
_locked-down_ environment; you then selectively enable capabilities via the `allow-*` tokens.
When you add the `sandbox` attribute with no value (or with an empty string), the iframe loses many
privileges:
- **No script execution:** JavaScript and other scripts are disabled. The page cannot run inline
scripts or load external scripts.
- **No popups or new windows:** The page cannot open new windows via `[Link]()`.
- **No same-origin privileges:** Even if the iframe's content is from the same origin, it is treated as
if it were cross-origin. The parent cannot access the iframe's DOM via `contentWindow` and vice
versa.
- **No modal dialogs:** The page cannot call `alert`, `confirm` or `prompt`.
These restrictions make sandboxed iframes ideal for isolating untrusted content.
You can gradually restore specific capabilities by adding allow tokens in the `sandbox` attribute. Some
common tokens:
- `allow-scripts`: Allows executing JavaScript. If you include this token but not `allow-same-origin`, the
script runs in a unique origin and cannot access cookies or localStorage.
692
- `allow-same-origin`: Treats the iframe as same origin. Use with caution—if you also allow scripts,
the iframe could break out of confinement.
Example:
```html
<iframe
src="[Link]
sandbox="allow-scripts allow-forms"
></iframe>
```
Here, the iframe can run scripts and submit forms but cannot navigate its top-level frame or access
cookies because `allow-same-origin` is absent.
Even if you allow scripts via `sandbox`, CSP rules on the parent or the embedded document still
apply. For example, if the embedded page's own CSP forbids inline scripts, they will still be blocked.
Likewise, if the parent page uses a CSP with `frame-ancestors` directive, it can prevent other sites
from embedding it at all.
Sandboxing isolates the iframe from the parent DOM but is separate from the Shadow DOM concept.
The `sandbox` attribute controls privileges at the browsing context level, whereas Shadow DOM
encapsulates styles and markup within a component. You can nest sandboxed iframes inside Shadow
DOM, but they remain independent mechanisms.
693
## Practice questions
1. **Theory:** What happens when you add `sandbox` with no tokens to an iframe? List at least
three restrictions it imposes.
3. **Coding:** Create an iframe that embeds a third-party comments widget. Allow scripts to run
but prevent access to cookies and localStorage. Explain which tokens you use and why.
4. **Coding:** Demonstrate how a parent page can use postMessage to communicate with a
sandboxed iframe. Highlight security considerations.
694
What are cross-origin resource policies (CORP, COEP,
COOP) and why do they matter?
# What are cross-origin resource policies (CORP, COEP, COOP) and why do they matter
Web security has evolved to protect against cross-origin data leaks and to enable powerful features
like SharedArrayBuffer. Three complementary HTTP headers—**Cross-Origin Resource Policy
(CORP)**, **Cross-Origin Embedder Policy (COEP)** and **Cross-Origin Opener Policy (COOP)**—
allow sites to opt in to stricter isolation. Together, COOP and COEP provide **cross-origin
isolation**, which unlocks high-resolution timers and shared memory in browsers.
- `same-origin`: Only the same origin can load the resource. Requests from other origins will be
blocked.
- `same-site`: Only the same site (including subdomains) can load the resource.
- `cross-origin`: Any origin may load the resource (default). Use this for public assets.
CORP helps prevent _side-channel attacks_ where a malicious page attempts to fetch a resource and
infer its content based on response timing or error messages. For example, an image served with
`same-origin` cannot be embedded by another origin.
`Cross-Origin-Embedder-Policy` controls which cross-origin resources (scripts, images, etc.) your page
is allowed to load. It has two primary values:
- `require-corp`: Your page may only load cross-origin resources that either explicitly allow being
embedded with `Cross-Origin-Resource-Policy: cross-origin` or come from the same origin. Resources
without CORP or CORS headers will be blocked.
695
COEP effectively protects you from accidentally including third-party resources that might leak data,
and is one half of achieving cross-origin isolation.
`Cross-Origin-Opener-Policy` defines the relationship between your page and any opened windows
or tabs. It also determines whether your page shares the same browsing context group with pages
from other origins. Important values:
- `unsafe-none`: Default; your page shares a browsing context group with any opener, meaning
`[Link]` is available and resources like `[Link]` can be leaked via side channels.
- `same-origin`: The opener of your window must be the same origin; otherwise the browsing
contexts are isolated. `[Link]` becomes `null` for cross-origin openers.
By isolating browsing contexts, COOP prevents cross-origin pages from using `[Link]` to
manipulate or peek into each other's state.
## Cross-origin isolation
```http
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
With these headers in place, the browser ensures that the page does not share its memory or
performance timing information with any non-isolated cross-origin contexts. In return, you can safely
use SharedArrayBuffer, Atomics and high-precision timers.
696
- **Security:** They mitigate side-channel attacks and cross-site leaks by limiting who can load your
resources and preventing untrusted pages from interacting with yours.
- **Future web APIs:** Many upcoming APIs (like WebAssembly threads) rely on cross-origin
isolation. Using CORP, COEP and COOP prepares your site for these features.
## Practice questions
2. **Theory:** What must be set on a document to achieve cross-origin isolation and why is it
required for SharedArrayBuffer?
4. **Coding:** Use the `fetch()` API to load a resource from a server that does not send CORP or
CORS headers. Then update the server to include `Cross-Origin-Resource-Policy: cross-origin` and
observe the difference when COEP is enabled on your page.
697
What is a content-type sniffing attack and how can JS
prevent it?
# What is a content-type sniffing attack and how can JS prevent it
Browsers need to know how to interpret a resource—whether it is a script, an image, a PDF or plain
text. They generally trust the `Content-Type` header sent by the server. However, to be user-friendly,
browsers sometimes perform **content sniffing**: they attempt to infer the type of a resource if
the `Content-Type` is ambiguous or missing. While helpful in some cases, this behavior opens the
door to **content-type sniffing attacks**.
## The attack
A content-type sniffing attack occurs when a browser interprets a resource as executable (e.g. HTML
or JavaScript) even though the server intended to serve it as something else (e.g. an image or text
file). A malicious actor can exploit this by storing an HTML/JS payload on a server that expects only
images. If the browser guesses incorrectly and executes the content, it may run attacker-supplied
scripts in the context of your site.
Consider a file upload endpoint that stores user uploads in a public `/uploads` folder and serves
them with a generic `Content-Type: application/octet-stream`. An attacker uploads a file containing
`<script>alert(1)</script>`. When a victim views the uploaded file, the browser might sniff it as HTML
and execute the script, leading to cross-site scripting (XSS).
Always set accurate `Content-Type` headers for responses. For user-uploaded files, determine the
MIME type server-side and send it accordingly. Avoid serving untrusted content with ambiguous
types.
This HTTP header tells the browser **not** to perform content sniffing. When set, the browser must
respect the provided `Content-Type`. If the type doesn't match the expected resource, the browser
will block the resource instead of guessing and running it.
698
For example:
```http
X-Content-Type-Options: nosniff
Content-Type: image/png
```
If a script is served as `image/png` with `nosniff`, the browser will refuse to execute it.
On the server, validate uploaded files: check their content matches the claimed MIME type, restrict
allowed types, and sanitize filenames. Store uploads outside of publicly accessible paths or serve
them from a different domain to isolate them from your main site.
When fetching data with `fetch()` or XHR, examine the `Content-Type` header and avoid using
potentially executable responses in dangerous ways. For example, do not insert untrusted text into
`innerHTML`. Use `textContent` for plain text and parse JSON with `[Link]()`.
```js
if () {
699
}
[Link] = imgUrl;
[Link](img);
```
This function ensures the server returns an image before creating an `<img>` element. It avoids
content sniffing by verifying the header.
## Practice questions
1. **Theory:** Why do browsers perform content sniffing? What risks does it introduce?
2. **Theory:** How does the `X-Content-Type-Options: nosniff` header help mitigate content-type
sniffing attacks? What happens if the header is missing?
3. **Coding:** Write server code (in Express or another framework) that serves files with proper
`Content-Type` headers and includes `X-Content-Type-Options: nosniff`.
4. **Coding:** Implement a fetch wrapper that verifies the MIME type before creating DOM
elements. How would you extend it to handle JSON, images and text safely?
700
What is the Observer pattern and how is it
implemented in JavaScript?
# What is the observer pattern and how is it implemented in JavaScript
The **observer pattern** is a design pattern in which an object (called the _subject_ or
_observable_) maintains a list of dependent objects (called _observers_) and notifies them
automatically when its state changes. This allows the subject to publish state changes without
knowing anything about the observers' internal details, promoting a loose coupling.
## Key components
- **Subject (observable):** Holds the state being observed and a collection of observers. Provides
methods to subscribe or unsubscribe observers and a method to notify them of changes.
- **Observers:** Objects that want to be notified when the subject changes. They implement an
`update()` or similar method called by the subject.
- **Decoupling:** The subject does not need to know how many observers there are or what they
do; it simply notifies them. Observers can be added or removed at runtime.
- **Maintainability:** The pattern centralizes state changes and reduces the number of direct
method calls between components.
Below is an implementation of a subject that manages a list of observers. Observers are objects that
define an `update()` method.
```js
class Observable {
constructor(value) {
this._value = value;
701
this._observers = new Set();
get value() {
return this._value;
set value(newValue) {
this._value = newValue;
[Link](newValue);
subscribe(observer) {
this._observers.add(observer);
[Link](this._value);
notify(val) {
// Usage
const logger = {
update(value) {
},
};
702
const unsubscribe = [Link](logger);
unsubscribe();
```
The `Observable` class encapsulates a value and notifies subscribed observers whenever it changes.
Observers can unsubscribe by calling the returned function.
JavaScript environments like [Link] provide an `EventEmitter` which implements a similar pattern.
You can subscribe to named events and emit them when appropriate:
```js
[Link]("Received:", payload);
});
```
Here, `emitter` acts as a subject and handlers act as observers. The principle remains the same:
decoupled notification of state changes or events.
- Building reactive UI components that should update when underlying data changes.
703
- Coordinating asynchronous processes (e.g. network events, WebSocket messages).
## Practice questions
1. **Theory:** Describe the roles of the subject and observer in the observer pattern. How does the
pattern promote loose coupling?
2. **Theory:** What are some real-world examples of the observer pattern in browser APIs or
JavaScript libraries?
3. **Coding:** Extend the `Observable` class above to support multiple values (e.g. an observable
object with multiple keys). Write a test observer that listens to changes on a specific key.
4. **Coding:** Use [Link] `EventEmitter` to implement a chat room where users can subscribe to
receive messages. Explain how this illustrates the observer pattern.
704
What is the Publish–Subscribe pattern and how does it
differ from Observer?
# What is the publish-subscribe pattern and how does it differ from observer
1. **Subscribers** register interest in one or more topics. They provide a callback to be executed
when a message on that topic is published.
2. **Publishers** emit messages to the broker, tagging each message with a topic.
3. The **broker** looks up subscribers for that topic and invokes their callbacks with the message
payload. Subscribers can subscribe or unsubscribe at runtime.
```js
class PubSub {
constructor() {
subscribe(topic, handler) {
if () {
[Link](topic).add(handler);
publish(topic, data) {
705
const handlers = [Link](topic);
if (!handlers) return;
});
unsubscribe();
```
In this implementation, subscribers listen for the `'news'` topic. When a publisher publishes a
message with that topic, the message is delivered to all subscribed handlers.
When you call `[Link]('news', handler)`, you are telling the message bus: "Please call my
`handler` function whenever someone publishes a `'news'` message." Internally, the `subscribe()`
method adds your handler to a list of listeners for that topic. To give you an easy way to stop listening
later, `subscribe()` returns another function.
Think of it like signing up for a newsletter: you give the publisher your email address, and they start
sending you updates. Along with your subscription, they include a special "unsubscribe" link. If you
click that link, they remove your email address from their mailing list so you stop receiving messages.
```js
706
});
```
The `unsubscribe` variable now holds a function. Whenever you no longer want to receive `'news'`
messages, you call it:
```js
```
After calling `unsubscribe()`, any future calls to `[Link]('news', ...)` won't trigger your handler.
Saving the returned function in a variable like `unsubscribe` is just a convenient way to keep a
reference to it so you can clean up your listener later.
Although the pub-sub and observer patterns both involve one-to-many communication, there are
key differences:
- **Direct vs. mediated:** In the observer pattern, observers subscribe directly to a specific subject
and the subject notifies them. In pub-sub, publishers and subscribers are decoupled via a broker. The
subject (topic) does not directly know its subscribers.
- **Topics/queues:** Pub-sub uses a named channel (topic) or queue. Observers typically register
with a specific object, not a global topic.
- **Unknown publishers:** Observers know which subject they observe; subscribers to a pub-sub
system may not know who publishes messages.
- **Scalability:** Pub-sub scales to distributed systems and message queues (e.g. RabbitMQ, Kafka)
where publishers and subscribers may run on different machines. Observer is often used within a
single process or object graph.
- Decoupling modules in a large application. For example, a logging service can subscribe to `'error'`
events without the application knowing about it.
707
- Distributing events across processes or servers using message brokers or WebSockets.
## Practice questions
1. **Theory:** Explain how the publish-subscribe pattern decouples publishers and subscribers.
What are the benefits of this decoupling?
2. **Theory:** List two differences between the pub-sub and observer patterns.
3. **Coding:** Implement a basic pub-sub system that supports wildcard subscriptions (e.g.
subscribing to `'user.*'` to receive both `'[Link]'` and `'[Link]'`).
4. **Coding:** Use the pub-sub pattern to implement a notification system where different modules
can publish success or error notifications. Demonstrate subscribing and unsubscribing to a topic.
708
Explain functional composition in JavaScript
# Explain functional composition in JavaScript
Functional composition is the process of combining multiple functions so that the output of one
becomes the input of the next. Rather than calling functions one after the other manually,
composition creates a new function that represents the pipeline of operations. This leads to code
that is declarative, reusable and easy to test.
## What is composition
Given two functions `f` and `g`, the composition `f ∘ g` is a function such that `(f ∘ g)(x) = f(g(x))`. In
JavaScript, you can compose functions manually:
```js
[Link](shout("hello")); // "HELLO!"
```
The `shout` function composes `toUpper` and `exclaim`. Composition becomes more powerful when
you compose many functions and reuse the resulting pipeline.
We can write a higher-order function that takes an arbitrary number of functions and returns their
composition. A common implementation composes functions from right to left:
```js
function compose(...fns) {
709
return [Link]((value, fn) => fn(value), initial);
};
```
Here, `compose(addPeriod, lower, trim)` returns a function that first applies `trim`, then `lower`, then
`addPeriod` to the input. You can swap the order of functions or replace one function without
affecting others.
Alternatively, you can implement `pipe()` which composes functions left to right:
```js
const pipe =
(...fns) =>
(initial) =>
```
## Advantages of composition
- **Reusability:** You write small, single-purpose functions and compose them into more complex
operations.
710
- **Testability:** Each function can be tested in isolation. The composed function has no hidden
state.
- **Readability:** A composed pipeline reads like a data flow: the value passes through a sequence
of transforms.
- **Immutability:** Composed functions are pure if the individual functions are pure; they don't
mutate shared state.
## Composition in libraries
Libraries like Lodash and Ramda provide helpers (`_.flow`, `[Link]`, `[Link]`) to make
composition easy. React's hooks and higher-order components also encourage composing
functionality.
## Practice questions
1. **Theory:** In your own words, explain what function composition is and why it is useful.
4. **Coding:** Use function composition to build a data processing pipeline that normalizes an array
of names (trim, capitalize first letter, append an index). Show how to swap out one step without
modifying others.
711
What are Higher-Order Components (HOC) and render-
props patterns conceptually?
# What are higher-order components (HOC) and render props patterns conceptually
React encourages code reuse by allowing you to encapsulate behavior and share it between
components. Two common patterns for sharing logic are **higher-order components** (HOCs) and
**render props**. Both allow you to abstract stateful logic away from the components that use it.
A higher-order component is a function that takes a component and returns a new component with
enhanced functionality. HOCs wrap the original component, injecting additional props or
behavior. They are similar to higher-order functions in functional programming.
Example: a HOC to inject the current window width into any component.
```jsx
function withWindowWidth(WrappedComponent) {
componentDidMount() {
[Link]('resize', [Link]);
componentWillUnmount() {
[Link]('resize', [Link]);
render() {
};
712
// Usage
```
`withWindowWidth` adds resize listeners and passes the width as a prop. Any component can
become responsive by being wrapped with this HOC. HOCs are commonly used for cross-cutting
concerns such as authentication, routing or theming.
## Render props
The render props pattern involves a component that takes a function as its `children` or `render`
prop. Instead of returning JSX directly, the component calls this function with relevant data or
callbacks. The caller decides how to render the UI.
Example: a `<MouseTracker>` component that tracks mouse position and uses a render prop to
display it.
```jsx
state = { x: 0, y: 0 };
handleMouseMove = e => {
};
render() {
return (
{[Link]([Link])}
</div>
);
713
const App = () => (
)} />
);
```
`MouseTracker` manages state internally but leaves presentation up to the caller. Render props are
flexible and work well for composing multiple behaviors. They avoid component names that wrap
the original component and are easier to type-check compared to HOCs.
* **Composition style:** HOCs wrap components and return new components, while render props
involve a component that invokes a function passed as a prop.
* **Name collisions:** HOCs can cause prop name collisions if not careful. Render props avoid this
by explicitly passing arguments to the render function.
* **Ease of testing:** Both are testable; HOCs can be more opaque if many wrappers are stacked.
Render props sometimes result in deeply nested JSX.
* **Hooks era:** Since React 16.8, custom hooks have become the primary way to share logic.
Hooks can replace many HOC or render prop patterns with simpler syntax.
## Practice questions
1. **Theory:** Explain the main difference between a higher-order component and a render prop.
When would you choose one over the other?
2. **Theory:** What problems might occur if you wrap a component with multiple HOCs? How can
you avoid them?
3. **Coding:** Write a HOC that injects network status (`online`/`offline`) into a component using
the `[Link]` API and `online`/`offline` events.
4. **Coding:** Implement a render prop component `Toggle` that manages a boolean `on` state and
provides a function to toggle it. Show how to use it to build a custom switch UI.
714
715
What is dependency injection and can it be achieved in
JavaScript?
# What is dependency injection and can it be achieved in JavaScript
**Dependency injection (DI)** is a design pattern in which an object's dependencies are provided
from the outside rather than being created internally. Instead of hard-coding the creation of services
or collaborators, you inject them through constructors, functions or setters. This approach promotes
loose coupling, easier testing and flexibility.
- **Decoupling:** Components do not need to know how to instantiate their collaborators. They can
work with any implementation of a given interface.
- **Testability:** During unit tests, you can inject mocks or stubs instead of real services.
- **Flexibility:** You can switch implementations (e.g. swap a local storage service for an in-memory
cache) without modifying the consumer.
## DI in JavaScript
JavaScript does not have built-in dependency injection containers like some server-side frameworks,
but the pattern can still be applied through techniques such as:
```js
class UserService {
constructor(api) {
[Link] = api;
async getUser(id) {
716
}
class ApiClient {
async fetch(url) {
return [Link]();
// Dependency injection
```
```js
class FakeApi {
async fetch(url) {
```
Instead of using `new`, create objects via factory functions that accept dependencies.
717
```js
function createLogger(prefix) {
return {
async showUser(id) {
return user;
},
};
```
For larger applications, you can build or use a DI container. A container registers providers for
different tokens and resolves dependencies recursively. Libraries like InversifyJS offer decorators and
metadata to declare dependencies.
```js
class Container {
constructor() {
718
register(token, provider) {
[Link](token, provider);
resolve(token) {
return provider(this);
return provider;
[Link](1);
```
- **Don't over-engineer:** Small scripts don't need a full DI container. Use simple functions or
parameters.
## Practice questions
1. **Theory:** Why is dependency injection beneficial for testing? Give an example scenario.
719
2. **Theory:** What is the difference between constructor injection and setter injection? Which is
preferred in JavaScript and why?
3. **Coding:** Refactor a class that directly calls `fetch()` to instead accept an HTTP client
dependency. Show how to inject a mock client for testing.
4. **Coding:** Implement a simple DI container in JavaScript that supports singleton providers and
demonstrates resolving nested dependencies.
720
What are singletons and their drawbacks in JavaScript?
# What are singletons and their drawbacks in JavaScript
A **singleton** is a design pattern that restricts the instantiation of a class to a single instance.
Whenever you request the singleton, the same object is returned. Singletons provide a global point
of access to resources such as configuration, logging or database connections.
JavaScript's module system naturally lends itself to singleton-like behavior: modules are cached after
the first import, so subsequent imports return the same instance. However, you can also implement
a singleton class manually:
```js
class Logger {
constructor() {
if ([Link]) {
return [Link];
[Link] = [];
[Link] = this;
log(message) {
[Link](message);
[Link]("[LOG]", message);
get count() {
return [Link];
[Link]("Hello");
721
[Link]([Link]); // 1
```
The constructor checks if an instance already exists and returns it if so. This ensures only one
instance is created.
Alternatively, you can use an IIFE (Immediately Invoked Function Expression) to encapsulate the
instance:
```js
let instance;
function create() {
let value = 0;
return {
increment() {
value++;
},
getValue() {
return value;
},
};
return {
getInstance() {
return instance;
},
};
})();
722
const counterB = [Link]();
[Link]();
[Link]([Link]()); // 1
```
## Drawbacks of singletons
While singletons can simplify access to shared resources, they come with significant downsides:
- **Global mutable state:** A singleton introduces global state. Any part of your application can
mutate it, making behavior unpredictable and complicating debugging.
- **Hidden dependencies:** Components that rely on singletons implicitly depend on them. Testing
such components becomes harder because you must reset the singleton's state between tests.
- **Tight coupling:** Consumers become tightly coupled to the singleton implementation. Swapping
implementations or running multiple instances (e.g. multiple databases) becomes difficult.
## Alternatives
- **Dependency injection:** Inject dependencies instead of accessing singletons directly. This makes
dependencies explicit and testable.
- **Factory functions:** Use functions to create instances as needed. Pass them down the call chain
rather than storing them globally.
- **Module exports:** Use ES modules to encapsulate state and functions. If you need multiple
instances, export a factory function instead of exporting a single shared object.
## Practice questions
1. **Theory:** Why do singletons make unit testing more complicated? Provide an example of test
pollution caused by a singleton.
2. **Theory:** Describe scenarios where a singleton might be appropriate and scenarios where it
should be avoided.
723
3. **Coding:** Implement a singleton pattern using ES module caching. Then show how you would
refactor the code to avoid the singleton by injecting dependencies.
4. **Coding:** Create a logging module that stores logs in memory but allows multiple independent
loggers. How would you structure the module to avoid a singleton?
724
What is event-driven architecture and how can it be
implemented in JavaScript?
# What is event-driven architecture and how can it be implemented in JavaScript
## Characteristics of EDA
- **Producers and consumers:** Components produce events when something happens (e.g. user
input, a message arrives). Other components consume these events and perform actions.
- **Decoupling:** Producers and consumers do not call each other directly. They interact through an
event broker or message bus.
- **Event flow:** Events can trigger other events, forming a reactive chain.
## Event-driven JavaScript
JavaScript is well suited for EDA because of its event loop and asynchronous APIs. You already use
event-driven programming when handling DOM events (click, keydown) or [Link] events.
```js
725
[Link]("user:login", (e) => {
});
function loginUser(user) {
```
Here, components fire events on the bus and others listen for them. The bus decouples producers
from consumers. You can also use libraries like mitt or build more sophisticated buses that support
namespaces or wildcard topics.
[Link] has a built-in `EventEmitter` class used throughout the standard library:
```js
// Consumer
});
// Producer
function createOrder(items) {
726
const order = { id: [Link](), items };
[Link]("order:created", order);
createOrder(["apple", "banana"]);
```
`EventEmitter` queues listeners and executes them asynchronously after the current call stack,
making it ideal for I/O completion events, timers, etc.
In distributed systems, EDA is implemented using message brokers (e.g. RabbitMQ, Kafka, Redis
Pub/Sub). Components publish events to a broker; other services subscribe to event streams. This
approach decouples services, improves reliability (thanks to queues and persistence) and allows
independent scaling. Libraries like `amqplib` (RabbitMQ) or `kafkajs` can be used in [Link] to
integrate with such brokers.
**Benefits:**
**Challenges:**
- Harder to trace flow—events may pass through many handlers, making debugging more complex.
- Error handling—in distributed EDA, ensure failed event processing is retried or logged
appropriately.
727
## Practice questions
1. **Theory:** What are the main benefits of event-driven architecture compared to a traditional
request-response model?
2. **Theory:** Explain how the EventEmitter API in [Link] enables EDA. How do you handle errors
thrown inside event handlers?
3. **Coding:** Implement an event bus in the browser that supports namespaced events (e.g.
`'chat:message'`). Show how producers and consumers interact through the bus.
4. **Coding:** Set up a simple [Link] service that publishes messages to Redis Pub/Sub and
another service that subscribes and processes them. Explain how this demonstrates EDA in a
distributed system.
728
Explain memoization strategies and cache invalidation
techniques
# Explain memoization strategies and cache invalidation techniques
**Memoization** is an optimization technique that stores the results of expensive function calls and
returns the cached result when the same inputs occur again. When used properly, it can
dramatically improve performance for pure functions that are called repeatedly with the same
arguments. However, effective memoization requires choosing appropriate caching strategies and
knowing when to invalidate stale entries.
## Basic memoization
A simple memoization function wraps another function and caches its results based on the input
arguments. For functions with a single primitive argument, a plain object or `Map` suffices:
```js
function memoize(fn) {
return function(arg) {
if ([Link](arg)) {
return [Link](arg);
[Link](arg, result);
return result;
};
[Link]('Computing', n);
return n * n;
};
729
const fastSquare = memoize(slowSquare);
```
This approach works for deterministic functions with primitive arguments. If the function takes
multiple parameters or objects, you need a more robust key—e.g. serializing arguments with JSON or
using a `WeakMap` keyed by object.
## Advanced strategies
An LRU cache discards the least recently accessed items when it reaches a maximum size. This
prevents memory from growing unbounded. You can implement an LRU cache by combining a map
with a doubly linked list to track usage order. Libraries such as `lru-cache` handle this for you.
You might want to invalidate cache entries after a certain period. A TTL cache associates each entry
with an expiration timestamp. When retrieving a value, the cache checks whether it has
expired. This is useful when underlying data changes over time.
Functions that accept many arguments or objects can benefit from normalizing parameters to a
unique key. For example, you can create a composite key by joining arguments or by using a
WeakMap that maps argument objects to results.
730
Not all functions should be memoized. Functions with side effects, non-deterministic results (e.g.
random or time based) or huge argument spaces may not benefit. Memoization works best with
pure functions that return the same output for the same input.
Memoization caches can become stale. You need strategies to invalidate or refresh entries:
* **Manual invalidation:** Expose a method to clear the cache or remove specific keys when
underlying data changes.
* **Max size eviction:** In an LRU cache, old items are removed automatically when new items
push the cache over its size limit.
* **Time-based expiration:** Entries expire after a timeout (TTL). When retrieving, check if the
entry is still valid.
* **Event-driven invalidation:** In more complex systems, listen for events (e.g. database updates)
and invalidate related cache entries.
The classic example is the Fibonacci sequence. A naive recursive implementation has exponential
complexity. Memoization turns it linear:
```js
function memoizeFib() {
function fib(n) {
if (n < 2) return n;
cache[n] = result;
return result;
return fib;
731
}
```
Here, the inner `fib` function uses a closure over the cache. Without memoization, computing
`fib(40)` would take millions of recursive calls.
## Practice questions
1. **Theory:** Why is memoization effective only for pure functions? Give an example where
memoization would not help.
2. **Theory:** Compare LRU and TTL caches. When would you choose one over the other?
3. **Coding:** Implement a memoized version of a function that sorts arrays of numbers. How
would you ensure the cache key uniquely identifies the input array regardless of reference?
4. **Coding:** Write a memoization helper that supports a maximum cache size and automatically
evicts the least recently used entry. Test it with a computationally expensive function.
732
What is reactive programming and how does it differ
from imperative programming?
# What is reactive programming and how does it differ from imperative programming
## Imperative vs reactive
Imperative code describes *how* to achieve a result by giving explicit commands. You manage state
and control flow manually. For example, consider updating the UI when a user types:
```js
});
```
Here you explicitly attach a listener, fetch results and update the DOM in response.
733
Reactive programming treats values as streams that you can transform, filter, combine and
observe. When a stream emits a new value, any dependent computations automatically
update. Libraries like RxJS provide observables for this.
```js
fromEvent(input, 'input').pipe(
debounceTime(300),
).subscribe(text => {
[Link] = text;
});
```
This code creates an observable stream of input events, debounces them, maps to query strings,
switches to a new network request whenever the query changes and updates the UI whenever new
results come back. The code describes *what* should happen rather than *how* to manage control
flow. The RxJS library handles the timing and cancellation of requests.
734
* **Composability:** Operators like `map`, `filter`, `debounceTime`, `combineLatest` allow you to
build complex data pipelines from simple pieces.
* **Error handling and cancellation:** Streams can propagate errors and support cancellation
semantics (e.g. using `switchMap` to cancel previous requests).
* **Consistency:** Reactive code often looks similar on the client and server (e.g. RxJS in the
browser and RxJava on the backend).
* **Control flow:** In imperative code, you control when and how operations run. In reactive code,
you declare relationships and let the runtime schedule operations.
* **State management:** Reactive systems maintain state through streams and reactive
variables. Imperative code stores state in variables you update manually.
* **Concurrency:** Reactive systems are inherently asynchronous and often use non-blocking
I/O. Imperative code may block or require manual callback handling.
* Complex user interfaces with many interdependent events and asynchronous operations.
* Systems that need to react to multiple sources of events and combine them elegantly.
## Practice questions
1. **Theory:** In your own words, define reactive programming and contrast it with imperative
programming.
2. **Theory:** What are the advantages of using a reactive approach when dealing with user input
and network requests?
3. **Coding:** Using RxJS (or a similar library), create a reactive autocomplete input that fetches
suggestions as the user types, debouncing requests and cancelling previous ones.
4. **Coding:** Implement a simple reactive data flow without external libraries by creating a `Signal`
class that notifies observers when its value changes. Use it to link two input fields so that changing
one updates the other.
735
How does decorator syntax enhance class behavior?
# How does decorator syntax enhance class behavior
Decorators are a proposed language feature (currently at Stage 3 of the TC39 process) that allow you
to attach behaviors or metadata to classes, methods, fields and accessors using a concise
`@decorator` syntax. Decorators enable modular composition of cross-cutting concerns—such as
logging, memoization, access control or dependency injection—without modifying the core business
logic. Several frameworks (e.g. Angular, NestJS) and libraries use decorators extensively.
For example, a class decorator might modify the class constructor or static fields; a method decorator
can wrap a method to add logging; a property decorator can enforce validation.
## Examples of decorators
Suppose you want to log every call to a method. You could write a decorator that wraps the original
function and logs arguments and return values.
```js
736
return result;
};
class Calculator {
@log
add(a, b) {
return a + b;
```
The `log` decorator receives the original method (`target`) and a `context` object describing the
method. It returns a new function that wraps the original method with logging. When `[Link]()` is
called, the wrapper logs the call and delegates to the original implementation.
```js
return function () {
if (!(cacheKey in this)) {
this[cacheKey] = [Link](this);
737
}
return this[cacheKey];
};
class Expensive {
@memoizeAccessor
get largeArray() {
```
Here, the `memoizeAccessor` decorator wraps the getter so that it only executes once. Subsequent
accesses return the cached result.
```js
function controller(path) {
738
[Link](path, target);
};
@controller('/users')
class UserController {
// ...methods...
```
The `@controller('/users')` decorator stores the class in a registry keyed by path. Later, a framework
could read this registry to configure routes.
Decorators are syntactic sugar over higher-order functions applied at declaration time. You could
achieve similar results by manually wrapping methods or classes, but decorators make the intent
clear and reduce boilerplate. Unlike higher-order components in React, which wrap components at
runtime, class decorators run once when the class is defined.
The decorator proposal is still experimental and may evolve. Not all environments support
decorators yet. Transpilers like Babel or TypeScript support older decorator syntax, but it differs from
the current proposal shown here. When using decorators today, check your tooling and be aware
that the API may change.
## Practice questions
1. **Theory:** What kinds of elements (class, method, field) can be decorated, and what does a
decorator function receive as arguments?
739
2. **Theory:** How does a decorator differ from a higher-order function? When would you prefer
one over the other?
3. **Coding:** Implement a method decorator `time` that measures and logs the execution time of a
method. Apply it to a function that performs a heavy computation.
4. **Coding:** Create a class decorator `sealed` that prevents further modification of the class
prototype (e.g. using `[Link]()`). Demonstrate its effect.
740
What are import assertions and how do they ensure
module type safety?
# What is import assertions and how do they ensure module type safety
When using JavaScript modules, the default behavior is to treat imported files as JavaScript.
However, modern applications often import other types of resources, such as JSON or CSS modules.
**Import assertions** provide a standardized way to explicitly declare the expected type of a
module when importing it. By asserting the type, you help the JavaScript engine validate that the
imported content matches your expectation and prevent silent failures or security issues.
Historically, browsers and bundlers allowed syntax like `import data from './[Link]';` to import
JSON as if it were a JavaScript module. This was convenient but ambiguous: what if a `.js` file is
accidentally served as JSON? To remove this ambiguity and improve security, the ECMAScript spec
introduced **import assertions**—an extra clause in the import statement that declares the
module's type.
Import assertions use the `assert` keyword followed by an object literal after the module specifier:
```js
});
```
In the static form, the `assert { type: 'json' }` tells the loader to treat the imported resource as JSON.
If the resource is not of the asserted type, the import will fail with a syntax error. In the dynamic
form, the second argument is an options object with an `assert` property.
741
### Supported types
At the time of writing, the `type` assertion is defined for JSON (`'json'`) in browsers and [Link].
Future proposals may define assertions for other module formats (e.g. `css`, `wasm`). Tools and
loaders can extend this mechanism to support custom file types.
```json
"apiUrl": "[Link]
"timeout": 5000
```
```js
// [Link]
return [Link];
```
742
1. The loader sees the `assert { type: 'json' }` clause and knows to parse the file as JSON, not
JavaScript.
2. If the server returns a MIME type that is not JSON (`application/json`), or the file contains invalid
JSON, the import fails at parsing time rather than silently returning an unexpected value.
3. The imported value is the parsed JSON object (`config`), which you can use immediately. You don't
need to call `fetch()` and `[Link]()` yourself.
If you omit the assertion and try to import a JSON file, many environments will throw an error
because unasserted JSON imports are not allowed. Assertions explicitly opt into this behavior.
- **Type safety:** Assertions ensure that a module is only loaded if it matches the expected format.
They prevent accidentally interpreting a JSON file as JavaScript or vice versa.
- **Security:** They help mitigate attacks where a malicious server returns a different file type (e.g.
serving JavaScript when JSON was expected). The import will fail instead of executing unexpected
code.
- **Clarity:** The import statement documents the developer's intent. Other developers reading the
code know that a non-JavaScript resource is being imported.
- **Extensibility:** In the future, assertions may allow fine-grained control over module loading,
such as specifying integrity hashes or custom loaders.
## Practice questions
1. **Theory:** What problem do import assertions solve, and how do they improve module type
safety?
2. **Theory:** How do import assertions differ between static and dynamic imports? Provide the
correct syntax for each.
3. **Coding:** Create a module that imports a JSON configuration file with an import assertion and
exports a function that reads a property from it. Then write a test showing that the import fails if the
file contains invalid JSON.
4. **Coding:** Suppose browsers support CSS modules with `type: 'css'`. Demonstrate how you
would import a stylesheet using an import assertion and apply it to a component.
743
What is module federation and how does it enable
micro-frontend architectures?
# What is module federation and how does it enable micro-frontend architectures
**Module Federation** is a feature introduced in webpack 5 that allows multiple separate builds to
dynamically share and load modules at runtime. Traditionally, JavaScript bundles are isolated—code
in one bundle cannot import code from another unless they're built together. Module Federation
breaks this barrier by enabling applications to **expose** modules and **consume** modules from
remote builds on demand, without needing to publish them to a package registry or rebuild the
consuming application.
- **Host (or shell) application:** The main application that will load remote modules at runtime.
- **Remote application:** A separate build that exposes some of its modules for consumption by
others.
Each build configures the webpack `ModuleFederationPlugin` with information about what it
exposes and what it consumes.
```js
744
[Link] = {
plugins: [
new ModuleFederationPlugin({
name: "remoteApp",
filename: "[Link]",
exposes: {
"./Button": "./src/components/[Link]",
"./utils": "./src/utils/[Link]",
},
shared: {
},
}),
],
};
```
- `filename`: The compiled file that contains the exposed modules. It is served via HTTP.
- `exposes`: Maps internal modules to public names. These paths will be available to hosts.
- `shared`: Declares shared dependencies so that only one copy is loaded (singleton).
```js
745
[Link] = {
plugins: [
new ModuleFederationPlugin({
remotes: {
remoteApp: "remoteApp@[Link]
},
shared: {
},
}),
],
};
```
Here, the host declares a `remotes` object mapping the remote's name to a URL where
`[Link]` is served. At runtime, webpack fetches this file, resolves the exposed modules and
makes them available via `import()`.
Once configured, the host can import remote modules just like local ones:
```js
});
746
[Link](formatDate(new Date()));
});
```
Webpack loads `[Link]` asynchronously, resolves the module, ensures shared dependencies
(like React) are not duplicated, and returns the exported value. This happens at runtime, not build
time.
## Enabling micro-frontends
In a micro-frontend architecture, each team can build its part of the UI as a separate project with its
own repository and deployment pipeline. Module Federation enables these fragments to be
integrated into a host at runtime without central coordination.
For example, a dashboard might consist of several panels built by different teams. Each panel is a
remote exposing a React component. The shell application dynamically loads the panels based on
configuration and composes them together. When a panel team releases a new version, the host
automatically picks it up without a rebuild. This independence accelerates deployment and reduces
coupling between teams.
Shared dependencies (such as React) should be marked as singletons so that only one instance is
loaded. Otherwise, multiple versions of the library might be imported, leading to inconsistent state
or duplicate React copies.
- **Version compatibility:** All consuming apps must use compatible versions of shared libraries.
- **Runtime failures:** If a remote is unavailable or misconfigured, the host must handle errors
gracefully.
- **Complexity:** Module Federation introduces additional build and deployment considerations
(e.g. serving [Link] over HTTP). Proper versioning and contract testing are essential.
## Practice questions
747
1. **Theory:** Explain how the `ModuleFederationPlugin` enables a host application to load
modules from a remote at runtime.
2. **Theory:** What is the purpose of the `shared` section in the module federation configuration?
Why are singletons important?
3. **Coding:** Set up a minimal host and remote application using webpack. Expose a component
from the remote and consume it in the host. Describe how you would handle errors if the remote
fails to load.
4. **Coding:** Suppose you have two remotes that both depend on the same version of a UI library.
Show how to configure module federation so that only one copy of the library is loaded.
748
What are WeakMap-based private fields and how do
they differ from native private fields ()?
# What are WeakMap-based private fields and how do they differ from native private fields
Before the introduction of native private fields (`#field`) in JavaScript classes, developers often used
`WeakMap`s to simulate private data. Understanding how these patterns work helps you appreciate
the benefits and trade-offs of the new syntax.
A `WeakMap` is a collection where keys must be objects and values can be arbitrary data. The keys
are weakly referenced: if no other references to the key object exist, it can be garbage-collected and
the corresponding entry in the `WeakMap` is removed.
Using a `WeakMap`, you can associate private data with each instance of a class:
```js
class Person {
constructor(name, age) {
getName() {
return _privateData.get(this).name;
celebrateBirthday() {
[Link]++;
749
}
[Link]([Link]()); // Alice
[Link]([Link]); // undefined
```
**How it works:**
2. In the constructor, the private values are stored in the map with `this` as the key.
4. There is no public way to access the private data because it's scoped outside the class.
5. When the instance (`alice`) is no longer referenced, the entry in the `WeakMap` disappears
automatically.
- **Pros:** Works in older environments; private data is not exposed on the object; memory cleans
up automatically. You can have truly private properties with dynamic names.
- **Cons:** Slightly verbose and error-prone (you must remember to use the map in every method);
still accessible to privileged code that closes over the WeakMap; cannot define private methods this
way; slower access due to the map lookup.
ECMAScript now supports **private class fields** and methods with a `#` prefix. Private fields are
defined inside the class body and are only accessible from within the class declaration.
```js
class BankAccount {
750
#balance;
constructor(initial) {
this.#balance = initial;
deposit(amount) {
this.#balance += amount;
getBalance() {
return this.#balance;
[Link](50);
[Link]([Link]()); // 150
```
**Key features:**
- **Lexical privacy:** The `#balance` field is only accessible from within the class body. Attempting
to access it outside results in a syntax error. No property with that name exists on the instance; it is
stored in an internal slot.
- **Performance:** Native private fields have optimized access and don't require a map lookup.
- **Private methods and accessors:** You can declare `#method()` and private getters/setters: `get
#secret() { ... }`. These are not possible with the WeakMap pattern without additional complexity.
- **Static private fields:** You can declare `static #counter = 0` to have per-class private state.
751
## Differences at a glance
Native private fields are the preferred way to declare private data in modern JavaScript. They provide
language-level enforcement, better ergonomics and performance. However, if you need to attach
private data to objects that are not class instances (e.g. DOM elements), or you need dynamic private
keys, the WeakMap pattern remains useful.
## Practice questions
1. **Theory:** Describe how private data is stored and accessed in the WeakMap pattern versus
native private fields.
2. **Theory:** What happens if you try to access a native private field from outside the class?
Contrast this with the WeakMap pattern.
3. **Coding:** Rewrite the `Person` class using native private fields instead of a WeakMap. Include a
private method that returns a greeting.
4. **Coding:** Show how you might attach private data to DOM elements using a WeakMap. Explain
why native private fields wouldn't work for this use case.
752
How does lazy vs eager evaluation affect performance in
iterables?
# How does lazy vs eager evaluation affect performance in iterables
**Lazy evaluation** delays computing values until they are needed, while **eager evaluation**
computes values immediately when an operation is invoked. In the context of iterables, lazy
evaluation allows you to process sequences element by element on demand, often through
generators or iterators. Eager evaluation typically produces a fully realized array or collection up
front.
Understanding the performance implications of these strategies helps you choose the right approach
for your use case.
## Eager iterables
Eager methods (such as `[Link]()`, `filter()` and `reduce()`) create intermediate arrays
for each step. Consider this chain:
```js
```
Each call to `map` and `filter` creates a new array. For small collections, the overhead is negligible.
For large datasets, however, repeatedly allocating intermediate arrays consumes memory and time.
Lazy evaluation uses generators to produce values on the fly. You define a generator that yields
values one at a time, and consumers iterate through them as needed. You can build your own lazy
counterparts to `map` and `filter`:
753
```js
yield fn(item);
);
let total = 0;
total += val;
[Link](total);
```
754
**What's happening?**
2. `map(range..., x => x * 2)` wraps that generator and yields each value multiplied by 2.
4. The `for...of` loop requests values from this pipeline one at a time. As soon as `total` exceeds 1000,
the loop breaks and the generator pipeline stops producing more values. Most of the million values
are never computed at all.
- **Memory efficiency:** No intermediate arrays are created. Only one value at a time exists in
memory.
- **Large collections:** Lazy iteration shines when processing large or potentially infinite sequences.
Eager methods may allocate large arrays and block the event loop, whereas generators yield values
lazily.
- **Single pass vs multiple passes:** If you need to iterate over the data multiple times, a generator
may recompute values each time. A fully realized array may be faster on subsequent passes.
- **Complex transformations:** For small arrays or simple operations, the overhead of generator
functions may outweigh the benefits.
- **Debugging:** Lazy pipelines can be harder to debug because computations happen at iteration
time. Eager arrays are easier to inspect.
755
Use eager evaluation when:
- Simplicity and ease of debugging are more important than raw efficiency.
## Practice questions
1. **Theory:** Explain why lazy evaluation can reduce memory usage compared to eager evaluation.
Give an example where this matters.
2. **Theory:** What are some potential downsides of using lazy evaluation for small arrays?
Describe scenarios where eager evaluation might be better.
3. **Coding:** Implement a lazy version of `take(n, iterable)` that yields the first `n` values of an
iterable. Then use it to sum the first 100 even numbers from an infinite generator.
4. **Coding:** Compare the performance of summing all numbers from 1 to 1 million using eager
array methods vs. a lazy generator pipeline. Measure memory usage and execution time, and discuss
the results.
756
What are WeakKeys and WeakRefs — new memory-safe
references?
# What are WeakKeys and WeakRefs: new memory-safe references
JavaScript memory management is automatic: the garbage collector frees objects that are no longer
reachable. However, certain data structures (like `Map` and `Set`) hold strong references to their keys
and values. As long as a key is present in a `Map`, the object cannot be reclaimed even if there are no
other references. This can lead to memory leaks in caches or listeners.
## WeakRef
`WeakRef` is a class that lets you hold a weak reference to an object. A weak reference does not
prevent the object from being garbage-collected. You can attempt to access the object via `.deref()`,
which returns the object if it is still alive or `undefined` if it has been collected.
```js
class Cache {
constructor() {
set(key, value) {
get(key) {
757
if (value === undefined) {
[Link](key);
return value;
[Link]("foo", obj);
// Later, obj may be garbage-collected. [Link]('foo') will return undefined once collected.
```
**When to use WeakRef:** WeakRefs are useful for caches or look-aside maps where you can
recreate a value if it's been collected. They should not be used to reference critical data because
derefing a collected object returns `undefined`. You should always check for undefined.
## FinalizationRegistry
`FinalizationRegistry` lets you register a finalization callback that the engine calls when an object is
garbage-collected. This is helpful for cleaning up resources associated with an object, such as
removing it from other data structures.
```js
});
function trackResource(resource) {
758
[Link](obj, [Link]);
return obj;
tracked = null; // when obj is collected, the callback logs "Cleaning up for 1"
```
Finalization callbacks run at an arbitrary time after the object becomes unreachable. You should not
rely on them for timely cleanup or critical logic.
The traditional `WeakMap` and `WeakSet` require that the keys are objects and hold weak references
to those keys. However, the values of a `WeakMap` are strongly referenced. New proposals (often
called **WeakKey** collections) aim to extend this concept by allowing keys to be weak and values
to be strong or weak, providing more flexibility for caches. As of now, these proposals are still being
developed.
- **No memory leaks:** If the only references to an object are weak, it is eligible for garbage
collection. This avoids leaks in caches or observer lists.
- **No access after collection:** If you dereference a WeakRef after the object has been collected,
you get `undefined`. There is no chance of accessing stale memory.
- **Controlled cleanup:** FinalizationRegistry gives you a hook to remove entries or free associated
resources.
### Caveats
- **No strong references:** Do not store critical data solely via WeakRefs; always keep at least one
strong reference if the data must persist.
759
- **Potential misuse:** Overusing WeakRefs can make code hard to reason about. Use them for
cache layers or to break cycles, not for general data storage.
## Practice questions
1. **Theory:** Explain how a weak reference differs from a strong reference. Why can weak
references help prevent memory leaks?
2. **Theory:** What does `FinalizationRegistry` do, and why should you not rely on it for critical
cleanup logic?
3. **Coding:** Implement a memoization cache that uses WeakRefs to store results keyed by
objects. Demonstrate how results may disappear from the cache when the keys are no longer
referenced elsewhere.
4. **Coding:** Write a simple wrapper around WeakRef that attempts to get the value and, if it has
been collected, computes a new value and stores a new WeakRef. Explain how this pattern can be
used in caches.
760
How do structuredClone, postMessage, and
transferable objects relate?
# How do `structuredClone`, `postMessage` and transferable objects relate
JavaScript environments often need to copy or send data between contexts—such as between the
main thread and Web Workers, or between different windows or iframes. Copying complex objects
safely and efficiently is non-trivial. Three related mechanisms address this: the **structured clone
algorithm**, the `postMessage()` API, and **transferable objects**.
The **structured clone algorithm** is a specification that defines how to deeply copy a broad range
of JavaScript values. It supports primitives, plain objects, arrays, dates, typed arrays, Map, Set,
RegExp, ArrayBuffer, and more. Crucially, it can handle circular references and shared references,
preserving object graphs.
Historically, browsers implemented the structured clone algorithm internally, but there was no way
for developers to invoke it directly. This changed with `structuredClone()`, a global function that
allows you to deep-clone structured clone-serializable values:
```js
[Link](4);
[Link]([Link]); // [1, 2, 3]
```
761
- **Window messaging:** `[Link]()` sends data to another window or iframe.
When you pass an object to `postMessage()`, the environment uses the structured clone algorithm
to copy the data from one context to another. The receiving context gets a deep clone of the original
object. This ensures that the sender and receiver do not share mutable objects, preventing
accidental sharing of memory and data races.
Example:
```js
// main thread
[Link](obj);
// [Link]
[Link]([Link]); // [1, 2, 3]
[Link](4);
};
// Mutating data inside the worker does not affect the original in the main thread
```
Because the clone is deep, modifying `[Link]` in the worker does not affect the original `obj`.
## Transferable objects
762
Some objects hold underlying binary data, such as `ArrayBuffer`, `MessagePort`, `OffscreenCanvas`
and `ImageBitmap`. Copying these objects can be expensive. **Transferable objects** allow you to
transfer ownership of the underlying resource from one context to another instead of copying it.
After transfer, the sender's object becomes unusable (it's "detached") and the receiver gains sole
control.
To transfer objects with `postMessage()`, pass them in an array as the second argument:
```js
// [Link]
[Link]([Link]); // 1024
};
```
In this example, the `ArrayBuffer` moves to the worker without copying its bytes. Attempting to use
the buffer in the main thread after transfer will throw a `TypeError: DetachedBuffer`. Transferables
are essential for high-performance applications such as streaming media, image processing and
parallel computations.
- **Structured clone algorithm** defines how data is copied in both `structuredClone()` and
`postMessage()`. It ensures deep cloning of complex objects and prevents sharing references across
threads.
763
- **`structuredClone()`** exposes the clone algorithm directly for your own deep copy needs within
a single context.
- **`postMessage()`** uses the clone algorithm to send data across execution contexts. It can
optionally transfer ownership of transferable objects to avoid copying.
## Practice questions
1. **Theory:** Describe how the structured clone algorithm differs from using
`[Link]()`/`[Link]()` for deep copying. What kinds of values can it handle that JSON
cannot?
2. **Theory:** Why do browsers detach a transferable object from the sender after it is transferred
via `postMessage()`? What would happen if they didn't?
3. **Coding:** Use `structuredClone()` to create a deep copy of an object containing a Map and a
Date. Demonstrate that modifications to the copy do not affect the original.
4. **Coding:** Send an `ArrayBuffer` to a worker using `postMessage()` with and without specifying
it as transferable. Measure the time taken and observe the behavior of the original buffer. Discuss
when you should use transferables.
764