The JavaScript Cheatsheet Collection
KDP Book Manifest & MetadataClick to Expand & CopyClick to CollapseManifest
Use the copy buttons below to copy metadata verbatim into the Amazon KDP Publishing forms.
- Books > Computers & Technology > Programming > Languages > JavaScript
- Books > Computers & Technology > Web Development > Web Programming
- Books > Computers & Technology > Software Development & Engineering > Tools
The JavaScript Cheatsheet Collection
A Simple, Scannable Quick Reference Guide for Modern Web Developers
The JavaScript Cheatsheet Collection
First Edition: 2026
Copyright © 2026 by usefulcheatsheets.com. All rights reserved.
No part of this book may be reproduced in any form or by any electronic or mechanical means, including information storage and retrieval systems, without written permission from the publisher, except for the use of brief quotations in a book review.
Table of Contents
A quick reference for adding, transforming, searching, and ordering items in JavaScript arrays.
Learn arrow function syntax, implicit returns, and lexical this with clear practical examples.
Create, format, and modify dates in JavaScript with timestamps, UTC, and common calculations.
Quick debugging one-liners for logging, tracing, timing, and inspecting JavaScript values in any project.
Master object and array destructuring for cleaner variable assignment and function parameters.
Select, edit, style, and create DOM elements with vanilla JavaScript and handle user events.
Pick the right loop in JavaScript with for, while, for-of, for-in, and array methods.
Create, clone, merge, and transform JavaScript objects with modern syntax and practical patterns.
Use localStorage and sessionStorage to persist data in the browser across sessions and refreshes.
Master string manipulation with substring, slice, replace, split, and common text operations.
Table of Contents
Learn template literals, string interpolation, multiline strings, and tagged templates for cleaner code.
Handle errors safely with try-catch patterns for async code, error propagation, global handlers, and JSON parsing.
Handle async code cleanly with async functions, await, error handling, and parallel execution.
Build objects with class syntax covering constructors, inheritance, static members, and private fields.
Handle errors gracefully in JavaScript using try/catch, custom error classes, and finally blocks.
Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.
Master the Fetch API for making HTTP requests in JavaScript, including GET, POST, error handling, and working with JSON responses.
Organize JavaScript code with ES6 modules using named exports, default exports, and dynamic imports.
Access nested properties safely with optional chaining and pair it with nullish coalescing for defaults.
Master asynchronous operations with promises, chaining methods, async/await syntax, and parallel combinators.
Table of Contents
Master collection structures using Set and Map, understand key differences, and implement dynamic data lookups.
Use spread syntax to expand arrays and objects, and rest parameters to handle variable function arguments.
Schedule code execution using setTimeout and setInterval, and rate-limit handlers using debounce and throttle patterns.
Understand how JavaScript converts types automatically and avoid common equality and comparison bugs.
Learn async iterators, async generators, and for await...of for consuming asynchronous data lazily.
Control the this keyword explicitly using call, apply, and bind on any function.
Understand how closures allow inner functions to retain access to variables from parent scopes with examples.
Transform multi-argument functions into chained calls and combine small functions into pipelines.
Learn how the call stack, microtask queue, and task queue control JavaScript execution order.
Learn how generator functions pause and resume execution to build lazy sequences and iterables.
Table of Contents
Learn iterators, the iteration protocol, and generators for controlling how data is consumed.
Understand the prototype chain, Object.create, and how class syntax wraps prototypal inheritance.
Learn how Proxy traps and the Reflect API intercept and control object behavior in JavaScript.
Learn regex patterns, flags, exec, test, match, and replace for powerful string processing.
Use unique Symbol values as collision-free object keys and customize built-in object behavior.
Store object-keyed data and hold references without blocking garbage collection in JavaScript.
Welcome to the JavaScript Masterclass
JavaScript is a key topic in Technology development.
This reference book compiles comprehensive cheatsheets covering everything from fundamentals to advanced patterns.
Use this book as a daily reference or read it linearly to build your knowledge.
How to Use This Book
Each page is a visual cheatsheet with core concepts, practical steps, code snippets, and warnings.
JavaScript Array Methods
A quick reference for adding, transforming, searching, and ordering items in JavaScript arrays.
TL;DR
- 01Use
push(),pop(),shift(), andunshift()to add or remove items. - 02Use
map(),filter(), andreduce()to transform array data. - 03Use
find(),some(), andincludes()to search arrays.
Tips
- 01Prefer non-mutating methods like
map,filter, andslicewhen you want to keep your original array unchanged. - 02Chain
map,filter, andreducetogether to transform data in one readable pipeline instead of writing several separate loops.
Warnings
- 01Sort and reverse change the original array in place, so copy first if you need to keep the source order.
- 02
splice()also mutates the original array, and the wrong delete count can silently remove items you meant to keep.
JavaScript Array Methods
(continued)Add and Remove
push()Adds one or more items to the end, returns the new length.
const cart = ['apple']; cart.push('banana'); // 2pop()Removes and returns the last item.
const stack = ['init', 'save', 'close']; stack.pop(); // 'close'shift()Removes and returns the first item.
const queue = ['first', 'second', 'third']; queue.shift(); // 'first'unshift()Adds one or more items to the front, returns the new length.
const updates = ['old']; updates.unshift('new'); // 2splice()Inserts, removes, or replaces items at any index. Mutates in place.
const list = ['a', 'b', 'c']; list.splice(1, 1, 'x'); // returns ['b'] // list is now ['a', 'x', 'c']
JavaScript Array Methods
(continued)Copy and Combine
concat()Merges two or more arrays into a new array without mutation.
const arr1 = [1, 2]; arr1.concat([3, 4]); // [1, 2, 3, 4]spread operatorCopies or combines arrays cleanly without mutation.
const a = [1, 2]; const b = [...a, 3]; // [1, 2, 3] const merged = [...a, ...b, ...c]; // all combinedslice()Copies a whole array or a portion without mutating the source.
const arr = [1, 2, 3, 4]; arr.slice(1, 3); // [2, 3]Array.of()Builds a new array from arguments, even a single number.
Array.of(7); // [7] Array(7); // empty array with length 7
JavaScript Array Methods
(continued)Transform Data
map()Creates a new array by transforming every element with a function.
const prices = [10, 20, 30]; prices.map(p => p * 1.1); // [11, 22, 33]filter()Returns a new array keeping only items that pass the test.
const users = [{active: true}, {active: false}]; users.filter(u => u.active); // [{active: true}]reduce()Combines all items into a single value; always pass an initial value.
const nums = [1, 2, 3]; nums.reduce((acc, n) => acc + n, 0); // 6
JavaScript Array Methods
(continued)Search and Test
find()Returns the first element matching a condition, or undefined.
users.find(u => u.id === 1); // {id: 1, ...}findIndex()Returns the index of the first match, or -1 if not found.
users.findIndex(u => u.id === 1); // 0includes()Returns true if the exact value exists in the array.
['read', 'write'].includes('write'); // truesome()Returns true if at least one element passes the test.
users.some(u => u.isAdmin); // true or falseevery()Returns true only if all elements pass the test.
tasks.every(t => t.done); // true or false
JavaScript Array Methods
(continued)Order and Display
sort()Sorts the array in place; always pass a comparator for reliable results.
names.sort((a, b) => a.localeCompare(b));reverse()Flips the order of items in place.
[1, 2, 3].reverse(); // [3, 2, 1]join()Combines all elements into a string with a separator.
['a', 'b', 'c'].join(','); // "a,b,c"forEach()Runs a function on each item for side effects; returns undefined.
items.forEach(item => console.log(item));Array.from()Converts any iterable — string, Set, NodeList — into a real array.
Array.from('hi'); // ['h', 'i'] Array.from(new Set([1,1,2])); // [1, 2]
JavaScript Array Methods
(continued)Flatten and Access
flat()Collapses nested arrays one level deep; pass a depth to go deeper.
const nested = [1, [2, [3]]]; nested.flat(); // [1, 2, [3]] nested.flat(2); // [1, 2, 3]flatMap()Maps over items and flattens the result one level in a single pass.
const words = ['hi there', 'foo bar']; words.flatMap(s => s.split(' ')); // ['hi', 'there', 'foo', 'bar']at()Accesses an element by index; negative values count from the end.
const arr = [10, 20, 30, 40]; arr.at(0); // 10 arr.at(-1); // 40fill()Overwrites a range of elements with a static value in place.
const arr = [1, 2, 3, 4, 5]; arr.fill(0, 1, 3); // [1, 0, 0, 4, 5]Array.isArray()Safely checks whether a value is an array at runtime.
Array.isArray([1, 2]); // true Array.isArray('abc'); // false
JavaScript Array Methods
(FAQ)FAQ
Use [...new Set(array)] to deduplicate primitives in one line. For objects, filter by a unique key instead: array.filter((item, i, arr) => arr.findIndex(x => x.id === item.id) === i).
map() returns a new array of transformed values and is chainable. forEach() always returns undefined and exists purely for side effects. If you need the result, use map().
Use findIndex() with a callback — it returns the index of the first match or -1 if none found. Use indexOf() only when searching for an exact primitive value, since it can't accept a predicate.
Use the spread operator: const merged = [...arr1, ...arr2], or arr1.concat(arr2). Both return a new array and leave the originals unchanged.
Without an initial value, reduce() uses the first element as the accumulator and starts iterating at index 1. This silently breaks with empty arrays, which throw, or when the first element is the wrong type. Always pass an explicit initial value as the second argument.
[1, 2, 3].reduce((sum, n) => sum + n, 0); // 6
[].reduce((sum, n) => sum + n, 0); // 0
[].reduce((sum, n) => sum + n); // TypeErrorJavaScript Array Methods
(In Practice)Filter, Map, Sort, Then Reduce
Chains filter, map, sort, and reduce on an order list to compute tax-adjusted revenue in one readable pipeline.
- 01
filter()removes pending and cancelled orders before they reach the revenue calculation. - 02
map()adds a tax-adjustedtotalfield to each order without mutating the originals. - 03
sort()reorders results from highest to lowest total. - 04
reduce()sums every total into a single revenue figure, starting from zero. - 05
at(0)retrieves the top order; negative indexes likeat(-1)work too.
const orders = [
{ name: 'Keyboard', price: 89.99, status: 'completed' },
{ name: 'Monitor', price: 349.99, status: 'pending' },
{ name: 'Mouse', price: 49.99, status: 'completed' },
{ name: 'Webcam', price: 79.99, status: 'completed' },
{ name: 'Headset', price: 129.99, status: 'cancelled' },
];
const summary = orders
.filter(o => o.status === 'completed')
.map(o => ({ ...o, total: +(o.price * 1.08).toFixed(2) }))
.sort((a, b) => b.total - a.total);
const revenue = summary.reduce((acc, o) => acc + o.total, 0);
console.log('Top order:', summary.at(0).name); // 'Keyboard'
console.log(`Revenue (after tax): $${revenue.toFixed(2)}`); // $237.57Chain filter → map → sort → reduce to build a full data pipeline — one concern per step.
JavaScript Arrow Functions
Learn arrow function syntax, implicit returns, and lexical this with clear practical examples.
TL;DR
- 01Write concise callbacks using
=>instead of thefunctionkeyword. - 02Skip
returnand braces for one-line expressions with implicit returns. - 03Inherit
thisfrom the enclosing scope instead of redefining it.
Tips
- 01Use arrow functions for short callbacks inside
map()andfilter(), since they keep the code easy to scan. - 02Name longer arrow functions by assigning them to a
const, since stack traces then show the variable name.
Warnings
- 01Do not use arrow functions as methods on objects when those methods need access to the object through
this. - 02Arrow functions cannot be used as constructors, so calling one with
newthrows a TypeError instead of creating an instance.
JavaScript Arrow Functions
(continued)Basic Syntax
One parameterDrop the parentheses when the function takes exactly one parameter.
const double = n => n * 2; double(4); // 8Multiple parametersWrap parameters in parentheses when the function takes two or more.
const multiply = (a, b) => a * b; multiply(2, 5); // 10No parametersUse empty parentheses when the function takes no parameters at all.
const greet = () => 'Hello!'; greet(); // "Hello!"Anonymous by defaultArrow functions are anonymous and are usually assigned to a variable.
const handlers = []; handlers.push(() => console.log('clicked'));Use constAssign arrow functions with `const` to prevent accidental, silent reassignment.
const sayHi = () => 'Hi'; sayHi = () => 'Yo'; // throws TypeError
JavaScript Arrow Functions
(continued)Returning Values
Implicit returnDrop the braces and the return keyword to return a single expression.
const sum = (a, b) => a + b;Block bodyUse curly braces with return for multi-line function bodies and logic.
const check = num => { if (num > 10) return 'big'; return 'small'; };Object literalWrap returned object literals in parentheses to avoid a syntax error.
const make = id => ({ id, active: true });Array methodsImplicit returns work well inside array methods like `filter()` and `map()`.
const evens = [1, 2, 3, 4] .filter(n => n % 2 === 0) .map(n => n * 10); // [20, 40]Pick a styleMatch the return style to the code's complexity and readability needs.
const isEven = n => n % 2 === 0; // implicit const classify = n => { // block if (n < 0) return 'negative'; return n % 2 === 0 ? 'even' : 'odd'; };
JavaScript Arrow Functions
(continued)Promise and Async Patterns
Promise chainsMost real-world arrow functions live inside `.then()`, `.catch()`, or async wrappers.
fetch('/api/user') .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err));Promise.all()Run independent async calls together and destructure results once every promise resolves.
const loadDashboard = async () => { const [users, posts] = await Promise.all([ fetch('/api/users').then(r => r.json()), fetch('/api/posts').then(r => r.json()), ]); };Async arrowUse an arrow function as the body of an async function for compact definitions.
const getUser = async (id) => { const res = await fetch(`/users/${id}`); return res.json(); };Async IIFERun an async arrow immediately when you need `await` outside any named function.
(async () => { const data = await fetchDashboardStats(); console.log(data); })();try/catchWrap `await` calls in `try/catch` to handle rejected promises inside async arrows.
const loadUser = async (id) => { try { const res = await fetch(`/users/${id}`); return await res.json(); } catch (err) { console.error('Failed to load user', err); } };
JavaScript Arrow Functions
(continued)Lexical This
Arrow inherits thisAn arrow function inherits `this` from its surrounding scope, so it stays bound.
function Timer() { this.count = 0; setInterval(() => this.count++, 1000); }Regular function thisA regular function creates its own `this`, which can cause unexpected values.
function Timer() { this.count = 0; setInterval(function () { this.count++; // this is wrong here }, 1000); }Class methodsThis behavior makes callbacks inside class methods work without manual binding.
class Counter { count = 0; increment = () => { this.count++; }; }Method callbacksArrow callbacks inside a method inherit `this` from that method, not the caller.
const cart = { items: [{ price: 10 }, { price: 25 }], total() { return this.items.reduce( (sum, i) => sum + i.price, 0 ); }, }; cart.total(); // 35Event listenersBind event handlers with arrow functions so `this` still points at the class instance.
class Button { clicks = 0; constructor(el) { el.addEventListener('click', () => { this.clicks++; }); } }
JavaScript Arrow Functions
(continued)Limitations
No arguments objectArrow functions have no `arguments` object; use rest parameters to collect arguments instead.
function legacyLogger() { return arguments.length; } const modernLogger = (...args) => args.length;No constructorsArrow functions cannot be used as constructors with the `new` keyword.
const Person = (name) => { this.name = name; }; new Person('Alex'); // throws: not a constructorNo generatorsArrow functions cannot be used as generator functions with `yield`.
function* range(n) { for (let i = 0; i < n; i++) yield i; } [...range(3)]; // [0, 1, 2]No prototypeArrow functions have no `prototype` property, since they can never act as constructors.
const Greeter = () => {}; Greeter.prototype; // undefined
JavaScript Arrow Functions
(FAQ)FAQ
Use arrow functions for short, inline callbacks like those passed to map(), filter(), or setTimeout(). Stick with regular functions when you need your own this binding, the arguments object, or when defining object methods.
Wrap the object literal in parentheses: const getUser = () => ({ name: 'Alice', age: 30 }). Without the parentheses, the curly braces are parsed as a function body instead of an object.
Arrow functions capture this from the enclosing lexical scope at definition time, not the call site. If you need this to refer to a specific object at runtime, use a regular function instead.
No, arrow functions cannot act as constructors and throw a TypeError when called with new. They also lack a prototype property, so use a regular function or a class to instantiate objects.
The concise body form () => value implicitly returns the expression with no braces or return keyword. The block body form () => { return value; } uses an explicit return. Use the block form for multiple statements.
JavaScript Arrow Functions
(In Practice)Formatting a Product List with Arrow Functions
Chain arrow functions through filter and map to build a display-ready list of in-stock products with formatted prices.
- 01
filter()keeps only products whereinStockis true, using a one-line arrow function with an implicit return. - 02
map()returns a new object literal for each product, so the object needs to be wrapped in parentheses. - 03A template literal inside the arrow function formats each price to two decimal places.
- 04The result is a fresh array — the original
productsarray is never mutated.
const products = [
{ name: 'Desk Lamp', price: 24.5, inStock: true },
{ name: 'Notebook', price: 4.99, inStock: true },
{ name: 'Stapler', price: 12, inStock: false },
];
const display = products
.filter(p => p.inStock)
.map(p => ({ label: `${p.name} — $${p.price.toFixed(2)}` }));
console.log(display);
// [{ label: 'Desk Lamp — $24.50' }, { label: 'Notebook — $4.99' }]Implicit-return arrow functions keep filter/map chains compact — just remember to wrap returned objects in parentheses.
JavaScript Date and Time
Create, format, and modify dates in JavaScript with timestamps, UTC, and common calculations.
TL;DR
- 01Create dates using
new Datewith several input options. - 02Read date parts with
getFullYear(), change them withsetFullYear(). - 03Format dates with
toISOString()for machines andtoLocaleDateString()for humans.
Tips
- 01For complex date math like time zones, recurring events, or relative time, use a library like
date-fnsorLuxon. - 02Store dates as ISO strings or timestamps in APIs and databases, converting to a
Dateobject only for display.
Warnings
- 01JavaScript months are zero-indexed, so passing
0means January and passing11means December, not month 12. - 02Date string parsing varies by browser for non-ISO formats, so prefer ISO 8601 or
new Date(year, month, day)instead.
JavaScript Date and Time
(continued)Creating Dates
new Date()The Date constructor accepts four different argument shapes for creating dates.
const now = new Date();From millisecondsPass a timestamp in milliseconds to create a date from a number.
const fromMs = new Date(1697040000000);From a stringPass a date string to parse a specific date in standard format.
const d = new Date("2025-10-11");From partsPass year, month (zero-indexed), day, and time parts to build a custom date.
const custom = new Date(2025, 9, 11, 15, 30);Checking validityDetects an invalid date by testing whether getTime() returns NaN.
Number.isNaN(d.getTime()); // true if invalid
JavaScript Date and Time
(continued)Reading Parts
getFullYear()Reads the four-digit year directly from a Date object instance.
now.getFullYear(); // 2025getMonth()Reads the month as a zero-indexed number, where zero means January.
now.getMonth(); // 9 means OctobergetDate() and getDay()getDate() returns the day of the month; getDay() returns the weekday.
now.getDate(); now.getDay(); // 0 = SundayTime componentsUse getHours(), getMinutes(), and getSeconds() to read the time components.
now.getHours(); now.getMinutes(); now.getSeconds();getTime()Reads the date as a millisecond timestamp since the Unix epoch.
now.getTime(); // ms since epoch
JavaScript Date and Time
(continued)Changing Parts
setFullYear()Every setter mutates the original Date object in place — there's no immutable version.
const date = new Date(); date.setFullYear(2026);setMonth()Changes the month in place, again starting counting from zero.
date.setMonth(0); // JanuarysetDate()Changes the day of the month, rolling over into next month.
date.setDate(1);setHours()Use extra arguments to set minutes and seconds at the same time.
date.setHours(12, 0, 0);setTime()Sets the entire date at once from a millisecond timestamp value.
date.setTime(0); // Jan 1, 1970 UTC
JavaScript Date and Time
(continued)Timestamps and UTC
Date.now()Returns the current timestamp in milliseconds since the Unix epoch.
const ts = Date.now();getUTC methodsReads times in universal coordinated time instead of the local timezone.
now.getUTCFullYear(); now.getUTCHours();Easy comparisonsTimestamps make it easy to compare or subtract two dates directly.
date1.getTime() < date2.getTime();toJSON()Runs automatically inside JSON.stringify(), which produces an ISO date string.
JSON.stringify({ d: now }); // ISO stringnew Date(ts)Convert any millisecond timestamp back into a usable Date object.
const d = new Date(1700000000000);
JavaScript Date and Time
(continued)Formatting and Math
toISOString()Formats a date for machines, always returning UTC in a sortable format.
now.toISOString(); // "2025-10-11T00:00:00.000Z"toLocaleDateString()Formats the date for the user's region for a human-readable display.
now.toLocaleDateString();Adding daysRead the current date and add to it to shift by any number of days.
const today = new Date(); today.setDate(today.getDate() + 5);Subtracting datesSubtract two dates to get the difference in milliseconds between them.
const diffMs = date1 - date2; // millisecondsConverting unitsConvert milliseconds into days or hours using simple division for clarity.
const hrs = ms / (1000 * 60 * 60);
JavaScript Date and Time
(FAQ)FAQ
JavaScript's Date constructor takes months as zero-indexed values, so January is 0 and December is 11. Always subtract 1 when passing a human-readable month number, e.g. new Date(2024, 0, 15) for January 15th.
Use Date.now() to get milliseconds since the Unix epoch. Divide by 1000 and floor the result to get seconds: Math.floor(Date.now() / 1000). You can also call new Date().getTime() for the same millisecond result.
getMonth() returns the month in local time, based on the user's system timezone. getUTCMonth() always returns the month in UTC instead. Use UTC methods when working with server timestamps or storing dates that must be timezone-independent.
Read the current day with getDate(), add your offset, then set it back with setDate(), e.g. date.setDate(date.getDate() + 7). JavaScript automatically rolls over the month and year if the value exceeds the month's length. For anything more complex, like adding months or handling DST, use date-fns or Luxon.
Use toLocaleDateString() with a locale and options object: new Date().toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' }). For a sortable ISO format use toISOString(), which always returns UTC in the format YYYY-MM-DDTHH:mm:ss.sssZ.
JavaScript Date and Time
(In Practice)Calculating Days Until a Deadline
Creates a target date, computes the whole-day difference from today, and formats both dates for display.
- 01new Date(year, month, day) builds the deadline — remember month is zero-indexed, so October is 9.
- 02Subtracting two Date objects returns the difference in milliseconds, not days.
- 03Dividing by the number of milliseconds in a day and rounding up gives a whole day count.
- 04toLocaleDateString() formats each date for display while the raw Date objects handle the math.
const today = new Date();
const deadline = new Date(2026, 9, 31); // October 31, 2026 (month is zero-indexed)
const msPerDay = 1000 * 60 * 60 * 24;
const daysLeft = Math.ceil((deadline - today) / msPerDay);
console.log(`Today: ${today.toLocaleDateString()}`);
console.log(`Deadline: ${deadline.toLocaleDateString()}`);
console.log(`${daysLeft} day(s) remaining`);Date subtraction gives milliseconds — always divide by the right unit before displaying a day count.
JavaScript Debugging Tools
Quick debugging one-liners for logging, tracing, timing, and inspecting JavaScript values in any project.
TL;DR
- 01Log values with
console.log()and inspect withconsole.dir(). - 02Pause execution with the
debuggerstatement in DevTools. - 03Measure performance with
console.time()andconsole.timeEnd()using matching labels.
Tips
- 01Use
console.table()for arrays of objects, since it shows each property as a column for fast visual scanning. - 02Use the
%cformat specifier inconsole.log()to add custom CSS styling to messages, making important output easier to spot.
Warnings
- 01Remove
debuggerstatements and console logs before shipping production code, since they slow down your app and expose data. - 02
console.log()shows a live reference to objects, so an expanded log can differ from the object's state at log time.
JavaScript Debugging Tools
(continued)Logging Basics
console.log()The obvious choice, but several other console methods solve specific logging problems faster.
console.log('user:', user);Labeled logsLabel your logs by passing a string prefix as the first argument.
console.log('typeof myVar:', typeof myVar);console.table()Displays an array of objects as a clean, scannable table instead of logs.
console.table(users);Combined valuesCombine multiple values in one log call to compare them side by side.
console.log('before:', before, 'after:', after);%o SpecifierEmbeds an inspectable object directly inside a formatted log string.
console.log('user: %o', user);
JavaScript Debugging Tools
(continued)Pausing and Tracing
debugger;A single statement pauses your entire script wherever DevTools is open.
debugger;console.trace()Prints the full call stack leading up to the current line of code.
console.trace();Conditional debuggerPauses only when a condition is true, instead of every single time.
if (i === 5) debugger; // pause onceconsole.assert()Logs a message only when the given condition turns out false.
console.assert(user.id, 'Missing user id');console.count()Counts and logs how many times a labeled line has run.
console.count('render'); // render: 1
JavaScript Debugging Tools
(continued)Checking Values
Strict equalityMost "why is this undefined" bugs resolve faster with a strict equality check.
console.log(myVar === undefined);Nullish coalescingLog a fallback when values are missing using the ?? operator.
console.log(myVar ?? 'fallback');Falsy checkDetect any falsy value with a simple negation check inside an if.
if (!myVar) console.log('Falsy!');typeofUse typeof when results seem off, to confirm the value's data type.
typeof 'hi' === 'string'; // trueNumber.isNaN()Checks for NaN without the risky type coercion of the global isNaN().
Number.isNaN(NaN); // true Number.isNaN('x'); // false
JavaScript Debugging Tools
(continued)Snapshotting Live Objects
JSON.stringify snapshotconsole.log(obj) shows the object's state when expanded, not when logged — this fixes that.
console.log(JSON.stringify(obj, null, 2));structuredClone()Takes a frozen deep copy for logging without mutating the live original.
console.log(structuredClone(state));console.dir()Renders a DOM node as an interactive property tree instead of HTML.
console.dir(document.querySelector('button'));
JavaScript Debugging Tools
(continued)Timing and Errors
console.time/timeEndA matching label is the only thing connecting console.time() to its console.timeEnd().
console.time('label'); // code here console.timeEnd('label');console.group()Nests related log messages in a collapsible group for easier scanning.
console.group('info'); // related logs... console.groupEnd();try...catchWrap risky code in a try...catch block to handle errors safely.
try { riskyFunction(); } catch (e) { console.error('Error:', e.message); }console.error()Prints errors in red with a full, clickable stack trace attached.
console.error('Failed to save:', err);console.warn()Flags non-fatal issues in yellow so they still catch your eye.
console.warn('Approaching API rate limit');
JavaScript Debugging Tools
(FAQ)FAQ
console.log() prints a string representation of a value and is best for primitives and quick output. console.dir() renders an interactive property tree instead, which is far more useful for inspecting DOM nodes or complex objects. Use console.dir() when you need to drill into nested properties.
Add the debugger statement directly in your code. When DevTools is open and execution reaches that line, it pauses automatically. From there you can step through the call stack, inspect variables, and evaluate expressions in the console.
Wrap the code with console.time('label') before it runs and console.timeEnd('label') after. The browser logs the elapsed milliseconds to the console using the matching label. This identifies which measurement the output belongs to.
console.log() captures a live reference to objects, so expanding the output later shows the current state, not the logged state. Use console.log(JSON.stringify(obj)) or console.dir() to snapshot the value at the moment of logging.
In DevTools, open the Sources panel and enable 'Pause on exceptions' (the stop-sign icon). The debugger automatically breaks at the exact line that throws. This lets you inspect the local scope and call stack without manually adding breakpoints.
JavaScript Debugging Tools
(In Practice)Debugging a Slow Data Fetch
Combines console.time, console.table, and try/catch to time a fetch call, inspect its output, and catch failures.
- 01console.time('loadUsers') starts a labeled timer right before the fetch begins.
- 02console.table(users) renders the array of user objects as a scannable table instead of a nested log.
- 03The try/catch block logs a clear error message if the fetch or parsing fails.
- 04finally guarantees console.timeEnd('loadUsers') runs and reports the elapsed time either way.
async function loadUsers() {
console.time('loadUsers');
try {
const res = await fetch('/api/users');
const users = await res.json();
console.table(users);
return users;
} catch (error) {
console.error('Failed to load users:', error.message);
return [];
} finally {
console.timeEnd('loadUsers');
}
}
loadUsers();
// logs a table of users, then "loadUsers: 42.3ms"Pair console.time/timeEnd with try/catch so you always see how long code took, even when it fails.
JavaScript Destructuring
Master object and array destructuring for cleaner variable assignment and function parameters.
TL;DR
- 01Extract object properties into variables with curly brace syntax.
- 02Extract array elements into variables with square bracket syntax.
- 03Use default values when properties or elements are missing.
Tips
- 01Use destructuring in function parameters to document what properties a function expects, making code more readable and self-documenting.
- 02Combine destructuring with rest syntax to pull out a few named values while collecting the remaining properties into one object.
- 03Rename destructured variables to avoid naming collisions when two objects in the same scope share a property name.
Warnings
- 01Destructuring doesn't create new properties on objects — it just assigns values to variables in the local scope.
- 02Destructuring a null or undefined value throws a TypeError immediately, so guard against missing data before destructuring it.
- 03Default values only apply when a property is undefined, so a falsy value like false or 0 still wins.
JavaScript Destructuring
(continued)Object Destructuring
Basic extractionExtract properties from an object into separate variables.
const user = { name: "Alice", age: 30 }; const { name, age } = user; console.log(name); // "Alice"Exact key matchProperty names must match the object keys exactly.
const { name, email } = user; // name is available, but email is undefinedExtract only what you needDestructure only the properties you need from an object.
const { name } = user; // age is not extractedRenamingUse shorter or clearer variable names with renaming.
const { name: userName, age: userAge } = user;Nested objectsDestructure nested objects by continuing the pattern.
const user = { profile: { name: "Alice" } }; const { profile: { name } } = user;
JavaScript Destructuring
(continued)Array Destructuring
Position-based extractionExtract array elements into separate variables by position.
const colors = ["red", "green", "blue"]; const [first, second, third] = colors; console.log(first); // "red"Skipping elementsSkip elements by leaving the position empty.
const [first, , third] = colors; // second is not assignedRest syntaxUse rest syntax to capture remaining elements.
const [first, ...rest] = colors; // first = "red", rest = ["green", "blue"]Nested arraysDestructure nested arrays the same way as nested objects.
const matrix = [[1, 2], [3, 4]]; const [[a, b], [c, d]] = matrix;Swapping variablesSwap variables without a temporary variable.
let x = 1, y = 2; [x, y] = [y, x]; // x = 2, y = 1
JavaScript Destructuring
(continued)Default Values
Basic defaultsProvide default values for properties that might be missing.
const { name = "Guest", email = "no-email" } = {}; console.log(name); // "Guest"Works with arrays tooDefaults work with both objects and arrays.
const [first = "a", second = "b"] = []; // first = "a", second = "b"Undefined onlyDefaults are used only if the value is undefined, not falsy.
const { count = 0 } = { count: false }; // count = false, not 0Defaults in parametersUse defaults with function parameters for required values.
function greet({ name = "Guest" } = {}) { console.log(`Hello ${name}`); } greet(); // "Hello Guest"Renaming plus defaultsCombine renaming and defaults in one destructuring expression.
const { name: userName = "Anonymous", age: userAge = 0 } = {}; console.log(userName); // "Anonymous" console.log(userAge); // 0
JavaScript Destructuring
(continued)Function Parameters
Destructured object paramsDestructure objects directly in function parameters.
function displayUser({ name, age }) { console.log(`${name} is ${age}`); } displayUser({ name: "Alice", age: 30 });Destructured array paramsDestructure arrays in function parameters the same way.
function sum([a, b]) { return a + b; } sum([1, 2]); // 3Default paramsUse default parameters together with destructuring.
function greet({ greeting = "Hello" } = {}) { console.log(greeting); } greet(); // "Hello"Self-documentingThis pattern makes function signatures self-documenting.
Shape validationDestructuring in parameters forces the caller's data to have the expected shape.
JavaScript Destructuring
(continued)Advanced Patterns
Computed property namesExtract a property using a dynamic key with computed property names.
const key = "name"; const { [key]: value } = { name: "Alice" };Collect remaining propertiesExtract named properties and collect the rest into a new object.
const { name, ...rest } = { name: "Alice", age: 30, city: "NYC" }; // rest = { age: 30, city: "NYC" }Rename plus defaultsRename multiple properties and set defaults in the same pattern.
const { name: n = "Guest", age: a = 0 } = user;Deeply nested aliasesDestructure deeply nested paths with renaming in a single expression.
const { profile: { contact: { email } } } = user;
JavaScript Destructuring
(FAQ)FAQ
Use the colon syntax: const { name: firstName } = user assigns the name property to a variable called firstName. This lets you avoid naming conflicts or clarify intent without modifying the original object.
Yes — chain the syntax: const { address: { city } } = user extracts city from a nested object. Be careful with deep nesting. It throws if an intermediate property is null or undefined.
Array destructuring uses square brackets and assigns by position, like const [first, second] = arr. Object destructuring uses curly braces and assigns by property name instead. Use array destructuring when order matters, object destructuring when keys matter.
Leave a blank space with a comma: const [, second, , fourth] = arr skips the first and third elements. Each comma advances the position without creating a variable.
This usually means there's a typo in the property name — destructuring is case-sensitive and must match the exact key. Use default values, like const { count = 0 } = obj, to guard against missing or undefined properties.
JavaScript Destructuring
(In Practice)Extracting Config from an API Response
Nested destructuring, renaming, and defaults pull exactly the fields needed from a server response in one expression.
- 01The outer pattern reaches into data, then into user and settings, without intermediate variables.
- 02name: userName renames the nested property while role = 'guest' supplies a fallback if it's missing.
- 03settings: { theme = 'light' } = {} guards against settings itself being undefined.
- 04...meta collects every top-level property not already destructured, here just status.
const response = {
status: 200,
data: {
user: { id: 7, name: 'Priya', role: 'admin' },
settings: { theme: 'dark' }
}
};
const {
data: {
user: { name: userName, role = 'guest' },
settings: { theme = 'light' } = {}
},
...meta
} = response;
console.log(userName, role, theme); // "Priya" "admin" "dark"
console.log(meta); // { status: 200 }Nested destructuring with renaming and defaults pulls exactly the fields you need in one expression.
JavaScript DOM Manipulation
Select, edit, style, and create DOM elements with vanilla JavaScript and handle user events.
TL;DR
- 01Select elements with
querySelector()andgetElementById()before reading or editing them. - 02Edit content with
textContent, and toggle styles withclassList. - 03Attach
addEventListener()to respond to clicks and other user actions.
Tips
- 01Use
textContentinstead ofinnerHTMLwhen inserting user-provided text, since it prevents HTML injection and runs faster. - 02Attach one
addEventListener()call to a parent element instead of many on children, so new elements work automatically through delegation.
Warnings
- 01Adding many elements one by one causes slow page reflows, so use a
DocumentFragmentfor batch inserts in large lists. - 02Setting
innerHTMLwith untrusted or user-supplied content opens the door to script injection, so sanitize it or usetextContentinstead.
JavaScript DOM Manipulation
(continued)Selecting Elements
getElementById()Faster than querySelector, but only matches elements by their unique ID.
const title = document.getElementById('title');querySelector()Finds the first element that matches any valid CSS selector.
const btn = document.querySelector('.btn');querySelectorAll()Gets every matching element as a static NodeList for looping.
const els = document.querySelectorAll('.item');NodeList methodsNodeLists support forEach, but use Array.from() for full array methods.
Array.from(items).map(el => el.textContent);Cache selectionsCache repeated selections in a variable to keep your code fast.
const btn = document.querySelector('.btn');
JavaScript DOM Manipulation
(continued)Editing Content
textContentEscapes everything you assign to it, unlike innerHTML, which parses markup.
title.textContent = 'New Title';innerHTMLInserts HTML markup, but only with trusted content since it executes scripts.
title.innerHTML = '<em>New Title</em>';Reading textRead element text the same way you set it, using the same properties.
console.log(title.textContent);insertAdjacentHTML()Inserts markup at a specific position without replacing existing content.
list.insertAdjacentHTML('beforeend', html);Template literalsUse template literals to build dynamic strings before assigning them.
title.innerHTML = `<em>${name}</em> logged in`;
JavaScript DOM Manipulation
(continued)Styling Elements
Inline stylesWork well for one-off changes, but classList methods scale better overall.
btn.style.backgroundColor = 'tomato';classList.add()Adds a CSS class to the element for cleaner styling control.
btn.classList.add('active');classList.toggle()Toggles a class on and off with a single method call.
btn.classList.toggle('highlight');classList.remove()Removes a class when the state changes back to normal.
btn.classList.remove('active');classList.contains()Checks whether an element currently has a given class applied.
btn.classList.contains('active');
JavaScript DOM Manipulation
(continued)Creating and Removing
createElement()Builds a new element that isn't yet attached to the DOM.
const newDiv = document.createElement('div'); newDiv.textContent = 'Hello!';appendChild() / append()Inserts the new element into the DOM; append() also accepts strings and multiple arguments.
document.body.appendChild(newDiv);remove()Removes an element from the DOM by calling remove() on it directly.
newDiv.remove();insertBefore() / prepend()Use these to control the exact position where new elements land.
list.prepend(newItem); // inserts firstDocumentFragmentBatches multiple appends into one operation, avoiding repeated page reflows.
const frag = document.createDocumentFragment(); items.forEach(i => frag.append(i)); list.append(frag);
JavaScript DOM Manipulation
(continued)Traversing the DOM
parentElementAccesses the direct parent of the current element, or null.
const parent = btn.parentElement;childrenAccesses all direct children of an element as a live HTMLCollection.
const items = list.children; // live collectionSibling propertiesMove to the next or previous sibling at the same level.
const next = el.nextElementSibling; const prev = el.previousElementSibling;closest()Finds the nearest matching ancestor starting from any element, or null.
const card = btn.closest('.card');contains()Checks whether one element is a descendant of another, anywhere in the tree.
document.body.contains(btn); // true or false
JavaScript DOM Manipulation
(FAQ)FAQ
getElementById() is slightly faster and only searches by ID. querySelector() accepts any CSS selector, like class, tag, or attribute, making it more flexible. Use getElementById() when you have an ID, querySelector() for everything else.
Use element.classList.add('myClass'), classList.remove('myClass'), or classList.toggle('myClass') for conditional toggling. Avoid directly manipulating element.className as a string, since that overwrites all existing classes.
Use document.createElement('tag') to create the element, set its properties, then append it with parentElement.appendChild(newElement) or parentElement.append(newElement). The append method also accepts plain strings and multiple arguments, making it more versatile.
Event listeners attached with addEventListener only apply to elements that exist at the time of binding, not dynamically added ones. Use event delegation instead: attach the listener to a stable parent element and check event.target inside the handler.
Use getAttribute('src') and setAttribute('src', value) to work with the raw HTML attribute string. Access the property directly, like element.src, to get the resolved, live value instead. The property is usually more convenient, but attributes are necessary for custom or non-reflected attributes like data-* or aria-*.
JavaScript DOM Manipulation
(In Practice)Adding Todo Items with Event Delegation
Creates new list items dynamically and uses one delegated click listener on the parent to handle removal for every item.
- 01addTodo() builds a new li with createElement, then append() attaches a remove button inside it.
- 02One click listener on the parent list handles every item — event delegation means new items work without extra listeners.
- 03event.target checks which element was actually clicked, since the listener fires from the parent.
- 04closest('li') walks up from the clicked button to find the row that needs to be removed.
const list = document.querySelector('#todo-list');
function addTodo(text) {
const li = document.createElement('li');
li.textContent = text;
li.classList.add('todo-item');
const removeBtn = document.createElement('button');
removeBtn.textContent = 'Remove';
removeBtn.classList.add('remove-btn');
li.append(removeBtn);
list.append(li);
}
list.addEventListener('click', (event) => {
if (event.target.classList.contains('remove-btn')) {
event.target.closest('li').remove();
}
});
addTodo('Buy groceries');
addTodo('Walk the dog');
// clicking any "Remove" button, even on future items, removes its rowOne delegated listener on a parent element handles clicks on any child, present or future, without rebinding.
JavaScript Loops
Pick the right loop in JavaScript with for, while, for-of, for-in, and array methods.
TL;DR
- 01Use
forandwhileloops for full counter control. - 02Use
for...offor array values andfor...infor object keys. - 03Use
map()andfilter()array methods for transformations.
Tips
- 01Prefer array methods like
map()andfilter()overforloops because they make your data transformations more readable. - 02Use
for...ofinstead offor...inon arrays becausefor...initerates keys as strings and inherits prototype properties.
Warnings
- 01Avoid modifying an array with
splice()orpush()while looping, since this skips elements and causes subtle index bugs. - 02Using
awaitinside aforEachcallback silently fails to pause, becauseforEachignores returned promises and runs callbacks concurrently.
JavaScript Loops
(continued)For and While
forRuns a block of code a set number of times using an initialized counter.
for (let i = 0; i < 5; i++) { console.log(i); }whileRepeatedly executes a block of code as long as a specified condition remains true.
let n = 0; while (n < 3) { console.log(n); n++; }do...whileRuns a block of code once before checking if the loop condition is true.
let i = 0; do { console.log(i); i++; } while (i < 3);break / continueUse break to exit a loop immediately and continue to skip the current iteration.
for (let i = 0; i < 5; i++) { if (i === 2) continue; if (i === 4) break; console.log(i); }
JavaScript Loops
(continued)For-of for Arrays
for...ofIterates directly over iterable values like array elements without manual index management.
const nums = [10, 20, 30]; for (const n of nums) { console.log(n); }Iterable objectsWorks natively on strings, sets, maps, and other built-in iterable structures.
for (const char of "Hi") { console.log(char); }DestructuringUnpacks properties directly within the loop declaration for cleaner object access.
const users = [{ name: "Ada" }, { name: "Bob" }]; for (const { name } of users) { console.log(name); }entries()Returns index-value pairs to retrieve the loop counter alongside the element.
const items = ["a", "b"]; for (const [index, val] of items.entries()) { console.log(index, val); }
JavaScript Loops
(continued)For-in for Objects
for...inIterates over the enumerable string property keys of an object.
const user = { name: "Ada", age: 30 }; for (const key in user) { console.log(`${key}: ${user[key]}`); }hasOwnProperty()Filters out inherited properties to iterate only the object's own direct properties.
for (const key in user) { if (user.hasOwnProperty(key)) { console.log(key); } }Object.entries()Converts object properties into key-value arrays for cleaner, modern iteration.
const user = { name: "Ada", age: 30 }; for (const [key, val] of Object.entries(user)) { console.log(`${key}: ${val}`); }
JavaScript Loops
(continued)Async Loops
for...of with awaitExecutes async tasks sequentially, pausing the loop for each promise to resolve.
for (const id of ids) { const res = await fetchItem(id); console.log(res); }forEach warningAvoid await in forEach; it ignores returned promises and fires them concurrently.
// Broken: will not wait for save to complete items.forEach(async item => { await save(item); });Promise.all()Runs all async operations in parallel by mapping items to an array of promises.
const apiCall = id => fetchItem(id); const results = await Promise.all( ids.map(apiCall) );for await...ofIterates over async iterables, waiting for each value to resolve sequentially.
for await (const chunk of readStream()) { console.log(chunk); }
JavaScript Loops
(continued)Nested and Control
Nested loopsRuns a loop inside another loop; plain break only escapes the inner loop.
for (let i = 0; i < 2; i++) { for (let j = 0; j < 3; j++) { console.log(`${i},${j}`); } }Labeled breakExits multiple levels of nesting at once by referencing a statement label.
outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (i === 1) break outer; } }Loop performanceCache array length before the loop to avoid querying it on every iteration.
const len = items.length; for (let i = 0; i < len; i++) { console.log(items[i]); }
JavaScript Loops
(FAQ)FAQ
The for...of loop iterates over iterable values like arrays, strings, Set, or Map. In contrast, for...in iterates over an object's enumerable property keys as strings. Use for...of when you want element values and for...in when you need to walk property keys.
The await keyword works correctly inside for, for...of, and while loops, running each iteration sequentially. Avoid using await inside forEach because it ignores returned promises and runs callbacks concurrently without waiting. Use a for...of loop instead.
Label the outer loop as outer:. Call break outer inside the inner loop to exit both levels at once. Alternatively, extract the nested loops into a separate function and use a return statement to exit early.
The for...in loop enumerates all inherited enumerable properties in addition to direct properties. Use Object.keys(obj) to retrieve only the object's own enumerable keys as an array. Alternatively, add an hasOwnProperty() guard check inside the loop.
Prefer map() when producing a new array of the same length with transformed values, and filter() when selecting a subset. Both express intent clearly. Reach for a for or for...of loop when you need to break early or accumulate a non-array result.
JavaScript Loops
(In Practice)Processing Shopping Cart Items
Iterates through store inventory to calculate cart totals and build order summaries while skipping out of stock items.
- 01Declare an inventory array and initialize variables to track the final total and name list.
- 02Iterate through each inventory item using a sequential
for...ofloop. - 03Skip items that are out of stock using a
continuestatement to prevent incorrect calculations. - 04Accumulate the price of in-stock items and record their names to finalize the order summary.
const items = [
{ name: "Book", price: 15, stock: 4 },
{ name: "Pen", price: 2, stock: 0 },
{ name: "Laptop", price: 800, stock: 2 }
];
let cartTotal = 0;
const orderSummary = [];
for (const item of items) {
if (item.stock === 0) continue;
cartTotal += item.price;
orderSummary.push(item.name);
}Use continue to skip invalid or out-of-stock data without breaking the entire loop execution.
JavaScript Object Manipulation
Create, clone, merge, and transform JavaScript objects with modern syntax and practical patterns.
TL;DR
- 01Create objects with literals, classes, or
Object.createfor different needs. - 02Clone shallow with spread or deep with
structuredClone. - 03Merge objects easily using spread syntax or
Object.assign.
Tips
- 01Use
structuredClone()instead ofJSON.parse(JSON.stringify(obj))to deep-clone, since it handles dates, maps, and other types correctly. - 02Use
Object.hasOwn(obj, 'key')instead ofhasOwnProperty()directly, since it works safely even on objects created withObject.create(null).
Warnings
- 01Spread and
Object.assign()only do shallow copies, so changes to nested objects in the copy will still affect the original. - 02Checking a property with truthy logic like
if (obj.key)misfires when the value is legitimately0orfalse.
JavaScript Object Manipulation
(continued)Creating Objects
Object LiteralBuilds an object directly from comma-separated key-value pairs in one line.
const user = { name: 'Ava', age: 28 };classDefines a reusable blueprint with shared methods and a constructor.
class User { constructor(name) { this.name = name; } }Object.create()Creates an object that inherits directly from a given prototype object.
const proto = { greet() { return 'Hi!'; } }; const user = Object.create(proto);Computed PropertySets a dynamic key name using bracket syntax inside an object literal.
const key = 'role'; const user = { [key]: 'admin' };
JavaScript Object Manipulation
(continued)Access and Modify
Dot NotationReads or writes a property using a fixed, known key name.
console.log(user.name); // 'Ava'Bracket NotationReads a property using a variable or a name with special characters.
console.log(user['role']); // dynamic key okOptional ChainingSafely reads a deeply nested property without throwing if it's missing.
console.log(user?.address?.city);Nullish CoalescingProvides a fallback value only when the left side is null or undefined.
console.log(user.phone ?? 'N/A');deleteRemoves a property from an object entirely, including its key.
delete user.temp; // removes the property
JavaScript Object Manipulation
(continued)Enumerate Properties
Object.keys()Returns an array of an object's own enumerable property names.
Object.keys({ a: 1, b: 2 }); // ['a', 'b']Object.values()Returns an array of just the object's own property values.
Object.values({ a: 1, b: 2 }); // [1, 2]Object.entries()Returns an array of [key, value] pairs for looping or transforms.
Object.entries({ a: 1 }); // [['a', 1]]for...inLoops over enumerable keys, including any inherited from the prototype.
for (const key in user) { console.log(key); }Object.hasOwn()Checks whether an object owns a given key, ignoring inherited ones.
Object.hasOwn(user, 'name'); // true
JavaScript Object Manipulation
(continued)Cloning
SpreadCreates a shallow copy with every enumerable own property copied over.
const copy = { ...original };Object.assign()Copies properties into a new target object; also works for merging.
const copy = Object.assign({}, original);structuredClone()Deep-clones nested objects, Dates, Maps, and Sets without shared references.
const deep = structuredClone(original);
JavaScript Object Manipulation
(continued)Merge and Transform
Spread MergeMerges objects into a new one; later keys overwrite earlier matches.
const merged = { ...base, ...extra };Object.assign(target, source)Merges source properties directly into an existing target object in place.
Object.assign(base, extra); // mutates baseObject.fromEntries()Builds an object from an array of [key, value] pairs for fast lookups.
const map = Object.fromEntries( users.map(u => [u.id, u]) );entries() + map()Converts an object back into an array to transform or filter it.
Object.entries(user).map(([k, v]) => `${k}:${v}`);
JavaScript Object Manipulation
(FAQ)FAQ
Use structuredClone(obj), which natively handles Dates, Maps, Sets, RegExp, and typed arrays. JSON.parse(JSON.stringify()) silently corrupts Dates into strings and drops undefined values entirely.
Both produce a shallow merge, but Object.assign() mutates the target in place, while spread ({...a, ...b}) returns a new object. Prefer spread to avoid modifying an existing object by accident.
Use Object.entries(obj) with for...of to destructure each pair: for (const [key, val] of Object.entries(obj)). Use Object.keys() or Object.values() when you only need one side.
Use computed property syntax directly in the literal: { [variableName]: value }. This is cleaner than creating the object first and then assigning with bracket notation afterward.
Use Object.hasOwn(obj, 'key') for own properties (ES2022), or the in operator to include inherited ones. Avoid relying on truthiness like if (obj.key), since a property can legitimately hold 0, false, or null.
JavaScript Object Manipulation
(In Practice)Merge Settings and Build a Lookup
Merges user setting overrides onto defaults, then builds an id-keyed lookup map with Object.fromEntries().
- 01Spread merges
overrideson top ofdefaults, so later keys always win. - 02
Object.fromEntries()turns theusersarray into an id-keyed lookup map. - 03Nullish coalescing (
??) supplies a fallback only when a value is missing. - 04Optional chaining (
?.) readsbyId[9]safely even though that id doesn't exist.
const defaults = { theme: 'light', notifications: true };
const overrides = { theme: 'dark', fontSize: 14 };
const settings = { ...defaults, ...overrides };
const users = [
{ id: 1, name: 'Ava', role: 'admin' },
{ id: 2, name: 'Noah', role: 'editor' },
{ id: 3, name: 'Mia', role: 'viewer' },
];
const byId = Object.fromEntries(users.map(u => [u.id, u]));
console.log(settings.theme); // 'dark'
console.log(settings.fontSize ?? 12); // 14
console.log(byId[2]?.name); // 'Noah'
console.log(byId[9]?.name ?? 'N/A'); // 'N/A'Spread later objects last so their keys win, and use Object.fromEntries() for instant id-based lookups.
JavaScript Storage
Use localStorage and sessionStorage to persist data in the browser across sessions and refreshes.
TL;DR
- 01Use
localStorageto save data that persists across browser sessions. - 02Use
sessionStoragefor temporary data cleared when the tab closes. - 03Store only strings by converting objects to
JSONfirst.
Tips
- 01Use
localStoragefor user preferences andsessionStoragefor temporary state, then sync between tabs usingstorageevents. - 02Wrap
JSON.parse()calls in atry/catchblock because corrupted or manually edited storage data throws exceptions.
Warnings
- 01Avoid storing sensitive data like passwords or tokens in
localStoragebecause it is vulnerable to cross-site scripting attacks. - 02Saving an object directly without
JSON.stringify()stores the useless string[object Object]instead of your data.
JavaScript Storage
(continued)localStorage Basics
localStorageA browser storage object that persists key-value data indefinitely with no expiration date.
localStorage.setItem("username", "Alice"); const name = localStorage.getItem("username");removeItem()Deletes a specific key-value pair from storage by providing the key name.
localStorage.removeItem("username");clear()Wipes all stored key-value data for the current domain origin at once.
localStorage.clear();in operatorChecks if a specific storage key exists in the storage object dictionary.
if ("username" in localStorage) { console.log("Key exists!"); }
JavaScript Storage
(continued)sessionStorage
sessionStorageA storage object that maintains key-value data for the duration of the page session.
sessionStorage.setItem("tabId", "12345"); const id = sessionStorage.getItem("tabId");Tab isolationMaintains separate storage instances for each open browser tab, even on the same origin.
// A new window starts a fresh storage instanceIdentical APIShares the same storage interface, methods, and behaviors as localStorage.
sessionStorage.setItem("key", "val"); sessionStorage.removeItem("key"); sessionStorage.clear();
JavaScript Storage
(continued)Storing Objects
JSON.stringify()Serializes objects or arrays into strings before saving them to browser storage.
const user = { name: "Alice", age: 30 }; localStorage.setItem("user", JSON.stringify(user));JSON.parse()Deserializes stored JSON strings back into usable JavaScript objects or arrays.
const raw = localStorage.getItem("user"); const user = JSON.parse(raw); console.log(user.name); // "Alice"Error handlingWraps JSON parsing in try-catch blocks to prevent crashes on invalid data formats.
try { const user = JSON.parse(localStorage.getItem("user")); } catch (e) { console.error("Invalid JSON stored"); }
JavaScript Storage
(continued)Storage Events
storage eventListens for storage changes on the window object across different origin tabs.
window.addEventListener("storage", e => { console.log(`${e.key} changed to ${e.newValue}`); });Cross-tab syncNotifies other open tabs on the same origin immediately when storage updates.
// Tab A writes to storage localStorage.setItem("theme", "dark"); // Tab B receives storage event automaticallyStorageEvent objectExposes modified keys, new values, old values, and the target storage area.
window.addEventListener("storage", e => { if (e.key === "theme") { applyTheme(e.newValue); } });
JavaScript Storage
(continued)Best Practices
QuotaExceededErrorCatches browser storage limit exceptions to avoid crashing applications when disk space runs out.
try { localStorage.setItem("key", largeData); } catch (e) { if (e.name === "QuotaExceededError") { console.warn("Storage limit exceeded!"); } }NamespacingPrefixes storage keys to prevent overlap conflicts with other third-party scripts.
localStorage.setItem("myApp_theme", "dark"); localStorage.setItem("myApp_lang", "en");Security warningsAvoids storing sensitive authentication tokens, passwords, or personal data in plaintext storage.
// Avoid storing JWTs in localStorage // Secure: use HTTP-only, secure cookies instead
JavaScript Storage
(FAQ)FAQ
The localStorage object persists data indefinitely across browser sessions and tabs. In contrast, sessionStorage is cleared automatically when the tab or window closes. Use localStorage for user preferences and sessionStorage for temporary form state.
Convert the object to a string using JSON.stringify() before saving it with localStorage.setItem(). Read it back by parsing the string with JSON.parse(). Skipping this serialization step mistakenly stores the string [object Object].
Listen for the storage event on the window object. This event fires in all other tabs when localStorage is updated. It provides the modified key, oldValue, and newValue to sync your frontend.
The localStorage.setItem() method throws a QuotaExceededError when storage limit is reached. This limit is typically five megabytes. Always wrap write operations in try/catch blocks to handle storage failures gracefully.
Call localStorage.removeItem('key') to delete a specific item. Alternatively, use localStorage.clear() to wipe all stored key-value pairs for the origin. Use clear() with caution as it clears data from all scripts.
JavaScript Storage
(In Practice)Managing User Preference Storage
Saves and loads user interface preferences using JSON serialization while handling storage quotas and parsing errors.
- 01Create a preferences object containing the user's selected theme and font size.
- 02Serialize the preferences object to a JSON string and store it safely in
localStorage. - 03Catch any storage quota errors that might arise if the browser storage is full.
- 04Retrieve and parse the stored JSON string back into a JavaScript object when the page loads.
- 05Fallback to default values if preferences are missing or the string contains invalid data.
function savePreferences(theme, fontSize) {
const prefs = { theme, fontSize };
try {
const serialized = JSON.stringify(prefs);
localStorage.setItem("user_prefs", serialized);
} catch (e) {
console.error("Save failed", e);
}
}
function loadPreferences() {
const raw = localStorage.getItem("user_prefs");
if (!raw) return { theme: "light", fontSize: 14 };
try {
return JSON.parse(raw);
} catch (e) {
return { theme: "light", fontSize: 14 };
}
}Always serialize objects before storing them and use try-catch to safeguard against corrupted data or full storage.
JavaScript String Methods
Master string manipulation with substring, slice, replace, split, and common text operations.
TL;DR
- 01Use
slice()andsubstring()to extract portions of strings. - 02Use
replace()andreplaceAll()to substitute text inside strings. - 03Use
split()andjoin()to convert between strings and arrays.
Tips
- 01Use
includes()instead ofindexOf() !== -1to write cleaner, more readable boolean search checks on strings. - 02Use
localeCompare()instead of comparison operators when sorting strings containing accented characters to ensure correct alphabetical ordering.
Warnings
- 01Avoid using the deprecated
substr()method because it is not supported in some modern environments and libraries. - 02Remember that JavaScript strings are immutable, meaning methods like
trim()andreplace()always return new string values.
JavaScript String Methods
(continued)Extracting Substrings
slice()Extracts a section of a string and returns it as a new string.
const text = "Hello World"; const res = text.slice(0, 5); // "Hello" const word = text.slice(-5); // "World"substring()Extracts characters between two indices, treating negative index numbers as zero.
const text = "Hello"; const res = text.substring(1, 4); // "ell"substr()Extracts a substring starting at an index for a specified character length.
const text = "Hello"; const res = text.substr(1, 3); // "ell"
JavaScript String Methods
(continued)Finding and Checking
indexOf()Returns the index of the first occurrence of a specified substring.
const text = "hello world"; text.indexOf("world"); // 6 text.indexOf("xyz"); // -1includes()Performs a case-sensitive search to determine if a substring exists.
const text = "hello world"; text.includes("world"); // truestartsWith()Checks if a string begins with the characters of a specified string.
const text = "hello"; text.startsWith("he"); // truesearch()Executes a regular expression search and returns the first matching index.
const text = "hello123"; text.search(/\d+/); // 5
JavaScript String Methods
(continued)Replace and Transform
replace()Replaces the first match of a substring or regular expression pattern.
const text = "hello world"; text.replace("world", "there"); // "hello there"replaceAll()Replaces all occurrences of a literal substring or global regex pattern.
const text = "hello hello"; text.replaceAll("hello", "hi"); // "hi hi"Case conversionTransforms all characters in a string to uppercase or lowercase forms.
const text = "Hello"; text.toUpperCase(); // "HELLO" text.toLowerCase(); // "hello"Replace callbackUses a replacement function to compute custom replacements for each match.
const text = "hello world"; const title = text.replace( /\b\w/g, ch => ch.toUpperCase() ); // "Hello World"
JavaScript String Methods
(continued)Split and Join
split()Splits a string into an array of substrings using a separator.
const text = "a,b,c"; text.split(","); // ["a", "b", "c"]join()Concatenates all elements of an array into a single string separator.
["a", "b", "c"].join(","); // "a,b,c"split limitTruncates the resulting array to a specified maximum number of elements.
const text = "a,b,c,d"; text.split(",", 2); // ["a", "b"]regex splitSplits a string using a regular expression to match multiple separators.
const text = "one, two; three"; text.split(/[,;]\s*/); // ["one", "two", "three"]
JavaScript String Methods
(continued)Trimming and Padding
trim()Removes whitespace characters from both the beginning and end of strings.
const text = " hello "; text.trim(); // "hello"trimStart()Removes whitespace characters only from the beginning of a string.
const text = " hello"; text.trimStart(); // "hello"padStart()Pads the current string from the start with a given fill character.
const text = "5"; text.padStart(3, "0"); // "005"repeat()Returns a new string containing the specified number of concatenated copies.
const text = "-"; text.repeat(10); // "----------"
JavaScript String Methods
(FAQ)FAQ
Both methods extract parts of strings using start and end indices. However, slice() supports negative indices to count from the end of the string. The substring() method treats negative values as zero. Prefer slice() for its flexibility.
Use replaceAll('old', 'new') to replace every match of a literal substring. Alternatively, use a regular expression with the global flag like replace(/pattern/g, 'new'). The standard replace() method only replaces the first occurrence.
Chain the split() and join() methods to transform the text. For example, 'hello world'.split(' ') creates an array of words. Rejoin them using .join('-') to produce the dashed string 'hello-world'.
Use padStart(targetLength, padChar) to insert characters from the left. For example, '7'.padStart(3, '0') outputs '007'. To pad from the right, use padEnd() instead.
These methods perform clean boolean checks to verify string boundaries. They return true if the string starts or ends with the target substring. Both methods accept an optional index to adjust the search boundary.
JavaScript String Methods
(In Practice)Formatting and Normalizing User Names
Cleans up messy text inputs by trimming excess whitespace and capitalizing the first letter of each name word.
- 01Trim leading and trailing whitespace from the raw user input.
- 02Split the cleaned string into an array of individual words.
- 03Map over each word to isolate and capitalize its first character.
- 04Convert the remaining characters of each word to lowercase form.
- 05Rejoin the capitalized words back into a single space-separated string.
function cleanName(rawName) {
const trimmed = rawName.trim();
if (!trimmed) return "";
const words = trimmed.split(/\s+/);
const capitalized = words.map(w => {
const first = w[0].toUpperCase();
const rest = w.slice(1).toLowerCase();
return first + rest;
});
return capitalized.join(" ");
}Chain trimming, splitting, mapping, and joining to build powerful and clean text normalization pipelines.
JavaScript Template Literals
Learn template literals, string interpolation, multiline strings, and tagged templates for cleaner code.
TL;DR
- 01Use backticks instead of quotes to declare template literals.
- 02Insert variables with
${}syntax for clean string interpolation. - 03Write multiline strings directly without escape characters or concatenation.
Tips
- 01Prefer template literals over standard string concatenation for readability when assembling strings containing multiple variables.
- 02Use a tagged template function to parse and escape user input safely when constructing HTML strings.
Warnings
- 01Remember that whitespace and indentation inside template literals are preserved, which can affect your final output layout.
- 02Avoid embedding unescaped user inputs into template literals because it introduces severe cross-site scripting vulnerabilities.
JavaScript Template Literals
(continued)Basic Syntax
backticksCreates template literals using backticks instead of single or double quotes.
const text = `Hello, world!`;Multiline stringsWrites strings across multiple lines without using string concatenation or escape characters.
const poem = `Roses are red Violets are blue`;Line preservationPreserves any newlines and indentation inside the template literal body automatically.
const raw = ` Indented Line `;
JavaScript Template Literals
(continued)String Interpolation
${} syntaxInserts variables directly into strings without using the concatenation plus operator.
const name = "Alice"; const greeting = `Hello, ${name}!`;ExpressionsEvaluates any valid JavaScript expressions inside the interpolation curly braces.
const a = 5, b = 10; const sum = `${a} + ${b} = ${a + b}`;Method callsInvokes functions or prototype methods directly inside the string interpolation.
const title = "main"; const html = `<h1>${title.toUpperCase()}</h1>`;
JavaScript Template Literals
(continued)Multiline HTML
HTML templatesBuilds complex multiline HTML string blocks with clean formatting and indentation.
const html = ` <div class="card"> <h2>${title}</h2> </div> `;trim() utilityTrims leading and trailing whitespace from the resulting multiline output string.
const html = ` <div>${content}</div> `.trim();
JavaScript Template Literals
(continued)Escaping Characters
Backtick escapeEscapes the backtick character using a backslash inside a template literal.
const text = `Use \`backticks\` inside`;Special sequencesSupports standard escape sequences like tabs and newlines within backticks.
const csv = `Name\tAge\n${name}\t${age}`;Dollar sign escapeEscapes the dollar sign to prevent it from being parsed as interpolation.
const price = `Price: \$${amount}`;
JavaScript Template Literals
(continued)Tagged Templates
Tag functionIntercepts the template string components and interpolated values using a prefix function.
function tag(strings, ...values) { return strings[0] + values[0]; } const res = tag`Hi ${name}`;Value processingInspects and sanitizes string values before returning the final formatted output.
function clean(strings, ...values) { return values.map(v => String(v).trim()); }Tagged CSSDefines styling or markup templates using tagged template literals.
const styles = css` color: ${color}; font-size: 14px; `;
JavaScript Template Literals
(FAQ)FAQ
Yes, template literals preserve actual line breaks. Pressing Enter inside backticks creates a real newline in the string. You do not need to use \n escape sequences anymore.
Place any valid JavaScript expression inside the ${} syntax. For example, you can write mathematical operations or function calls directly. The return value is coerced to a string automatically.
Escape the backtick using a backslash like ``` inside the template body. This prevents the backtick from incorrectly terminating the template string. The backslash is stripped out of the final output.
A tagged template passes the string segments and expression values to a custom function. This function controls how the string is constructed. Tagged templates are common in styling libraries or HTML sanitizers.
No, modern JavaScript engines optimize template literals and string concatenation equally well. There is no performance reason to choose string concatenation over template literals. Use template literals by default.
JavaScript Template Literals
(In Practice)Sanitizing HTML with Tagged Templates
Prevents cross-site scripting attacks by escaping raw user input within a custom tagged template function.
- 01Create an escaping utility function to substitute hazardous HTML brackets.
- 02Define a tagged template function that iterates over string parts and variables.
- 03Sanitize each interpolated value using the escape function before assembly.
- 04Reduce and concatenate the parts together to build the secure output string.
- 05Invoke the tagged template with potentially unsafe user object values.
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function safeHtml(strings, ...values) {
return strings.reduce((acc, str, i) => {
const rawVal = values[i - 1];
const safe = rawVal ? String(rawVal) : "";
const val = escapeHtml(safe);
return acc + val + str;
});
}
const user = {
name: "<script>alert(1)</script>",
role: "Admin"
};
const card = safeHtml`<div class="card">
<h3>${user.name}</h3>
<p>Role: ${user.role}</p>
</div>`;Use tagged templates to automatically sanitize user inputs, preventing cross-site scripting vulnerabilities in your HTML.
JavaScript Try Catch
Handle errors safely with try-catch patterns for async code, error propagation, global handlers, and JSON parsing.
TL;DR
- 01Wrap risky code in
tryblocks to intercept runtime errors. - 02Use
try-catchwithawaitto catch rejected promises cleanly. - 03Catch unhandled rejections globally using
windowerror event listeners.
Tips
- 01Always include a
finallyblock when managing resources like file handles or database connections to guarantee cleanup. - 02Use the
causeoption in theErrorconstructor to preserve the original traceback when wrapping error exceptions.
Warnings
- 01Avoid using bare
catchblocks that swallow errors silently, as this makes diagnosing application bugs very difficult. - 02Never wrap entire script bodies in a single
try-catchstatement because it masks syntax errors during load time.
JavaScript Try Catch
(continued)Async Error Handling
try...catchCatches errors thrown during asynchronous operations when combined with await.
async function loadUser(id) { try { const res = await fetch(`/users/${id}`); return await res.json(); } catch (err) { console.error(err.message); return null; } }Multiple awaitsGroups multiple sequential asynchronous actions into a single error handler block.
try { const user = await getUser(); const orders = await getOrders(user.id); console.log(orders); } catch (err) { console.error("Chain failed", err); }finallyGuarantees resource cleanup or UI state resets regardless of try-catch outcomes.
setLoading(true); try { await saveData(form); } catch (err) { showError(err.message); } finally { setLoading(false); }
JavaScript Try Catch
(continued)Error Propagation
throwRethrows a caught error to escalate it up the application call stack.
function processData(raw) { try { return JSON.parse(raw); } catch (err) { console.warn("Parsing failed", err); throw err; } }error causeAttaches a low-level error cause when throwing a new high-level wrapper error.
try { await db.query(sql); } catch (cause) { throw new Error("DB failed", { cause }); }cause propertyRetrieves the nested origin error object from the cause property during inspection.
try { await loadData(); } catch (err) { console.log(err.cause.message); }
JavaScript Try Catch
(continued)Global Handlers
window.onerrorListens for unhandled synchronous execution errors globally across browser page environments.
window.onerror = (msg, src, line) => { console.error(`Error: ${msg} at ${src}:${line}`); return true; // intercept };unhandledrejectionCatches any promise rejections that lack a corresponding catch block handler.
window.addEventListener("unhandledrejection", e => { console.error("Unhandled:", e.reason); e.preventDefault(); });uncaughtExceptionListens for terminal exceptions globally in Node.js process environments.
process.on("uncaughtException", err => { console.error("Fatal error occurred:", err); process.exit(1); });
JavaScript Try Catch
(continued)Safe Parsing Patterns
JSON.parse()Safeguards JSON parsing routines by catching syntax validation failures.
function parseJSON(str, fallback = null) { try { return JSON.parse(str); } catch { return fallback; } }Optional catchOmits the catch block error variable binding when the object is unused.
try { data = JSON.parse(raw); } catch { data = {}; }localStorage guardProtects localStorage reads which can throw errors in private browser modes.
function getStored(key) { try { return JSON.parse(localStorage.getItem(key)); } catch { return null; } }
JavaScript Try Catch
(continued)Inspecting Errors
error.stackProvides trace details including file locations and execution call history.
try { riskyOperation(); } catch (err) { console.error(err.stack); }cause tracingRecursively traverses a wrapped error cause chain to resolve the root error.
try { await saveOrder(order); } catch (err) { let current = err; while (current?.cause) { current = current.cause; } console.log("Root:", current.message); }AggregateErrorCollects multiple individual promise errors during collective parallel operations.
try { await Promise.any([checkA(), checkB()]); } catch (err) { err.errors.forEach(e => console.log(e.message)); }
JavaScript Try Catch
(FAQ)FAQ
Use try-catch blocks for unpredictable operations like JSON.parse() or network requests. For values you can verify beforehand using variables or properties, choose conditional null checks instead. Conditional checks are faster and cleaner.
Inspect the caught exception using the instanceof operator inside the catch block. For example, check if (err instanceof TypeError) to handle specific issues. Always rethrow unknown exceptions with throw err to prevent swallowing bugs.
Yes, the finally block is guaranteed to execute even if the try block calls return. It runs right before the function exits. This makes it perfect for resetting loading states and releasing resource handles.
Extend the built-in Error class: class ValidationError extends Error { ... }. Set this.name inside the constructor method. Extending the class ensures you maintain the correct stack trace for debugging.
Yes, wrapping an await statement inside a try-catch block catches promise rejections. This synchronous-like syntax avoids trailing .catch() chains. It simplifies error handling across multiple sequential asynchronous requests.
JavaScript Try Catch
(In Practice)Custom Network Error Wrapping
Wraps network and response parsing exceptions inside a custom error class to maintain context and track causes.
- 01Extend the standard error class to declare a custom
NetworkErrorconstructor. - 02Perform a fetch request inside a synchronous-like
tryblock. - 03Throw a high-level error if the server response status is not successful.
- 04Catch any network or parsing failure inside the
catchblock handler. - 05Rethrow a custom
NetworkErrorwrapping the original error as the cause.
class NetworkError extends Error {
constructor(msg, cause) {
super(msg, { cause });
this.name = "NetworkError";
}
}
async function fetchJson(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
} catch (err) {
throw new NetworkError("Fetch failed", err);
}
}Extend the standard Error class and utilize cause wrapping to propagate debuggable contextual exceptions safely.
JavaScript Async and Await
Handle async code cleanly with async functions, await, error handling, and parallel execution.
TL;DR
- 01Mark functions
asyncto make them return promises. - 02Use
awaitto pause and unwrap promise values. - 03Wrap awaited code in
try/catchfor error handling.
Tips
- 01Use
Promise.all()for independent async operations to run them in parallel and get faster results. - 02Wrap an async IIFE around top-level code in older environments that don't support top-level await directly.
Warnings
- 01Awaiting operations sequentially when they are independent is slower than running them together with
Promise.all(). - 02An unhandled rejection inside an async function without try/catch crashes Node.js processes by default in current versions.
JavaScript Async and Await
(continued)Async Functions
async keywordAdding async to a function changes its return contract to always be a promise.
async function fetchData() { return "data"; } fetchData().then(result => console.log(result));Wrapped return valueAn async function that returns a plain value wraps it in a resolved promise.
await inside asyncYou can use await only inside a function declared with async.
async function getData() { const response = await fetch("/api/data"); return response.json(); }Cleaner syntaxAsync functions are just a cleaner way to work with promises, nothing more.
Always a promiseEvery async function returns a promise, even one that never uses await.
JavaScript Async and Await
(continued)Await Keyword
Pause and resolveAwait pauses the function and hands back the resolved value, nothing more.
async function getUser(id) { const response = await fetch(`/api/users/${id}`); const user = await response.json(); return user; }Unwraps promisesAwait unwraps the resolved value from a promise automatically.
Scope restrictionAwait only works inside async functions or at a module's top level.
async function getMultiple() { const [a, b] = await Promise.all([ fetch("/a").then(r => r.json()), fetch("/b").then(r => r.json()) ]); return [a, b]; }Avoid sequential awaitDo not await independent operations one at a time — use Promise.all instead.
JavaScript Async and Await
(continued)Error Handling
try/catchA rejected await throws synchronously, so try/catch works like it does with normal exceptions.
async function fetchData() { try { const res = await fetch("/data"); return await res.json(); } catch (error) { console.error("Failed to fetch:", error); return null; } }Catches any awaitThe catch block runs when any awaited call inside the try block rejects.
finallyUse finally to run cleanup code regardless of success or failure.
async function withCleanup() { try { return await operation(); } finally { cleanup(); } }Throwing errorsThrow errors from async functions to propagate them as promise rejections.
Chained catchAttach .catch() to an async call when you prefer promise chaining instead.
fetchData().then(data => process(data)).catch(error => console.error("Failed:", error.message));
JavaScript Async and Await
(continued)Parallel Execution
Promise.all()Runs every promise concurrently and resolves once all of them succeed.
async function getUsers() { const [user1, user2] = await Promise.all([ fetch("/users/1").then(r => r.json()), fetch("/users/2").then(r => r.json()) ]); return [user1, user2]; }Faster than sequentialRunning promises together is much faster than awaiting each one in turn.
Promise.race()Resolves or rejects as soon as the first promise settles, whichever it is.
const fastest = await Promise.race([api1(), api2()]);Promise.allSettled()Waits for every promise to finish and reports each result, even failures.
Promise.any()Resolves as soon as the first promise succeeds, ignoring earlier rejections.
const result = await Promise.any([ fetch("/endpoint-1").then(r => r.json()), fetch("/endpoint-2").then(r => r.json()) ]); // Resolves with whichever responds first without rejecting
JavaScript Async and Await
(continued)Common Patterns
Top-level awaitES modules can await at the top level without a wrapping async function.
// In a module const config = await loadConfig();Chained workflowChain multiple async operations together, each awaiting the previous result.
async function workflow() { const data = await fetch1(); const result = await process(data); return await fetch2(result); }Async IIFEUse an async IIFE for immediate async execution without a named function.
(async () => { const data = await fetchData(); console.log(data); })();for-await-ofLoop over async iterables item by item using for-await-of.
for await (const item of asyncIterator()) { console.log(item); }
JavaScript Async and Await
(FAQ)FAQ
Declaring a function with async makes it automatically return a Promise, even if you return a plain value. This means callers can use .then() or await on it without any extra wrapping.
At the top level of ES modules, you can use top-level await directly. Inside regular scripts or non-async functions, wrap your code in an async function first. Attempting await in a non-async context throws a SyntaxError.
Wrap your awaited calls in a try/catch block. The catch block receives the rejection reason, just like a .catch() handler on a Promise chain. You can also attach .catch() directly to an awaited expression if you only need to handle one specific call.
They are functionally equivalent — async/await is syntactic sugar over Promises that makes asynchronous code read like synchronous code. Use async/await for cleaner control flow and easier debugging. Use Promise chains when composing reusable utility functions or when you prefer a functional style.
Store each async call in a variable without awaiting it immediately. Then pass the resulting Promises to Promise.all() and await that. This kicks off all operations concurrently, so total time equals the slowest operation rather than the sum of all.
JavaScript Async and Await
(In Practice)Loading a Dashboard with Parallel Requests
Combines Promise.all with async/await to fetch a user's profile and stats in parallel, with a fallback if either request fails.
- 01Promise.all() kicks off both fetch calls at the same time instead of one after another.
- 02await pauses until both promises resolve, then the responses are parsed as JSON.
- 03The try/catch block catches a failure in either request and returns a safe fallback.
- 04Total time equals the slowest request, not the sum of both requests.
async function loadDashboard(userId) {
try {
const [profileRes, statsRes] = await Promise.all([
fetch(`/api/users/${userId}`),
fetch(`/api/users/${userId}/stats`),
]);
const profile = await profileRes.json();
const stats = await statsRes.json();
return { profile, stats };
} catch (error) {
console.error('Dashboard load failed:', error);
return { profile: null, stats: null };
}
}
loadDashboard(42).then(data => console.log(data));Promise.all() plus try/catch runs independent requests in parallel while still handling failures in one place.
JavaScript Classes
Build objects with class syntax covering constructors, inheritance, static members, and private fields.
TL;DR
- 01Define object blueprints with
classandconstructorsyntax. - 02Inherit shared behavior using
extendsandsuper()calls. - 03Hide internal state with private fields marked by
#.
Tips
- 01Use private fields with a hash prefix to stop outside code from reading or changing internal state directly.
- 02Call super() before using this in a subclass constructor, since the parent must initialize the instance first.
- 03Prefer static methods for utility functions that relate to a class but don't need a specific instance.
Warnings
- 01Forgetting to call super() in a subclass constructor throws a ReferenceError before this can be accessed.
- 02Arrow function class fields capture this permanently, which can surprise developers expecting normal method binding rules.
- 03Class declarations are not hoisted like functions, so using a class before its definition throws an error.
JavaScript Classes
(continued)Class Basics
class + constructorDefine a blueprint for creating objects with shared methods.
class User { constructor(name, email) { this.name = name; this.email = email; } }new keywordCreate instances with new, which runs the constructor automatically.
const user = new User('Ana', 'ana@example.com'); console.log(user.name); // "Ana"Instance methodsDefine instance methods inside the class body without the function keyword.
class User { constructor(name) { this.name = name; } greet() { return `Hi, ${this.name}`; } }Shared prototypeMethods live on the prototype, so every instance shares one copy instead of duplicating.
const a = new User('Ana'); const b = new User('Leo'); console.log(a.greet === b.greet); // trueStrict modeClass declarations run in strict mode automatically, catching more silent bugs.
class Demo { constructor() { undeclaredVar = 1; // throws ReferenceError in strict mode } }No hoistingClasses are not hoisted the way function declarations are, so define them before use.
// new Greeter() here would throw a ReferenceError class Greeter {}
JavaScript Classes
(continued)Static Methods and Properties
static methodMark a method static to attach it to the class itself instead of each instance.
class MathHelper { static square(n) { return n * n; } } MathHelper.square(4); // 16No instance thisStatic methods cannot access instance data through this because no instance exists.
class Counter { static count = 0; constructor() { Counter.count++; } }Shared dataUse static properties to track data shared across all instances, like a running total.
new Counter(); new Counter(); console.log(Counter.count); // 2Factory methodsBuild factory methods as static functions that return configured instances.
class User { static fromJSON(json) { const data = JSON.parse(json); return new User(data.name, data.email); } }Static blocksStatic blocks let you run setup logic once when the class is first defined.
class Config { static settings; static { Config.settings = loadDefaults(); } }
JavaScript Classes
(continued)Getters, Setters, and Private Fields
getDefine a property that computes its value each time it's read.
class Circle { constructor(radius) { this.radius = radius; } get area() { return Math.PI * this.radius ** 2; } }setRun logic, like validation, whenever a property is assigned.
class Circle { set radius(value) { if (value <= 0) throw new RangeError('Radius must be positive'); this._radius = value; } }Private fields (#)Mark fields private with a leading # so they can't be read or changed outside the class.
class BankAccount { #balance = 0; deposit(amount) { this.#balance += amount; } get balance() { return this.#balance; } }Enforced privacyAccessing a private field from outside the class throws a SyntaxError, not just undefined.
const acc = new BankAccount(); acc.#balance; // SyntaxError: Private field must be declared in an enclosing classPrivate methodsPrivate methods work the same way, hiding internal logic from the public API.
class Order { #calculateTax(amount) { return amount * 0.08; } getTotal(amount) { return amount + this.#calculateTax(amount); } }
JavaScript Classes
(continued)Inheritance with Extends and Super
extendsCreate a subclass that inherits methods and properties from a parent.
class Animal { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound`; } } class Dog extends Animal {}super()Call super() inside a subclass constructor to run the parent constructor first.
class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } }Overriding methodsOverride a parent method by redefining it with the same name in the subclass.
class Dog extends Animal { speak() { return `${this.name} barks`; } }super.method()Call super.methodName() to reuse parent logic instead of duplicating it.
class Dog extends Animal { speak() { return `${super.speak()} loudly`; } }instanceofCheck whether an object inherits from a given class.
const rex = new Dog('Rex', 'Lab'); console.log(rex instanceof Animal); // true
JavaScript Classes
(continued)Classes vs Prototypes
Syntactic sugarClasses are syntactic sugar over JavaScript's existing prototype-based inheritance model.
class Point { constructor(x, y) { this.x = x; this.y = y; } } // Roughly equivalent to a constructor function + prototype assignmentMethods on prototypeA class method becomes a non-enumerable property on the constructor's prototype object.
class Point { distanceTo(other) { return Math.hypot(this.x - other.x, this.y - other.y); } } console.log(typeof Point.prototype.distanceTo); // "function"typeof a classThe typeof a class is still "function", confirming classes are functions under the hood.
console.log(typeof Point); // "function"Requires newUnlike old-style constructor functions, classes throw an error if called without new.
function OldStyle() {} OldStyle(); // works (but usually a bug) class NewStyle {} NewStyle(); // TypeError: Class constructor cannot be invoked without 'new'
JavaScript Classes
(FAQ)FAQ
A class field declares a property directly on the class body, and it runs before the constructor body executes. A constructor assignment sets the property inside the constructor function instead. Both end up creating the same instance property, but fields are often shorter for simple defaults.
Yes, classes compile down to the same prototype-based inheritance JavaScript always used. Methods defined in a class body are added to the prototype, not to each instance. Classes simply give that pattern cleaner, more familiar syntax.
An underscore prefix like _name is just a convention; outside code can still access it. A true private field written as #name is enforced by the engine. Accessing it from outside the class throws an error, so private fields offer real encapsulation, not just a hint.
Use a static method when the logic doesn't depend on a specific instance, like a factory function or a helper. Static methods are called on the class itself, such as MyClass.create(). Instance methods need this to refer to specific object data.
No, a class cannot have a field and an accessor pair with the same name; that throws a SyntaxError. Pick one approach: a plain field for simple storage, or a getter/setter pair for computed values. Use accessors when you need validation logic too.
JavaScript Classes
(In Practice)Modeling a Savings Account with Inheritance
A SavingsAccount subclass extends a base Account class, using super(), a private field, and a getter to apply interest safely.
- 01Account keeps #balance private, exposing it only through a read-only balance getter.
- 02SavingsAccount extends Account and calls super() to initialize the shared owner and balance fields.
- 03applyInterest() reads this.balance through the inherited getter, then calls the inherited deposit() method.
- 04instanceof confirms SavingsAccount still inherits from Account despite adding its own behavior.
class Account {
#balance;
constructor(owner, balance = 0) {
this.owner = owner;
this.#balance = balance;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
class SavingsAccount extends Account {
constructor(owner, balance, rate) {
super(owner, balance);
this.rate = rate;
}
applyInterest() {
const interest = this.balance * this.rate;
return this.deposit(interest);
}
}
const savings = new SavingsAccount('Priya', 1000, 0.05);
savings.applyInterest();
console.log(savings.balance); // 1050
console.log(savings instanceof Account); // trueextends plus super() lets a subclass reuse private state and methods it can't access directly.
JavaScript Error Handling
Handle errors gracefully in JavaScript using try/catch, custom error classes, and finally blocks.
TL;DR
- 01Wrap risky code in
try/catchto handle thrown errors cleanly. - 02Throw custom error classes to make
catchblocks more precise. - 03Use
finallyto run cleanup code regardless of success or failure.
Tips
- 01Create custom error classes to identify error types in catch blocks — makes branching logic far clearer than checking messages.
- 02Use finally blocks to release resources like file handles or database connections, since they run regardless of errors.
- 03Re-throw an error after logging it so calling code further up the stack still gets a chance to handle it.
Warnings
- 01Never swallow errors silently with an empty catch block — always log or handle them so bugs don't disappear.
- 02Throwing a plain string instead of an Error object loses the automatic stack trace, making bugs harder to track down.
- 03A return statement inside finally silently overrides any return or thrown error from the try or catch block above it.
JavaScript Error Handling
(continued)Try/Catch Basics
try/catchWrap risky code in a try block to intercept runtime errors.
try { const result = riskyOperation(); console.log(result); } catch (error) { console.error('Error:', error.message); }Catch only runs on errorThe catch block only runs when an error is thrown in try.
try { const data = JSON.parse('invalid'); } catch (error) { console.error('Caught:', error.message); // SyntaxError }Error object propertiesThe error object contains a message, name, and a stack trace.
catch (error) { console.log(error.name); // "SyntaxError" console.log(error.message); // "Unexpected token i" console.log(error.stack); // full trace }Execution stops at throwCode inside try after the thrown line does not execute.
try { throw new Error('stop here'); console.log('never runs'); } catch (e) { console.log(e.message); // "stop here" }Optional catch bindingOmit the catch binding if you don't need the error object.
try { mayFail(); } catch { // optional binding — no variable needed console.log('Something went wrong'); }
JavaScript Error Handling
(continued)Finally Block
Always runsRun cleanup code with finally, which always executes.
try { const file = openFile('data.txt'); processFile(file); } catch (error) { console.error('Error:', error); } finally { closeFile(); // Always runs }Runs without an errorFinally runs even when there is no error in try.
Runs before returnFinally runs even if catch re-throws or the try block returns early.
function getData() { try { return fetchData(); } finally { cleanup(); // runs before function returns } }Releasing resourcesUse finally to release resources like connections or file handles.
let connection; try { connection = openDB(); return connection.query('SELECT * FROM users'); } finally { connection?.close(); }Resetting UI stateFinally is useful for resetting loading or spinner state in UIs.
setLoading(true); try { await fetchData(); } finally { setLoading(false); // runs on success or failure }
JavaScript Error Handling
(continued)Throwing Errors
throw new Error()Throw a new Error with a descriptive message.
function divide(a, b) { if (b === 0) { throw new Error('Division by zero'); } return a / b; }Error objects, not stringsYou can throw any value, but Error objects are best practice.
// Avoid: throw 'something went wrong'; // Prefer: throw new Error('something went wrong');Built-in error typesThrow built-in error types for more specific problems.
function setAge(age) { if (typeof age !== 'number') { throw new TypeError('Age must be a number'); } if (age < 0 || age > 150) { throw new RangeError('Age out of valid range'); } }Re-throwingRe-throw errors after logging to let upstream code handle them.
try { riskyOp(); } catch (e) { logger.error(e); throw e; // propagate to caller }Throwing inside catchThrowing inside a catch block escalates the error upstream.
catch (error) { if (error instanceof SyntaxError) { throw new Error('Config file is malformed'); } }
JavaScript Error Handling
(continued)Custom Error Classes
Extending ErrorCreate custom error types by extending the built-in Error class.
class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; } }instanceof checksCheck error type with instanceof in catch blocks.
try { if (!email.includes('@')) { throw new ValidationError('Invalid email'); } } catch (error) { if (error instanceof ValidationError) { console.log('Validation error:', error.message); } }Extra propertiesAdd extra properties to custom errors for richer context.
class HttpError extends Error { constructor(status, message) { super(message); this.name = 'HttpError'; this.status = status; } } throw new HttpError(404, 'Resource not found');Multiple error classesUse multiple custom error classes to categorize problems.
class NetworkError extends Error { } class AuthError extends Error { } class NotFoundError extends Error { }Branching in catchHandle specific error types separately, letting unknown errors bubble up.
catch (error) { if (error instanceof AuthError) return redirectToLogin(); if (error instanceof NetworkError) return showRetry(); throw error; // unknown errors bubble up }
JavaScript Error Handling
(continued)Common Error Types
SyntaxErrorOccurs when code or data cannot be parsed.
try { JSON.parse('invalid json'); } catch (error) { if (error instanceof SyntaxError) { console.log('Invalid JSON format'); } }TypeErrorOccurs when a value is used with the wrong type.
try { const x = null; x.method(); // TypeError: Cannot read properties of null } catch (e) { console.log(e instanceof TypeError); // true }ReferenceErrorOccurs when a variable is not defined.
try { console.log(undeclaredVar); } catch (e) { console.log(e instanceof ReferenceError); // true }RangeErrorOccurs when a number falls outside valid bounds.
try { new Array(-1); // RangeError: Invalid array length } catch (e) { console.log(e instanceof RangeError); // true }error.nameCheck error names as a string alternative to instanceof.
catch (error) { console.log(error.name); // "TypeError", "RangeError", etc. if (error.name === 'TypeError') handleTypeError(error); }
JavaScript Error Handling
(FAQ)FAQ
Yes — wrap your await call inside a try/catch block and it will catch rejected promises just like synchronous errors. Alternatively, chain .catch() on the promise, but try/catch keeps async error handling visually consistent with synchronous code.
Yes, finally always executes before the function actually returns, even if try or catch contains a return. Be careful not to place a return inside finally itself. It overrides any return value from try or catch.
Always throw an Error object (or a subclass), never a plain string. Error objects capture a stack trace automatically, which is essential for debugging. Thrown strings produce no stack trace and are much harder to track down.
Use instanceof to branch on the error's class: if (err instanceof ValidationError) handles it differently from if (err instanceof NetworkError). This is why custom error classes are worth defining. Checking err.message with string matching is fragile and breaks as messages change.
A ReferenceError means you accessed a variable that doesn't exist in scope. A TypeError means a value exists but you're using it in an incompatible way, like calling null as a function. Recognizing which one you have narrows down the likely cause of the bug.
JavaScript Error Handling
(In Practice)Validating and Fetching User Data Safely
Combines a custom ValidationError class, try/catch/finally, and instanceof checks to handle distinct failure modes cleanly.
- 01ValidationError extends Error so it carries a stack trace and can be caught with instanceof.
- 02Invalid input throws before the fetch even starts, keeping validation separate from network errors.
- 03The catch block branches on instanceof to give validation and network failures different handling.
- 04finally always resets the loading state, whether the request succeeded, failed, or never started.
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
async function getUser(id) {
if (typeof id !== 'number') {
throw new ValidationError('id must be a number');
}
setLoading(true);
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return await res.json();
} catch (error) {
if (error instanceof ValidationError) {
console.error('Bad input:', error.message);
} else {
console.error('Fetch failed:', error.message);
}
return null;
} finally {
setLoading(false);
}
}Custom error classes plus instanceof checks let one catch block handle different failure types distinctly.
JavaScript Events
Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.
TL;DR
- 01Attach event listeners with addEventListener for flexible handling.
- 02Use event delegation to handle many items with one listener.
- 03Control event flow with stopPropagation and preventDefault when needed.
Tips
- 01Use event delegation to attach a single listener to a container instead of many listeners on child elements.
- 02Pass the { once: true } option to addEventListener when a handler should only run a single time.
- 03Check event.cancelable before calling preventDefault, since some events like scroll cannot be canceled at all.
Warnings
- 01preventDefault only works on cancelable events — always check if the event is cancelable before calling it.
- 02Inline handlers like onclick can only hold one function, so a second assignment silently replaces the first one.
- 03Forgetting removeEventListener on elements you remove from the DOM can leak memory in long-running single-page applications.
JavaScript Events
(continued)Basic Event Listeners
addEventListener()Attach event handlers for flexible, composable event handling.
const button = document.querySelector("button"); button.addEventListener("click", (event) => { console.log("Button clicked"); });The event objectContains details about what happened, like type, target, and position.
button.addEventListener("click", (e) => { console.log(e.type); // "click" console.log(e.target); // the button element console.log(e.clientX); // mouse position });removeEventListener()Removes a listener when it's no longer needed.
const handler = (e) => console.log("clicked"); button.addEventListener("click", handler); button.removeEventListener("click", handler);Avoid inline handlersInline event handlers like onclick are outdated compared to addEventListener.
// Avoid: <button onclick="handleClick()">Click</button> // Use addEventListener instead{ once: true }Fires a listener only one time, then removes it automatically.
button.addEventListener("click", handler, { once: true }); // Handler is automatically removed after first click
JavaScript Events
(continued)Event Bubbling and Capturing
Bubbling by defaultEvents bubble up from child to parent by default.
div.addEventListener("click", () => console.log("div clicked")); button.addEventListener("click", () => console.log("button clicked")); // Clicking button logs both "button clicked" and "div clicked"stopPropagation()Prevents an event from bubbling up to parent listeners.
button.addEventListener("click", (e) => { e.stopPropagation(); console.log("button only"); });Capture phasePass true as the third argument to listen during the capture phase.
div.addEventListener("click", handler, true); // Capture phase runs before bubble phaseMost events bubbleMost events bubble, but check the MDN docs for specific events that don't.
Blocking parent handlersUse stopPropagation to prevent parent handlers from running at all.
JavaScript Events
(continued)Preventing Default Behavior
preventDefault()Stops the browser's default action for an event.
form.addEventListener("submit", (e) => { e.preventDefault(); // Form does not submit to server console.log("Form submission intercepted"); });Common use casesWorks on clickable links, form submissions, and other cancelable events.
link.addEventListener("click", (e) => { e.preventDefault(); // Link does not navigate to href });event.defaultPreventedCheck whether preventDefault was already called on the event.
if (!event.defaultPrevented) { // Default action will occur }Not all events cancelableNot all events can be prevented — check if the event is cancelable first.
event.cancelableVerify the event supports preventDefault before calling it.
link.addEventListener("click", (e) => { if (e.cancelable) { e.preventDefault(); } });
JavaScript Events
(continued)Event Delegation
One listener, many childrenAttach a single listener to a parent to handle children efficiently.
const list = document.querySelector("ul"); list.addEventListener("click", (e) => { if (e.target.tagName === "LI") { console.log("Clicked item:", e.target.textContent); } });Fewer listenersThis pattern is efficient when there are many similar elements.
// Instead of attaching listeners to each item: items.forEach(item => item.addEventListener("click", handler)); // Attach one listener to the container: container.addEventListener("click", handler);e.target.closest()Finds the closest matching ancestor from the actual clicked element.
document.addEventListener("click", (e) => { const button = e.target.closest("button"); if (button) console.log("Button clicked"); });Works with dynamic elementsDelegation works with elements added to the DOM after the listener was attached.
JavaScript Events
(continued)Common Events
Mouse eventsclick, dblclick, mousedown, mouseup, and mousemove track pointer activity.
element.addEventListener("mousemove", (e) => { console.log(`Mouse at ${e.clientX}, ${e.clientY}`); });Keyboard eventskeydown and keyup track key presses; keypress is deprecated.
document.addEventListener("keydown", (e) => { console.log(`Key pressed: ${e.key}`); });Form eventschange, input, submit, reset, focus, and blur track form interaction.
input.addEventListener("input", (e) => { console.log(`Current value: ${e.target.value}`); });Window eventsload, unload, scroll, and resize track the page and viewport.
window.addEventListener("scroll", () => { console.log("Page scrolled"); });
JavaScript Events
(FAQ)FAQ
Bubbling propagates events from the target element up to the root; capturing goes from root down to the target. Pass true as the third argument to addEventListener to use capturing phase instead of the default bubbling.
Call event.stopPropagation() inside your handler to prevent the event from bubbling up. Use event.stopImmediatePropagation() if you also want to block other listeners on the same element.
Attach one listener to a parent element and use event.target to identify which child triggered the event. This is especially useful for dynamically added elements or large lists. Listeners on the parent automatically cover new children.
Not all events are cancelable — for example, scroll events cannot be prevented after they fire. Check event.cancelable before calling preventDefault(), and for scroll/touch performance consider using a passive event listener ({passive: true}).
onclick can only hold one handler at a time and overwrites any previously assigned function. addEventListener supports multiple handlers on the same element. It also gives you control over phase (capture vs bubble) and one-time execution via {once: true}.
JavaScript Events
(In Practice)Validating a Form with Delegated Listeners
One delegated submit listener validates required fields and prevents submission until every field passes.
- 01The submit listener checks every required field before the browser submits the form.
- 02e.preventDefault() only runs when validation fails, so a valid form submits normally.
- 03A single delegated input listener clears the error class as the user types, without per-field listeners.
- 04closest('[required]') confirms the input that fired the event is actually one that needs validation.
const form = document.querySelector('#signup-form');
form.addEventListener('submit', (e) => {
const invalid = [...form.querySelectorAll('[required]')].filter(field => !field.value.trim());
if (invalid.length > 0) {
e.preventDefault();
invalid.forEach(field => field.classList.add('error'));
console.log(`${invalid.length} field(s) missing`);
}
});
form.addEventListener('input', (e) => {
const field = e.target.closest('[required]');
if (field) field.classList.remove('error');
});Two delegated listeners — submit and input — validate an entire form without attaching a listener to each field.
JavaScript Fetch API
Master the Fetch API for making HTTP requests in JavaScript, including GET, POST, error handling, and working with JSON responses.
TL;DR
- 01Use
fetch()to make HTTP requests and await the response. - 02Check
response.ok, since fetch rejects only on network failure. - 03Use async/await with
try/catchfor clean, readable fetch code.
Tips
- 01In Next.js App Router, prefer the built-in extended
fetch, since it supportscacheandrevalidateoptions for data fetching. - 02Always wrap fetch calls in try/catch and check response.ok, so both network failures and HTTP error responses get handled consistently.
- 03Use an
AbortControllerto cancel in-flight requests on unmount, which prevents wasted network calls and stale state updates.
Warnings
- 01Calling
response.json()on an error response that returns HTML, like a 404 page, throws a JSON parse error. - 02Use
res.text()as a safe fallback when the content type of a response is unknown or unconfirmed. - 03Forgetting to set the
Content-Typeheader on a POST request can cause the server to misparse the JSON body.
JavaScript Fetch API
(continued)Basic GET Request
fetch()Returns a Promise that resolves to a Response object, the entry point for every request.
async function getUser(id) { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return response.json(); }response.okTrue for status codes 200-299 — always check it before trusting the response.
response.json()Parses the response body as JSON and returns another Promise.
const data = await response.json();Network-only rejectionfetch only rejects on network-level failures; a 404 or 500 still resolves successfully.
Replaces XMLHttpRequestFetch is the modern, promise-based replacement for the older XMLHttpRequest API.
JavaScript Fetch API
(continued)POST Request with JSON Body
Sending JSONSet method, headers, and body in the options object to send data.
async function createPost(data) { const response = await fetch('/api/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) throw new Error('Request failed'); return response.json(); }methodSets the HTTP verb, like 'POST', 'PUT', or 'DELETE'.
headersSets request headers, most commonly Content-Type for JSON bodies.
headers: { 'Content-Type': 'application/json' }bodyHolds the request payload, usually JSON.stringify(data) for JSON APIs.
body: JSON.stringify(data)credentialsControls whether cookies are sent, e.g. 'include' or 'same-origin'.
JavaScript Fetch API
(continued)Error Handling Patterns
Robust try/catchHandle both network errors and HTTP error responses in one function.
async function safeFetch(url, options = {}) { try { const res = await fetch(url, options); if (!res.ok) { const msg = await res.text(); throw new Error(`${res.status}: ${msg}`); } return await res.json(); } catch (err) { console.error('Fetch failed:', err); throw err; } }Network-level errorsWrap fetch in try/catch to catch network-level errors like being offline or CORS failures.
HTTP error statusCheck response.ok inside the try block to catch HTTP errors (4xx, 5xx).
Reading the error bodyRead res.text() or res.json() on error responses to get the server's error message.
const msg = await res.text();
JavaScript Fetch API
(continued)Common Fetch Patterns
Auth tokenSend a bearer token in the Authorization header.
headers: { Authorization: 'Bearer ' + token }Form dataSend a FormData body directly — no Content-Type header needed.
body: new FormData(formEl)Abort a requestCancel an in-flight request with AbortController.
const ac = new AbortController(); fetch(url, { signal: ac.signal });Read plain textRead a non-JSON response body as text.
const text = await response.text();Download a blobRead a binary response body as a Blob.
const blob = await response.blob();
JavaScript Fetch API
(continued)Fetch vs Alternatives
fetch()Best for simple requests with no extra dependencies; error handling is more verbose and there are no interceptors.
axiosBest for complex apps needing interceptors and retries; adds an external dependency of about 15 kB gzipped.
SWR / React QueryBest for data fetching in React with built-in caching; framework-specific and needs more setup.
tRPCBest for full-stack TypeScript with end-to-end types; requires a matching server setup.
JavaScript Fetch API
(FAQ)FAQ
fetch() only rejects its promise on network-level failures (e.g., no internet, DNS error). HTTP error status codes like 404 or 500 resolve successfully. You must check response.ok or response.status manually before treating the response as valid.
Set the method to 'POST', add a 'Content-Type': 'application/json' header, and pass JSON.stringify(yourData) as the body. Without the Content-Type header, many servers won't parse the payload correctly.
fetch() is built into modern browsers and Node.js 18+, making it a solid zero-dependency choice for most projects. Axios adds value if you need request/response interceptors, automatic JSON serialization, or broader legacy browser support out of the box.
Wrap your fetch call in a try/catch to handle network failures. Inside the try block, explicitly check response.ok and throw a new Error for HTTP errors. This lets your catch block handle both failure types in one place.
Yes — Next.js App Router extends the native fetch with cache and next.revalidate options. This lets you control caching and incremental static regeneration directly in server components without any additional library.
JavaScript Fetch API
(In Practice)Fetching with Timeout and Graceful Cancellation
Combines AbortController, try/catch, and a response.ok check to fetch data that gives up after a timeout instead of hanging forever.
- 01The AbortController's signal is passed to fetch, giving the request a way to be cancelled mid-flight.
- 02setTimeout calls controller.abort() if the response doesn't arrive within timeoutMs.
- 03response.ok is checked separately from the try/catch, since fetch only rejects on network failures, not HTTP error codes.
- 04clearTimeout in finally cancels the pending timer once the request settles, whether it succeeded, failed, or timed out.
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
} finally {
clearTimeout(timer);
}
}
fetchWithTimeout('/api/users').then(data => console.log(data));AbortController turns a fetch call into a cancellable operation — pair it with a timer to avoid requests that hang forever.
JavaScript Modules
Organize JavaScript code with ES6 modules using named exports, default exports, and dynamic imports.
TL;DR
- 01Export functions and variables using named or default export syntax.
- 02Import named exports using curly braces and defaults without them.
- 03Use dynamic
import()to load modules asynchronously at runtime.
Tips
- 01Use named exports for multiple utilities and reserve default exports for the primary object a module provides.
- 02Create barrel files named
index.jsto simplify deeply nested import statements across your application subfolders.
Warnings
- 01Avoid circular module dependencies where two files import each other, as this can generate unexpected
undefinedbindings. - 02Attempting to declare more than one default export in a single module throws a compile-time
SyntaxError.
JavaScript Modules
(continued)Named Exports
exportExposes functions, variables, or classes from a module under specific names.
// math.js export function add(a, b) { return a + b; } export const PI = 3.14159;import { ... }Imports specific named exports from another module file using curly brace syntax.
import { add, PI } from "./math.js"; console.log(add(5, PI));import * as namespaceBinds all named exports to a single namespace object variable.
import * as Math from "./math.js"; console.log(Math.add(5, 3)); console.log(Math.PI);Barrel re-exportRe-exports items from other modules directly without importing them locally first.
// index.js - barrel file export { add, subtract } from "./math.js"; export { formatDate } from "./date.js";
JavaScript Modules
(continued)Default Exports
export defaultExposes a single primary export value, function, or class from a module.
// logger.js export default function log(msg) { console.log(`[LOG] ${msg}`); }Default importImports a default export without using curly braces, using any local name.
import log from "./logger.js"; log("App started");Class exportExports an entire ES6 class definition as the default export of a module.
// UserService.js export default class UserService { getUser(id) { return { id }; } }
JavaScript Modules
(continued)Mixing and Re-exporting
Mixed importsImports both default and named exports within a single import statement.
import main, { helper, VERSION } from "./utils.js"; main();Default re-exportRe-exports a default export as a named export inside barrel files.
export { default as User } from "./User.js";Renamed re-exportRenames exports during the re-export process for public API clarity.
export { add as sum } from "./math.js";
JavaScript Modules
(continued)Renaming Imports
import asRenames imported values to prevent naming collisions with other local variables.
import { add as addition } from "./math.js"; addition(5, 3);Conflict resolutionAllows importing functions with identical names by mapping them to local aliases.
import { format as formatDate } from "./date.js"; import { format as formatMoney } from "./currency.js";
JavaScript Modules
(continued)Module Side Effects
Side-effect importImports a module purely for its side effects without binding any local variables.
import "./polyfills.js"; import "./analytics.js";Cached evaluationEvaluates modules only once per application lifecycle, caching subsequent imports.
import "./init.js"; // executes import "./init.js"; // loads from cachedynamic import()Loads modules dynamically and asynchronously at runtime using promise logic.
async function loadChart() { const { Chart } = await import("./chart.js"); return new Chart(); }
JavaScript Modules
(FAQ)FAQ
Use a default export for the primary component or class that a module provides. Choose named exports for utility functions or constants. This keeps imports consistent and easy to read.
Combine both exports into a single statement: import MyClass, { helperFn, CONST } from './module.js'. Place the default export before the curly braces containing the named exports.
Use the as keyword: import { render as renderList } from './list.js'. To rename default imports, simply choose a fresh local variable identifier during the import declaration.
Dynamic import() loads modules on demand at runtime and returns a Promise resolving to the module. Use it for lazy loading, route code splitting, or loading conditional scripts.
Circular dependencies cause modules to evaluate before their dependencies finish exporting. This leaves unfinished variables as undefined. Resolve this cycle by extracting shared variables to a third module.
JavaScript Modules
(In Practice)Dynamic Theme Module Loading
Loads visual style themes dynamically at runtime using asynchronous imports to minimize the initial application bundle size.
- 01Construct the dynamic path to the theme file based on user selection.
- 02Invoke the dynamic
import()function to request the module asynchronously. - 03Access the default theme export from the resolved module namespace.
- 04Call the apply method on the theme object to update user styles.
- 05Catch and report loading errors if the selected theme is not found.
export async function loadTheme(name) {
try {
const path = `./themes/${name}.js`;
const module = await import(path);
const theme = module.default;
theme.apply();
} catch (err) {
console.error(`Load failed: ${name}`, err);
}
}Utilize dynamic import() to lazy-load modules conditionally, reducing initial bundle sizes and improving page performance.
JavaScript Optional Chaining
Access nested properties safely with optional chaining and pair it with nullish coalescing for defaults.
TL;DR
- 01Access nested properties safely using the
?.operator. - 02Provide fallback values for missing properties using the
??operator. - 03Short-circuit evaluation chains immediately when any intermediate value is nullish.
Tips
- 01Combine the
?.and??operators to read nested properties and supply fallback values in one statement. - 02Utilize optional chaining with
?.()when invoking callback methods that might not exist on target objects.
Warnings
- 01Remember that optional chaining only guards against
nullandundefinedrather than other falsy values. - 02Avoid overusing the
?.operator because it can hide actual bugs by silently swallowing unexpected errors.
JavaScript Optional Chaining
(continued)Optional Chaining Basics
?.Accesses nested properties safely without throwing errors if the parent object is nullish.
const user = { profile: null }; const email = user.profile?.email; // undefinedPlain dot accessThrows a TypeError if you attempt to read properties of a missing parent.
const user = { profile: null }; // Throws: Cannot read properties of null const email = user.profile.email;Multi-level chainingChains multiple optional checks together to guard against several missing layers.
const data = {}; const city = data.user?.address?.city; // undefinedMixed chainingCombines optional chaining with standard dot access once existence is verified.
const user = { profile: { settings: { theme: "dark" } } }; const theme = user.profile?.settings.theme; // safe
JavaScript Optional Chaining
(continued)Method and Array Access
?.()Invokes an object method conditionally only if it is defined and executable.
const obj = {}; obj.greet?.(); // does nothing, no errorOptional callbacksInvokes optional callback parameters in a function safely to prevent errors.
function handleClick(onClick) { onClick?.(); // calls only if provided }?.[index]Accesses array elements or computed keys safely when the parent list is nullish.
const arr = null; const item = arr?.[0]; // undefinedDynamic objectsCombines optional brackets and dot identifiers when walking variable data shapes.
const res = data?.items?.[0]?.name;
JavaScript Optional Chaining
(continued)Nullish Coalescing
??Returns the right-hand value only when the left-hand expression is nullish.
const name = user.name ?? "Anonymous";?? versus ||Preserves falsy values like zero or empty strings, unlike standard logical OR.
const count = 0; console.log(count ?? 10); // 0 console.log(count || 10); // 10Safeguard chainCombines optional chaining and nullish fallback to supply safe defaults.
const theme = user.settings?.theme ?? "light";??=Assigns a default value to a variable only if it is currently nullish.
let config = {}; config.timeout ??= 3000; console.log(config.timeout); // 3000
JavaScript Optional Chaining
(continued)Short-Circuiting
Evaluation haltStops expression evaluation immediately when a nullish value is encountered.
let called = false; const getEmail = () => { called = true; }; const user = null; user?.getEmail(); console.log(called); // falseExpressions valueResolves the entire chained expression to undefined when short-circuited.
const val = null?.a?.b; console.log(val); // undefinedIndependent checksShort-circuits exclusively at optional chain points rather than plain dots.
const obj = { a: null }; // Throws TypeError: Cannot read property c of null const val = obj.a?.b.c;
JavaScript Optional Chaining
(FAQ)FAQ
Optional chaining verifies if the value to its left is null or undefined before accessing properties. If it is nullish, the expression short-circuits and evaluates to undefined. This prevents throwing TypeError errors.
The || operator falls back for any falsy value, including 0, empty strings, and false. The ?? operator only falls back for null or undefined. Use ?? when other falsy values are valid.
Yes, use ?.() to call a function only if it exists. Use ?.[index] to safely query array indexes or dynamic keys. Both forms resolve to undefined if the preceding value is nullish.
Yes, this behavior is known as short-circuiting. As soon as any link in the chain finds a nullish value, execution stops. The expression immediately returns undefined without executing subsequent operations.
No, you cannot use optional chaining on the left side of assignments. For example, obj?.prop = value throws a SyntaxError. The operator is strictly read-only and cannot write data.
JavaScript Optional Chaining
(In Practice)Safely Loading Configuration Settings
Extracts nested settings from an optional server configuration object, applying defaults and executing callback functions safely.
- 01Isolate the network configuration block using optional property chaining.
- 02Resolve the server host name and port value, applying default fallbacks.
- 03Retrieve the application debug flag using nullish coalescing to preserve false values.
- 04Capture the initialization callback function using optional method verification.
- 05Assemble the final config object, invoking the callback conditionally.
function getAppConfig(serverConfig) {
const net = serverConfig?.network;
const host = net?.host ?? "localhost";
const port = net?.port ?? 8080;
const debug = serverConfig?.debug ?? false;
const onReady = serverConfig?.callbacks?.onReady;
return {
connection: `${host}:${port}`,
debug,
initialize: () => onReady?.()
};
}Combine optional chaining and nullish coalescing to safely inspect dynamic objects and establish resilient default fallbacks.
JavaScript Promises
Master asynchronous operations with promises, chaining methods, async/await syntax, and parallel combinators.
TL;DR
- 01Use
Promiseinstances to manage deferred asynchronous values. - 02Chain
then(),catch(), andfinally()handlers to process values. - 03Leverage
asyncandawaitfor synchronous-looking promise code.
Tips
- 01Run independent asynchronous processes concurrently using
Promise.all()to prevent blocking call pipelines. - 02Utilize
Promise.allSettled()when you need results from all operations, including individual rejections.
Warnings
- 01Forgetting to return a value inside a
then()block breaks the promise chaining sequence. - 02A single rejected promise in
Promise.all()rejects the entire collection immediately without waiting.
JavaScript Promises
(continued)Creating Promises
new Promise()Constructs a promise wrapper by passing resolve and reject callback handles.
const p = new Promise((resolve, reject) => { if (success) resolve(data); else reject(new Error("Failed")); });Promise.resolve()Returns an already-fulfilled promise containing the supplied argument.
Promise.resolve(42) .then(val => console.log(val));Promise.reject()Returns an already-rejected promise containing the supplied error reason.
Promise.reject(new Error("Failed")) .catch(err => console.error(err));
JavaScript Promises
(continued)Handling Results
.then()Attaches a fulfillment handler to react when a promise resolves.
promise.then(result => { console.log("Resolved:", result); });.catch()Attaches a rejection handler to catch errors thrown in chains.
promise.catch(error => { console.error("Caught error:", error); });.finally()Attaches a callback that executes regardless of success or failure outcomes.
promise.finally(() => { console.log("Operation completed"); });
JavaScript Promises
(continued)Promise Chaining
Chaining .then()Passes the return value of each handler to the next link.
fetch("/api/users/1") .then(res => res.json()) .then(user => console.log(user.name)) .catch(err => console.error(err));Sequential waitingReturns a new promise inside then to pause subsequent step execution.
getUser(id) .then(user => getOrders(user.id)) .then(orders => console.log(orders));Error recoveryAttaches a catch block to supply fallback values and continue chaining.
fetch("/api/data") .catch(() => getCachedData()) .then(data => render(data));
JavaScript Promises
(continued)Combining Promises
Promise.all()Waits for all promises to resolve, rejecting instantly if any fail.
const [a, b] = await Promise.all([p1, p2]);Promise.race()Returns the result of the first promise to settle, resolving or rejecting.
const fastest = await Promise.race([p1, p2]);Promise.any()Returns the first successfully resolved promise, ignoring rejections.
const firstOk = await Promise.any([p1, p2]);Promise.allSettled()Waits for all promises to settle and returns their status array.
const results = await Promise.allSettled([p1, p2]);
JavaScript Promises
(continued)Common Patterns
Parallel executionInitiates independent promises simultaneously to speed up total execution times.
const p1 = fetchUser(); const p2 = fetchPosts(); const [user, posts] = await Promise.all([p1, p2]);Promisifying callbacksWraps a legacy callback function like setTimeout in a promise.
const delay = ms => { return new Promise(r => setTimeout(r, ms)); }; await delay(1000);
JavaScript Promises
(FAQ)FAQ
Instantiate a new Promise((resolve, reject) => { ... }) wrapper. Call the resolve callback upon success. Invoke the reject callback with an error object if the operation fails.
Each await statement blocks execution until that specific promise settles. Sequentially awaiting independent operations increases execution times. Wrap them in Promise.all() to trigger parallel execution.
Unhandled rejections can crash Node.js processes or trigger console warnings in browsers. Always attach a .catch() block. Alternatively, wrap your asynchronous expressions in try-catch structures.
Promise.race() resolves or rejects as soon as the first input promise settles. Promise.any() waits for the first successful resolution, ignoring intermediate rejections. It throws an AggregateError if all fail.
No, a forEach loop is not designed to await asynchronous callbacks. The loop completes before the async callbacks finish executing. Use a standard for...of loop to ensure sequential execution.
JavaScript Promises
(In Practice)Parallel Dashboard Resource Fetching
Loads user profiles and order histories concurrently using parallel fetch requests to minimize user interface load delay.
- 01Initiate the user fetch request without awaiting its resolution.
- 02Kick off the order fetch request simultaneously in the background.
- 03Combine both promises using
Promise.allto await their concurrent completion. - 04Parse the JSON content of both resolved responses in parallel.
- 05Catch any network or parsing error using a
try-catchwrapper.
async function getDashboardData(userId) {
try {
const userPromise = fetch(`/users/${userId}`);
const ordersPromise = fetch(`/orders/${userId}`);
const [userRes, ordersRes] = await Promise.all([
userPromise,
ordersPromise
]);
return {
user: await userRes.json(),
orders: await ordersRes.json()
};
} catch (err) {
console.error("Dashboard failed to load", err);
return null;
}
}Use Promise.all() to trigger independent requests concurrently, significantly reducing response wait times.
JavaScript Set and Map
Master collection structures using Set and Map, understand key differences, and implement dynamic data lookups.
TL;DR
- 01Use
Setto store unique value collections and eliminate duplicates. - 02Use
Mapto match key-value pairs using any key type. - 03Query
sizeand iterate directly using standardfor-ofloops.
Tips
- 01Deduplicate an array instantly by wrapping it in a
Setand spreading it back. - 02Initialize a new
Mapdirectly from objects using the staticObject.entries()conversion method.
Warnings
- 01Remember that object keys in
Mapcollections are compared using strict reference identity matches. - 02Standard
JSON.stringify()serialization does not natively supportSetorMapcollections.
JavaScript Set and Map
(continued)Set Basics
Set initializationCreates a new Set from an iterable, automatically filtering out duplicate values.
const numbers = new Set([1, 2, 2, 3]); console.log(numbers); // Set { 1, 2, 3 }add()Adds a new unique value to the Set collection and returns the Set.
const colors = new Set(); colors.add("red").add("blue");has()Checks if a specific value exists in the Set using constant time lookup.
const exists = colors.has("red"); // truedelete() and `clear()`Removes individual items or deletes all elements from the Set collection.
colors.delete("red"); colors.clear();
JavaScript Set and Map
(continued)Map Basics
Map initializationCreates a Map storing key-value pairs, maintaining insertion order of keys.
const config = new Map([ ["timeout", 5000], ["retries", 3] ]);set()Inserts or updates a value for a specific key in the Map.
const user = new Map(); user.set("name", "Alice");get()Retrieves the value associated with a specific key, returning undefined if missing.
const timeout = config.get("timeout"); // 5000has() checkVerifies if a key is present in the Map collection without reading it.
const hasKey = config.has("timeout"); // true
JavaScript Set and Map
(continued)Set vs Object
Type constraintsSet preserves variable types, while object keys are coerced to strings.
const set = new Set([1, "1"]); // Set { 1, "1" } const obj = {}; obj[1] = "num"; obj["1"] = "str"; // overrides obj[1]Unique storageSet filters duplicates natively, while objects require manual checks to prevent overwrite.
const set = new Set([5, 5, 5]); console.log(set.size); // 1
JavaScript Set and Map
(continued)Map vs Object
Key typesMap allows objects and functions as keys, whereas objects coerce keys to strings.
const map = new Map(); const keyObj = { id: 1 }; map.set(keyObj, "metadata"); console.log(map.get(keyObj)); // "metadata"Size propertiesMap counts elements directly via size, whereas objects require key array length.
const map = new Map([["a", 1]]); console.log(map.size); // 1 const obj = { a: 1 }; console.log(Object.keys(obj).length); // 1
JavaScript Set and Map
(continued)Iteration and Conversion
Set iterationLoops through Set values directly using a standard for-of loop.
const numbers = new Set([1, 2, 3]); for (const num of numbers) { console.log(num); }Map iterationDestructures entries into key-value pairs during iteration loops.
const user = new Map([["name", "Alice"]]); for (const [k, v] of user) { console.log(k, v); }Spread conversionsConverts collection elements back into standard arrays using the spread operator.
const set = new Set([1, 2]); const arr = [...set]; // [1, 2]
JavaScript Set and Map
(FAQ)FAQ
Use the has() method, which performs instant lookup check queries. This method is much faster than checking arrays with includes(). Set checks take constant time regardless of size.
Yes, you can use any value including objects and arrays as keys in a Map. These keys are matched by reference. Two separate empty objects are treated as two distinct keys.
Use the array spread operator within the Set constructor. For example, write new Set([...setA, ...setB]). This automatically merges all elements while discarding duplicate entries.
Choose Map when keys are not strings or when insertion order must be preserved. A Map is also optimized for frequent additions and removals. Plain objects work better for simple static configs.
Convert entries by spreading the collection: [...myMap]. This returns an array of key-value pairs. To get only keys or values, use [...myMap.keys()] or [...myMap.values()].
JavaScript Set and Map
(In Practice)Tracking Unique Logins and Frequencies
Processes raw login attempts to return unique user lists and trace login frequencies using Set and Map collections.
- 01Deduplicate the list of raw usernames by instantiating a
Setcollection. - 02Spread the unique set items back into a standard username array.
- 03Create a new
Mapinstance to log individual user frequency totals. - 04Iterate through usernames, retrieving previous counts or defaulting to zero.
- 05Increment and update the login total for each user in the map.
function analyzeLogins(usernames) {
const uniqueUsers = [...new Set(usernames)];
const loginCounts = new Map();
for (const user of usernames) {
const count = loginCounts.get(user) ?? 0;
loginCounts.set(user, count + 1);
}
return {
uniqueUsers,
loginCounts
};
}Use Set for instant value uniqueness checks and Map to associate dynamic values with reference keys.
JavaScript Spread and Rest
Use spread syntax to expand arrays and objects, and rest parameters to handle variable function arguments.
TL;DR
- 01Use
...to expand array elements or object properties into context. - 02Gather remaining variables or function arguments using the rest syntax.
- 03Ensure rest parameters reside at the end of argument signatures.
Tips
- 01Leverage object spread syntax to create shallow copies and merge multiple objects without mutating originals.
- 02Combine rest parameters and array destructuring to extract specific list elements and collect the rest.
Warnings
- 01Remember that object spread performs a shallow copy, leaving nested object references shared between copies.
- 02Placing a rest parameter before other parameters in a function signature throws a
SyntaxError.
JavaScript Spread and Rest
(continued)Spread with Arrays
Array mergingCombines elements of multiple arrays into a new array literal context.
const arr1 = [1, 2]; const arr2 = [3, 4]; const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]Array copyCreates a shallow copy of an array, breaking the original reference link.
const original = [1, 2, 3]; const copy = [...original];Function argumentsExpands array items into individual parameters for function execution calls.
const numbers = [5, 10, 3]; Math.max(...numbers); // 10Iterable spreadConverts strings or Sets into arrays using the spread operator.
const chars = [..."hi"]; // ["h", "i"]
JavaScript Spread and Rest
(continued)Spread with Objects
Object mergingMerges property fields of multiple objects into a new object container.
const user = { name: "Alice", age: 30 }; const updated = { ...user, active: true };Property overrideApplies new values to keys by placing overrides after the spread target.
const base = { role: "user", id: 10 }; const admin = { ...base, role: "admin" };Shallow constraintsSpreads only top-level fields, leaving nested objects pointing to shared references.
const obj = { nested: { val: 1 } }; const copy = { ...obj }; // copy.nested is shared
JavaScript Spread and Rest
(continued)Rest Parameters
Argument gatheringCollects excess function arguments into a single standard array handle.
function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); }Named plus restCombines initial named parameters with trailing rest parameter collections.
function greet(message, ...names) { console.log(`${message} ${names.join(", ")}`); }Array destructuringGathers remaining array items into a slice list during value assignment.
const [first, ...rest] = [1, 2, 3, 4]; // first = 1, rest = [2, 3, 4]Object destructuringExtracts target properties while collecting remaining fields in a separate object.
const { password, ...safeData } = user; // password is isolated, rest goes to safeData
JavaScript Spread and Rest
(continued)Spread vs Rest
Context directionSpread expands collections out, while rest collects free elements in.
const arr = [1, 2]; const spread = [...arr]; // expands elements const [...rest] = arr; // collects elementsUsage locationsSpread occurs in literals and calls; rest occurs in signatures and destructuring.
Math.min(...[1, 2]); // spread in call function test(...args) {} // rest in signature
JavaScript Spread and Rest
(FAQ)FAQ
Both features use the ... operator but behave oppositely. Spread expands iterables into separate elements in literals or calls. Rest collects multiple separate elements into a single array structure.
Construct a new object by spreading the sources: { ...obj1, ...obj2 }. If properties overlap, values on the right override properties on the left. This operation only copies own properties.
No, spread performs a shallow copy. If the source contains nested objects, their references are copied rather than duplicate objects. Use structuredClone() to perform a deep clone instead.
Declare a rest parameter: function sum(...nums) { ... }. The rest parameter must sit at the end of the argument list. This compiles arguments into a standard iterable array.
Spread only copies own enumerable properties. Non-enumerable properties and properties inherited from prototypes are bypassed. Use standard accessor methods if you need to fetch inherited properties.
JavaScript Spread and Rest
(In Practice)Immutable Shopping Cart Updates
Updates a specific shopping cart item quantity immutably using object spread syntax to ensure predictable state transitions.
- 01Map through the array of items in the cart object.
- 02Inspect each item to find the target item ID match.
- 03Spread properties of the matching item to construct a new object with updated quantity.
- 04Return unmodified items directly to preserve reference identities.
- 05Spread the root cart properties, overriding the items list and updating timestamps.
function updateItem(cart, itemId, newQty) {
const updatedItems = cart.items.map(item => {
if (item.id !== itemId) return item;
return {
...item,
quantity: newQty
};
});
return {
...cart,
items: updatedItems,
updatedAt: Date.now()
};
}Utilize object spread to perform non-mutating updates on nested state architectures, maintaining structural sharing in application data.
JavaScript Timers
Schedule code execution using setTimeout and setInterval, and rate-limit handlers using debounce and throttle patterns.
TL;DR
- 01Schedule delayed or repeating code using
setTimeoutandsetInterval. - 02Cancel pending asynchronous timer callbacks using clear methods.
- 03Rate-limit frequent event triggers with debounce and throttle functions.
Tips
- 01Always store the timer ID returned by
setTimeoutorsetIntervalto cancel execution when conditions change. - 02Prefer
requestAnimationFrameoversetIntervalwhen creating web animations to match the display refresh rate.
Warnings
- 01Remember that timer delays are minimums rather than exact guarantees due to event loop call stack blocking.
- 02Forgetting to clear an active
setIntervalloop creates memory leaks that persist throughout application life.
JavaScript Timers
(continued)setTimeout and clearTimeout
setTimeoutSchedules a single callback execution after a specified millisecond delay.
setTimeout(() => { console.log("Runs after 1 second"); }, 1000);Timer cancellationCancels a scheduled setTimeout callback before execution using its timer ID.
const id = setTimeout(() => console.log("done"), 5000); clearTimeout(id);Forwarding argumentsPasses extra arguments directly into the timer callback function handler.
setTimeout(u => console.log(u), 1000, "Alice");Zero delayQueues a callback at the end of the current execution stack immediately.
console.log("first"); setTimeout(() => console.log("third"), 0); console.log("second");
JavaScript Timers
(continued)setInterval and clearInterval
setIntervalSchedules repeated callback execution on a fixed time interval loop.
const id = setInterval(() => { console.log("tick"); }, 1000);Self-clearing intervalStops a repeating interval internally once a counter threshold is met.
let count = 0; const id = setInterval(() => { count++; if (count >= 5) clearInterval(id); }, 1000);Poller cleanupsClears active poll intervals during cleanups to prevent resource leaks.
function poll() { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // clean }Recursive timeoutChains setTimeouts recursively to ensure steady spacing between variable runs.
function poll() { doWork(); setTimeout(poll, 1000); } poll();
JavaScript Timers
(continued)Timer Precision and Event Loop
Main thread blocksTimers await thread clearance, causing late execution if synchronous blocks run.
setTimeout(() => console.log("late"), 0); while (Date.now() < start + 200) {} // blockSequential queueingLong synchronous execution blocks delay all queued callbacks concurrently.
setTimeout(() => console.log("A"), 10); setTimeout(() => console.log("B"), 20); // delayed together if thread is busyMacrotask queueingTimers run as macrotasks, resolving after synchronous code and microtasks.
console.log("1"); setTimeout(() => console.log("3"), 0); Promise.resolve().then(() => console.log("2"));
JavaScript Timers
(continued)Debounce and Throttle
Debounce helperDelays function execution, resetting the timer on each new invocation.
function debounce(fn, delay) { let id; return (...args) => { clearTimeout(id); id = setTimeout(() => fn(...args), delay); }; }Input searchLimits API fetch queries by triggering only after typing pauses.
const search = debounce(query => fetch(query), 300); input.addEventListener("input", e => { search(e.target.value); });Throttle helperExecutes a function at most once per fixed time interval window.
function throttle(fn, limit) { let wait = false; return (...args) => { if (wait) return; fn(...args); wait = true; setTimeout(() => { wait = false; }, limit); }; }Scroll trackingProtects browser scroll listeners from triggering expensive paint redraws.
const logScroll = throttle(() => updateUI(), 200); window.addEventListener("scroll", logScroll);
JavaScript Timers
(continued)requestAnimationFrame
rAF animation loopSchedules callbacks right before the browser repaints active screen elements.
function animate() { moveElement(); requestAnimationFrame(animate); } requestAnimationFrame(animate);Animation stopTerminates a requestAnimationFrame animation loop using the returned ID.
const id = requestAnimationFrame(animate); cancelAnimationFrame(id);Scroll syncSynchronizes visual adjustments directly with browser repaint refresh frames.
let ticking = false; window.addEventListener("scroll", () => { if (!ticking) { requestAnimationFrame(() => { updateScroll(); ticking = false; }); ticking = true; } });
JavaScript Timers
(FAQ)FAQ
JavaScript runs on a single thread. The event loop cannot run timer callbacks until the call stack is empty. Long synchronous tasks block the queue and delay timer execution.
Debouncing waits for a pause in events before running a function once. Throttling limits execution to at most once per fixed time interval. Use debouncing for search input and throttling for scrolling.
No, setTimeout callbacks remove themselves from the event queue automatically after running. You only need to call clearTimeout to cancel a pending timer before it executes.
The requestAnimationFrame method coordinates callbacks with display refresh rates, creating smoother motion. It also pauses automatically when browser tabs become inactive. This saves battery and processor power.
No, the browser prevents concurrent execution by waiting for the thread to clear. However, callbacks can queue up and fire rapidly in succession. Use recursive setTimeout for regular spacing instead.
JavaScript Timers
(In Practice)Search Debouncer with Resource Cleanup
Creates a search debouncer that delays query submissions and offers a cleanup method to prevent memory leaks.
- 01Establish a local variable to store the active timer identifier.
- 02Clear any pending search timeouts upon receiving new user key inputs.
- 03Schedule a new timeout handler to submit queries after three hundred milliseconds.
- 04Define a destroy function to cancel any outstanding timers during unmounting.
- 05Return the event handler and cleanup callbacks to the caller.
function createSearchInput(onSearch) {
let timerId;
function handleInput(event) {
clearTimeout(timerId);
const query = event.target.value;
timerId = setTimeout(() => {
onSearch(query);
}, 300);
}
function destroy() {
clearTimeout(timerId);
}
return { handleInput, destroy };
}Implement debouncing to rate-limit expensive api requests, and always clean up timers to prevent application memory leaks.
JavaScript Type Coercion
Understand how JavaScript converts types automatically and avoid common equality and comparison bugs.
TL;DR
- 01Compare values with strict
===to prevent implicit coercion. - 02Identify truthy and falsy variables inside conditional blocks.
- 03Convert data types explicitly using
Number,String, orBoolean.
Tips
- 01Always use strict comparison operators to prevent JavaScript from silently converting types behind your back.
- 02Convert numeric values explicitly using the standard
Number()constructor to ensure clean mathematical operations.
Warnings
- 01Remember that empty arrays and objects evaluate as truthy values inside conditional expressions.
- 02Adding numbers and strings triggers implicit string concatenation instead of expected arithmetic addition.
JavaScript Type Coercion
(continued)Loose vs Strict Equality
Strict equalityCompares values and types directly with zero implicit conversion.
console.log(1 === 1); // true console.log(1 === "1"); // falseLoose equalityConverts operand types automatically before comparing values.
console.log(1 == "1"); // true console.log(0 == false); // trueNullish checksUses loose equality to check for both null and undefined variables at once.
function isMissing(v) { return v == null; }NaN comparisonUses Number.isNaN because NaN never equals itself under comparison.
console.log(NaN === NaN); // false console.log(Number.isNaN(NaN)); // true
JavaScript Type Coercion
(continued)Truthy and Falsy Values
Falsy listLists all eight falsy values which evaluate to false in boolean contexts.
const falsy = [ false, 0, -0, 0n, "", null, undefined, NaN ];Empty collectionsVerifies that empty arrays and objects evaluate as truthy.
if ([]) console.log("runs"); // true if ({}) console.log("runs"); // trueLength verificationChecks array lengths explicitly rather than relying on list truthiness.
const list = []; if (list.length === 0) { console.log("empty"); }Double negationCoerces values into strict booleans using two logical NOT operators.
console.log(!!"text"); // true console.log(!!0); // false
JavaScript Type Coercion
(continued)Implicit Conversion
String concatenationConverts numbers to strings when executing addition with string operands.
console.log(1 + "1"); // "11" console.log("a" + 1); // "a1"Numeric coercionCoerces string values to numbers when using subtraction or division operators.
console.log("5" - 2); // 3 console.log("5" * "2"); // 10Relational operatorsConverts strings to numbers during numeric comparison evaluation checks.
console.log("10" > 5); // trueTemplate interpolationCoerces embedded variables to strings inside template literal strings.
const age = 30; console.log(`Age: ${age}`); // "Age: 30"
JavaScript Type Coercion
(continued)Explicit Conversion
Boolean()Converts any variable type into a true or false boolean value.
Boolean(""); // false Boolean("0"); // true (non-empty)Number()Parses strings or values into numbers, returning NaN on invalid characters.
Number("42"); // 42 Number("abc"); // NaNString()Converts numbers, null, or undefined values into matching literal strings.
String(42); // "42" String(null); // "null"parseInt()Parses numbers from strings with trailing non-numeric characters.
parseInt("42px", 10); // 42 Number("42px"); // NaN
JavaScript Type Coercion
(FAQ)FAQ
Loose equality == converts operand types before comparison. Strict equality === compares values and types directly. Strict equality is recommended to avoid unpredictable conversion bugs.
Loose equality coerces both operands toward numbers. The empty array converts to an empty string, then to zero. The boolean false also becomes zero. Since both evaluate to zero, they match.
Falsy values are false, 0, -0, 0n, empty strings, null, undefined, and NaN. All other values are truthy. This includes empty arrays, empty objects, and the string '0'.
The plus operator triggers string concatenation if either operand is a string. JavaScript converts the number to a string and links them. Use subtraction or explicit conversion to do math.
Do not use equality operators, because NaN never equals anything, including itself. Use the Number.isNaN() method instead. This method correctly determines if a value is not a number.
JavaScript Type Coercion
(In Practice)Validating Numeric Form Input
Converts raw form inputs explicitly to process user ages, avoiding implicit coercion bugs and checking for NaN failures.
- 01Convert the input value to a string explicitly and trim surrounding whitespace.
- 02Verify that the cleaned string is not empty before parsing.
- 03Coerce the string to a number using the explicit
Numberconstructor. - 04Check if the resulting number is
NaNto detect invalid input characters. - 05Return the verified number or a fallback null value.
function processAgeInput(inputValue) {
const cleanString = String(inputValue).trim();
if (!cleanString) return null;
const parsedNumber = Number(cleanString);
if (Number.isNaN(parsedNumber)) return null;
return parsedNumber;
}Always use explicit conversion methods and verify values with Number.isNaN() to prevent numeric comparison failures.
JavaScript Async Iterators
Learn async iterators, async generators, and for await...of for consuming asynchronous data lazily.
TL;DR
- 01Yield promises that resolve to
{ value, done }via async iterators. - 02Build async generators with
async function*toyieldvalues lazily. - 03Consume async iterables with
for await...ofas values arrive.
Tips
- 01Use async generators to wrap paginated APIs, so callers loop over pages without managing cursors manually.
- 02Prefer
for await...ofover manually callingnext()when consuming streams — it awaits values and cleans up automatically.
Warnings
- 01Awaiting each
next()call sequentially means items resolve one at a time, not all at once. - 02Forgetting that
for await...ofalso works on plain, synchronous iterables can confuse debugging of sequential async behavior.
JavaScript Async Iterators
(continued)What Async Iterators Are
Symbol.asyncIterator`next()` returns a Promise resolving to `{ value, done }`, not a plain object.
const asyncIt = { [Symbol.asyncIterator]() { let i = 0; return { next: () => Promise.resolve( { value: i++, done: i > 3 } ) }; } };Class-based iterableA class implements async iteration by returning `this` from `[Symbol.asyncIterator]()`.
class Logs { constructor(lines) { this.i = 0; this.lines = lines; } [Symbol.asyncIterator]() { return this; } next() { const done = this.i >= this.lines.length; const value = this.lines[this.i++]; return Promise.resolve({ value, done }); } }Dual protocolsAn object can implement `Symbol.iterator` and `Symbol.asyncIterator` for two iteration modes.
const range = { [Symbol.iterator]() { let i = 0; return { next: () => ({ value: i++, done: i > 2 }) }; }, [Symbol.asyncIterator]() { let i = 0; return { next: () => Promise.resolve( { value: i++, done: i > 2 } ) }; } };Manual next() callsCall `next()` directly and `await` each promise to drive iteration by hand.
async function drain(it) { let result = await it.next(); while (!result.done) { console.log(result.value); result = await it.next(); } }
JavaScript Async Iterators
(continued)Async Generator Functions
async function*Declare an async generator by combining `await` and `yield` in one body.
async function* fetchPages(url) { let next = url; while (next) { const res = await fetch(next); const page = await res.json(); yield page.items; next = page.nextUrl; } }yield pausesEach `yield` suspends the generator until the caller requests the next value.
async function* ticker() { console.log('start'); yield 1; console.log('resumed'); yield 2; } const t = ticker(); await t.next(); // logs 'start' await t.next(); // logs 'resumed'Returns an iterableCalling an async generator returns an async iterable right away; the body waits.
async function* slow() { console.log('running'); yield 1; } const gen = slow(); // logs nothing yet await gen.next(); // now logs 'running'Error propagationAn error thrown inside the generator rejects the promise `next()` returns.
async function* risky() { yield 1; throw new Error('boom'); } try { for await (const v of risky()) { console.log(v); } } catch (e) { console.log(e.message); // 'boom' }
JavaScript Async Iterators
(continued)The for await...of Loop
for await...ofConsume an async iterable with `for await...of`, awaiting each value automatically.
async function run() { for await (const items of fetchPages('/api/items')) { console.log(items); } }Waits per iterationThe loop body only runs once the currently yielded promise resolves.
async function* slowNums() { yield 1; await new Promise(r => setTimeout(r, 100)); yield 2; } for await (const n of slowNums()) { console.log(Date.now(), n); }Scope restriction`for await...of` only works inside an async function or a module's top level.
async function readAll(stream) { for await (const chunk of stream) { process(chunk); } }Accepts sync tooIt also accepts plain, synchronous iterables, awaiting each value for consistency.
for await (const n of [1, 2, 3]) { console.log(n); } // logs: 1 2 3Cleanup on exit`break` or `return` inside the loop runs the generator's `finally` block for cleanup.
async function* withCleanup() { try { yield 1; yield 2; } finally { console.log('cleanup'); } } for await (const n of withCleanup()) { if (n === 1) break; } // logs 'cleanup' after break
JavaScript Async Iterators
(continued)Consuming Streams and Paginated APIs
Wrap the endpointWrap a paginated endpoint in an async generator so callers never see cursor logic.
async function* paginate(fetchPage) { let cursor = null; do { const { items, nextCursor } = await fetchPage(cursor); yield* items; cursor = nextCursor; } while (cursor); }yield* delegation`yield*` unpacks an iterable and emits each item, instead of one array.
async function* asPage() { yield [1, 2, 3]; } async function* asItems() { yield* [1, 2, 3]; } // asPage yields one array // asItems yields 1, then 2, then 3Lazy evaluationPages fetch only when consumed; `break` stops further fetches and keeps memory flat.
let pagesFetched = 0; async function* lazyPages() { while (true) { pagesFetched++; yield pagesFetched; } } for await (const page of lazyPages()) { if (page === 2) break; } console.log(pagesFetched); // 2, not moreNative stream supportNode.js Readable streams implement `Symbol.asyncIterator` natively, so they work here.
for await (const chunk of fs.createReadStream('file.txt')) { console.log(chunk.length); }
JavaScript Async Iterators
(continued)Async vs Sync Iteration
Sync vs async generatorA sync generator yields values directly; an async one yields promises that resolve.
function* syncGen() { yield 1; yield 2; } async function* asyncGen() { yield 1; yield 2; }Matching loopPlain `for...of` cannot read an async iterable; it needs `for await...of` to unwrap values.
for (const x of asyncGen()) {} // TypeError: not a function or its // return value is not iterable for await (const x of asyncGen()) { console.log(x); // 1, then 2 }Spread limitationSpread syntax (`...`) only works with sync iterables; it cannot await an async one.
console.log([...asyncGen()]); // TypeError: not a function or its // return value is not iterableawait alone isn't enoughAdding `await` inside a plain `function*` is a syntax error; use `async function*`.
function* broken() { yield 1; await Promise.resolve(2); // SyntaxError: await is only // valid in async functions }Converting sync to asyncWrap a sync iterable in an async generator, awaiting each value as it's yielded.
async function* toAsync(iterable) { for (const value of iterable) { yield await Promise.resolve(value); } }
JavaScript Async Iterators
(FAQ)FAQ
Symbol.iterator defines a synchronous iterator whose next() returns {value, done} directly. Symbol.asyncIterator defines an async iterator whose next() returns a Promise that resolves to {value, done}. Use the async version whenever producing a value requires waiting, like a network request.
Combine the async and function* keywords into async function*. Inside it, use await for asynchronous work and yield to emit values. Calling it returns an async iterable you can loop over with for await...of.
Use for await...of when looping over an async iterable, such as an async generator or a stream of paginated results. It automatically awaits each yielded promise before running the loop body. A regular for...of loop cannot await values produced asynchronously.
Yes — for await...of works with any iterable, sync or async, and awaits each value automatically. Given an array of promises, it awaits each one in order before continuing. This makes it useful for processing a fixed list of pending requests sequentially.
An async generator can fetch one page, yield its items, then fetch the next page only when asked. This keeps memory usage low since pages load lazily instead of all at once. Callers just loop with for await...of and never see the pagination logic.
JavaScript Async Iterators
(In Practice)Streaming Paginated Search Results
An async generator lazily fetches pages of search results, and for await...of stops as soon as a match is found.
- 01fetchResults is an async generator that fetches one page of results at a time.
- 02yield* items emits each item individually instead of yielding whole page arrays.
- 03for await...of automatically awaits each yielded item before running the loop body.
- 04Returning early from the loop stops fetching further pages the caller no longer needs.
async function* fetchResults(query) {
let cursor = null;
do {
const res = await fetch(`/api/search?q=${query}&cursor=${cursor ?? ''}`);
const { items, nextCursor } = await res.json();
yield* items;
cursor = nextCursor;
} while (cursor);
}
async function findFirstMatch(query, predicate) {
for await (const item of fetchResults(query)) {
if (predicate(item)) return item;
}
return null;
}
const match = await findFirstMatch('laptop', item => item.price < 500);
console.log(match);
// stops fetching pages as soon as a match is foundAsync generators plus for await...of stream results lazily — you only fetch as many pages as you actually need.
JavaScript Call Apply Bind
Control the this keyword explicitly using call, apply, and bind on any function.
TL;DR
- 01
call()andapply()invoke a function with a chosenthisvalue. - 02
apply()takes arguments as an array;call()takes a list. - 03
bind()returns a new function withthispermanently fixed.
Tips
- 01Use bind() when passing a method as a callback or event handler, so this stays correct.
- 02Reach for apply() when arguments already exist as an array, such as forwarding arguments between wrapper functions.
Warnings
- 01Calling bind() repeatedly on the same function creates a new wrapper each time, which breaks reference equality checks like removeEventListener.
- 02Arrow functions ignore call, apply, and bind for this, since arrow functions always inherit this from their enclosing scope.
JavaScript Call Apply Bind
(continued)Why this Needs Explicit Control
Call site mattersThe value of this depends on how a function is called, not where it's defined.
const user = { name: 'Ada', greet() { return `Hi, ${this.name}`; } }; const fn = user.greet; fn(); // 'Hi, undefined' — this lost its connection to userDetached methodsPassing a method as a value, like a callback, detaches it from its original object.
Explicit controlcall, apply, and bind exist to set this explicitly regardless of call site.
Function.prototypeAll three live on Function.prototype, so every function has access to them.
Common breakageWithout explicit control, callbacks and event handlers commonly break on this.
Legacy codeUnderstanding these three methods is essential for working with older, non-arrow-function code.
JavaScript Call Apply Bind
(continued)Using call()
call(thisArg, ...args)Invoke a function immediately with arguments listed individually.
function greet(greeting) { return `${greeting}, ${this.name}`; } greet.call({ name: 'Ada' }, 'Hi'); // 'Hi, Ada'First argument is thisThe first argument becomes this inside the function for that one call.
Positional argumentsRemaining arguments map positionally to the function's parameters.
Borrowing methodsUse call() to borrow a method from one object and run it against another.
const max = Math.max.call(null, 1, 5, 3); // 5null or undefinedPassing null or undefined as thisArg uses the global object in non-strict mode.
Single invocation onlycall() does not change the original function; it only affects that single invocation.
JavaScript Call Apply Bind
(continued)Using apply()
apply(thisArg, argsArray)Works exactly like call(), but arguments are passed as one array.
function sum(a, b, c) { return a + b + c; } sum.apply(null, [1, 2, 3]); // 6Array-like argumentsapply() is the better choice when arguments already exist as an array or array-like.
Math.max.apply(null, [4, 8, 2]); // 8Spread replaces itModern code often replaces apply() with the spread operator: Math.max(...nums).
Forwarding argumentsapply() still matters when forwarding an arguments object between functions.
Synchronous returnBoth call() and apply() execute the function synchronously and return its result.
Choosing between themChoosing between call and apply is purely about argument shape — list versus array.
JavaScript Call Apply Bind
(continued)Using bind()
bind(thisArg)Creates a new function with this permanently fixed to the given value.
const user = { name: 'Ada', greet() { return `Hi, ${this.name}`; } }; const boundGreet = user.greet.bind(user); boundGreet(); // 'Hi, Ada' — works even detached from userDoesn't invokeUnlike call() and apply(), bind() does not invoke the function immediately.
Store for laterThe returned function can be stored, passed around, and called later safely.
Bind in constructorsBind methods in a constructor so they keep this when used as callbacks.
class Button { constructor() { this.onClick = this.onClick.bind(this); } onClick() { console.log(this); } }Fixed permanentlyCalling bind() again on an already-bound function cannot change its fixed this.
Bound function namesBound functions report 'bound functionName' when inspected, which helps when debugging.
JavaScript Call Apply Bind
(continued)Partial Application with bind
Prepended argumentsArguments passed to bind() after thisArg get permanently prepended to every future call.
function multiply(a, b) { return a * b; } const double = multiply.bind(null, 2); double(5); // 10Partial applicationThis technique is called partial application, fixing some arguments ahead of time.
Event handler contextCombine partial application with event handlers to pass extra context cleanly.
button.addEventListener('click', handleClick.bind(null, itemId));Extra arguments still workPartially applied functions still accept additional arguments at call time.
Specialized utilitiesUse partial application to build specialized utility functions from general ones.
Avoids wrappersThis pattern avoids writing repetitive wrapper functions for common argument combinations.
JavaScript Call Apply Bind
(FAQ)FAQ
Both invoke a function immediately with a specified this value. call() takes the function's arguments individually, separated by commas. apply() takes them bundled into a single array. They behave identically once the arguments are in place.
call() and apply() invoke the function right away. bind() does not call the function; it returns a new function with this permanently set. You call that returned function later, optionally with more arguments.
When a method is passed as a callback, like onClick={this.handleClick}, it loses its connection to the instance. Calling it later sets this to undefined or the global object instead of the instance. Binding it in the constructor, or using an arrow function class field, fixes this permanently.
Yes, this is called partial application. Any arguments passed to bind() after the this value get permanently prepended to future calls. For example, multiply.bind(null, 2) returns a function that always doubles its input.
Calling bind() on an arrow function has no effect on this, because arrow functions never have their own this. You can still bind arguments for partial application, but the this value stays whatever it was lexically. Use a regular function if you need bind() to control this.
JavaScript Call Apply Bind
(In Practice)Formatting Log Messages with call() and bind()
call() borrows a shared formatter for different log entries, while bind() creates reusable, prefixed logger functions.
- 01format() reads this.level and this.message, so call() supplies a different object as this each time.
- 02Passing 'API' as the second call() argument fills the prefix parameter for that one invocation.
- 03log.bind(null, 'API') permanently fixes the prefix argument, returning a reusable specialized function.
- 04apiLog can still be called later with the remaining level and message arguments.
function format(prefix) {
return `[${prefix}] ${this.level}: ${this.message}`;
}
const errorEntry = { level: 'ERROR', message: 'Connection lost' };
const infoEntry = { level: 'INFO', message: 'Server started' };
console.log(format.call(errorEntry, 'API')); // '[API] ERROR: Connection lost'
console.log(format.call(infoEntry, 'API')); // '[API] INFO: Server started'
function log(prefix, level, message) {
console.log(`[${prefix}] ${level}: ${message}`);
}
const apiLog = log.bind(null, 'API');
apiLog('WARN', 'Rate limit approaching');
// '[API] WARN: Rate limit approaching'call() sets this for a single invocation, while bind() locks in this and arguments for reuse.
JavaScript Closures
Understand how closures allow inner functions to retain access to variables from parent scopes with examples.
TL;DR
- 01Ensure inner functions retain access to their defining parent scopes.
- 02Store persistent private data state safely without using global variables.
- 03Resolve outer variables based on where functions are statically defined.
Tips
- 01Expose public API methods while keeping raw state hidden inside an enclosing closure scope function.
- 02Choose closures over standard class definitions when you only need to store small private states.
Warnings
- 01Avoid creating unnecessary closures enclosing huge objects because they can generate substantial memory leaks.
- 02Declare loop indexes using
letso that each iteration receives its own distinct variable binding.
JavaScript Closures
(continued)What Closures Are
Closure definitionKeeps reference access to outer scope variables even after parent execution finishes.
function outer() { let n = 0; return () => ++n; } const count = outer(); count(); // 1Scope nestingForms closures automatically whenever you nest child functions inside parent contexts.
function parent() { const x = 1; function child() { return x; } }Memory persistenceRetains outer scope values in memory as long as the child function exists.
const fn = outer(); // n stays in memoryLexical scopeResolves variable scopes statically based on where the functions are declared.
const x = 10; function test() { console.log(x); }
JavaScript Closures
(continued)Closures and Loops
var in loopShares a single variable reference across all loop callbacks, causing bugs.
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i)); } // logs 3, 3, 3let in loopCreates a new variable binding block per loop iteration to fix sharing.
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i)); } // logs 0, 1, 2IIFE captureCaps variable values per iteration loop by wrapping functions in IIFE scopes.
for (var i = 0; i < 3; i++) { (v => setTimeout(() => console.log(v)))(i); }
JavaScript Closures
(continued)Private Variables
State encapsulationStores internal variable values safely away from the global execution context.
function createCounter() { let count = 0; return { increment: () => ++count, get: () => count }; }Public API accessExposes interface methods to read and write private variables under control.
const c = createCounter(); c.increment(); console.log(c.get()); // 1Accidental mutationPrevents external script scripts from corrupting or writing internal state values directly.
let c = createCounter(); // c.count is undefined
JavaScript Closures
(continued)Function Factories
Behavior configurationCreates functions sharing standard behaviors but retaining distinct internal configurations.
function makeAdder(x) { return y => x + y; } const add5 = makeAdder(5); add5(10); // 15Private memoizationCloses over a private Map cache to return cached function outputs.
function memoize(fn) { const cache = new Map(); return x => { if (cache.has(x)) return cache.get(x); const res = fn(x); cache.set(x, res); return res; }; }
JavaScript Closures
(FAQ)FAQ
Closures inside a var loop share one variable reference. The loop terminates before the callbacks execute. Change the declaration to let to bind a fresh variable index per iteration.
Declare local variables inside a parent function and return helper functions accessing them. The returned helpers close over the state. External operations cannot inspect or alter this private state directly.
A function becomes a closure when it references variables outside its scope after the parent context exits. The closure keeps these external variables alive in memory. Normal functions only use parameters.
A factory function accepts configuration values and returns specialized functions enclosing those values. For example, makeAdder(5) returns a helper that always adds five. This keeps logic parameterized and clean.
Avoid capturing large object references that you do not need. Destructure only specific values required by the inner function. Nullify large variable handles once they are no longer needed.
JavaScript Closures
(In Practice)Debouncing Input with Closures
A debounce factory function closes over a timer reference to ensure that rapid handlers only execute once typing pauses.
- 01Declare a local variable to hold the active timeout identifier.
- 02Return a closure function that accepts arguments and intercepts calls.
- 03Clear any existing scheduled timeout to cancel the previous call.
- 04Schedule a new timeout to execute the target function after a delay.
- 05Forward the function arguments to the target handler on execution.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}The closure over the timer variable keeps it alive between calls without polluting the global variable namespace.
JavaScript Currying and Composition
Transform multi-argument functions into chained calls and combine small functions into pipelines.
TL;DR
- 01Transform multi-argument functions into nested single-argument
curryfunctions. - 02Apply subset arguments up front to generate specialized function presets.
- 03Compose independent single-input operations into clean linear data pipelines.
Tips
- 01Design small, single-purpose functions to make currying and function composition patterns easier to build.
- 02Prefer the
pipehelper to construct left-to-right processing streams matching natural reading orders.
Warnings
- 01Avoid currying every simple method because unnecessary function wrappers hurt overall script readability.
- 02Validate data parameters at each pipeline step to prevent silent runtime type errors.
JavaScript Currying and Composition
(continued)What Currying Does
Currying chainsConverts multi-argument functions into sequential single-argument calls.
function add(a, b, c) { return a + b + c; } const curryAdd = a => b => c => a + b + c; curryAdd(1)(2)(3); // 6Call executionInvokes the original function only after all arguments are received.
const add5 = curryAdd(5); add5(2)(3); // 10Arrow notationUses nested arrow functions to construct inline curried definitions.
const multiply = a => b => a * b;
JavaScript Currying and Composition
(continued)A Generic Curry Helper
curry() implementationGathers arguments recursively until the count matches function arity.
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } return (...next) => { return curried.apply(this, [...args, ...next]); }; }; }Function lengthResolves function arity dynamically using the fn.length property.
const sum = curry((a, b, c) => a + b + c); sum(1)(2)(3); // 6 sum(1, 2)(3); // 6Arity configurationsSpecifies manual arity limits for functions with default parameters.
function curryN(fn, arity) { return function collect(...args) { return args.length >= arity ? fn(...args) : (...more) => collect(...args, ...more); }; }
JavaScript Currying and Composition
(continued)Partial Application
bind() methodBinds arguments up front using the built-in prototype bind method.
function mult(a, b) { return a * b; } const double = mult.bind(null, 2); double(5); // 10Custom partialBuilds a partial helper to bind arguments without bind context.
function partial(fn, ...fixed) { return (...rest) => fn(...fixed, ...rest); } const greet = partial((g, n) => g + n, "Hi "); greet("Ada"); // "Hi Ada!"
JavaScript Currying and Composition
(continued)Composing Functions
compose() helperCombines functions executing right-to-left like mathematical equations.
const compose = (...fns) => x => fns.reduceRight((acc, f) => f(acc), x); const shout = s => s.toUpperCase() + "!"; const format = compose(shout, s => s.trim()); format(" hi "); // "HI!"pipe() helperCombines functions executing left-to-right to match reading order.
const pipe = (...fns) => x => fns.reduce((acc, f) => f(acc), x); const run = pipe(s => s.trim(), s => s.toUpperCase()); run(" hi "); // "HI"
JavaScript Currying and Composition
(continued)Practical Use Cases
Curried validationPre-fills validation parameters to generate specialized rule checklists.
const minLength = curry((min, s) => s.length >= min); const isValid = minLength(3); isValid("ok"); // falseMiddleware executionThreads orders or values through sequential modifier function lists.
const processOrder = pipe( applyDiscount, addTax ); processOrder(100);
JavaScript Currying and Composition
(FAQ)FAQ
Currying transforms a function to accept one argument per call sequentially. Partial application binds multiple arguments immediately and returns a function waiting for the remaining parameters.
Inspect target function arity using the length property. Recursively gather arguments until they match or exceed that length, then invoke the underlying function.
Both helpers chain functions together. Compose executes functions from right to left. Pipe executes them from left to right, matching sequential reading order.
Currying enables specialized helper construction by pre-filling configuration values. This reduces duplicate parameters and allows seamless integration inside pipeline composition chains.
Curried calls instantiate closure environments and nested calls. The performance cost is usually negligible. Avoid currying inside critical performance paths like tight execution loops.
JavaScript Currying and Composition
(In Practice)Order Processing Pipeline
Pairs a curried discount calculator with a left-to-right pipeline to process invoice figures.
- 01Write a generic currying helper to allow step-by-step argument input.
- 02Curry the discount calculation function and fix the percentage rate.
- 03Define standard tax and currency formatting math operations.
- 04Pipe the functions together to thread inputs through each step sequentially.
- 05Invoke the completed pipeline with a test order price.
const curry = fn => (...args) =>
args.length >= fn.length
? fn(...args)
: (...more) => curry(fn)(...args, ...more);
const applyDiscount = curry((rate, price) =>
price * (1 - rate)
);
const applyTenPercentOff = applyDiscount(0.1);
const addTax = price => price * 1.08;
const format = price => "$" + price.toFixed(2);
const pipe = (...fns) => x =>
fns.reduce((acc, fn) => fn(acc), x);
const processOrder = pipe(
applyTenPercentOff,
addTax,
format
);
console.log(processOrder(100)); // "$97.20"Pre-filling function parameters with currying yields simple unary steps that pipe links together smoothly.
JavaScript Event Loop
Learn how the call stack, microtask queue, and task queue control JavaScript execution order.
TL;DR
- 01The call stack executes synchronous code one frame at a time.
- 02Microtasks execute immediately after the current stack frame completes.
- 03Promises resolve before macrotasks like
setTimeoutcallbacks get processed.
Tips
- 01Use the native
queueMicrotask()method to run scripts immediately after synchronous blocks but before paint updates. - 02Split heavy data processing into smaller
setTimeoutbatches to give browsers time to render frames.
Warnings
- 01Running a long synchronous loop blocks the single execution thread, freezing page interactions and rendering updates.
- 02Remember that recursive promise microtasks can starve macrotasks by preventing the event loop from advancing.
JavaScript Event Loop
(continued)The Call Stack
Call stack framesTracks function calls in progress, stacking execution frames until returns occur.
function a() { b(); } function b() { console.log("b"); } a(); // pushes a, then b, then popsSingle execution threadAllows only one stack block to run at any single time.
// Synchronous execution runs sequentiallySync completenessProcesses synchronous scripts completely before checking background event queues.
console.log(1); console.log(2); // 1 always logs before 2Stack overflowTriggers errors when unbounded recursive calls consume all stack memory.
function recurse() { return recurse(); } // recurse(); // Maximum stack exceeded
JavaScript Event Loop
(continued)Macrotasks and the Task Queue
Macrotask callbacksIncludes timer callbacks, network input, and user action triggers.
setTimeout(() => console.log("macro"), 0); console.log("sync");Single task turnsExecutes exactly one macrotask per event loop rotation iteration.
setTimeout(() => console.log("1"), 0); setTimeout(() => console.log("2"), 0); // separate event loop iterationsBrowser paintingInterleaves browser paint updates between subsequent task queue turns.
// Repaints occur after a macrotask completes
JavaScript Event Loop
(continued)Microtasks and Promises
Promise microtasksRuns resolve, reject, and finally callbacks inside the microtask queue.
Promise.resolve().then(() => console.log("micro"));Direct queueingSchedules low-level microtasks directly using the queueMicrotask method.
queueMicrotask(() => console.log("fast"));Full queue drainingForces all queued microtasks to run before the loop advances.
Promise.resolve().then(() => console.log("m1")); Promise.resolve().then(() => console.log("m2")); // m1 and m2 execute back-to-backNested microtasksProcesses nested microtasks inside the same loop iteration drain phase.
Promise.resolve().then(() => { Promise.resolve().then(() => console.log("nested")); });
JavaScript Event Loop
(continued)setTimeout vs Promise Ordering
Order priorityExecutes promise resolutions before timers scheduled within the same block.
setTimeout(() => console.log("time"), 0); Promise.resolve().then(() => console.log("prom")); // logs: prom, timeLoop interleavingRuns all pending microtasks before processing any scheduled timeout callbacks.
setTimeout(() => console.log("time"), 0); [1, 2].forEach(n => { Promise.resolve().then(() => console.log(n)); }); // logs: 1, 2, timerAF visual timingRuns animation callbacks before paint updates, outside standard queues.
requestAnimationFrame(() => console.log("paint"));
JavaScript Event Loop
(continued)UI Responsiveness
Execution chunkingSchedules iteration batches with setTimeout to avoid locking layout repaints.
function chunk(items, index = 0) { const end = Math.min(index + 100, items.length); for (let i = index; i < end; i++) { /* do work */ } if (end < items.length) { setTimeout(() => chunk(items, end), 0); } }Web WorkersDelegates heavy computations to separate threads away from main render loops.
const worker = new Worker("task.js"); worker.postMessage(data); worker.onmessage = e => console.log(e.data);
JavaScript Event Loop
(FAQ)FAQ
The event loop orchestrates asynchronous callbacks inside a single-threaded runtime. It polls task queues when the call stack clears and forwards pending operations to the execution thread.
Promises resolve inside the microtask queue, which has higher execution priority. The event loop drains all microtasks before picking up the next macrotask from the timer queue.
Microtasks handle Promise resolutions and direct queueMicrotask() actions. Macrotasks process callbacks from timers, user interactions, and fetch operations. Microtasks drain completely between each individual macrotask.
JavaScript runs on a single main thread. Long calculations occupy the call stack, preventing the event loop from rendering layout repaints or handling clicks. Offload heavy math to Web Workers.
No, a zero delay registers a callback into the macrotask queue. The engine must finish executing the current call stack and empty the microtask queue before starting it.
JavaScript Event Loop
(In Practice)Predicting Execution Order
Combines synchronous execution, Promise microtasks, a setTimeout timer, and async awaits to demonstrate execution priorities.
- 01Execute the synchronous start log statement immediately.
- 02Register a zero-delay timeout callback into the macrotask queue.
- 03Push a Promise resolution callback to the microtask queue.
- 04Run the async function synchronously up to the first await keyword.
- 05Drain the complete microtask queue before picking up the pending macrotask.
console.log("1: sync start");
setTimeout(() => {
console.log("2: setTimeout (macrotask)");
}, 0);
Promise.resolve().then(() => {
console.log("3: promise (microtask)");
});
async function run() {
console.log("4: async start (sync)");
await null;
console.log("5: async microtask");
}
run();
console.log("6: sync end");
// Output order: 1, 4, 6, 3, 5, 2Synchronous execution completes first, then the microtask queue drains fully, and finally the next macrotask runs.
JavaScript Generators
Learn how generator functions pause and resume execution to build lazy sequences and iterables.
TL;DR
- 01Declare generators with
function*to suspend and resume functions. - 02Emit values lazily on demand using the
yieldkeyword. - 03Forward iteration sequences to external collections using
yield*delegation.
Tips
- 01Use generators to compute massive data sequences lazily without consuming system memory up front.
- 02Assign generator methods to
Symbol.iteratorproperties to create custom iterables cleanly.
Warnings
- 01Catch exceptions thrown inside generator scopes to prevent them from closing the iterator permanently.
- 02Avoid using spread syntax on infinite generators to prevent crashing the browser thread.
JavaScript Generators
(continued)Generator Basics
function* declarationDeclares generators which return an iterator object instead of running code.
function* counter() { yield 1; yield 2; } const it = counter();next() callsResumes execution block internally until encountering the next yield line.
it.next(); // { value: 1, done: false } it.next(); // { value: 2, done: false }Return valuesSignals completion with done true and returns standard values if declared.
function* range() { yield 1; return "stop"; } const it2 = range(); it2.next(); // { value: 1, done: false } it2.next(); // { value: "stop", done: true }
JavaScript Generators
(continued)Controlling Generators
Value injectionPasses parameter values back into the generator at pause lines.
function* greet() { const name = yield "name?"; yield `Hi, ${name}`; } const g = greet(); g.next(); // "name?" g.next("Ada"); // { value: "Hi, Ada", done: false }return() methodTerminates generator runs early, returning specified values immediately.
const it = counter(); it.next(); it.return("end"); // { value: "end", done: true }throw() methodInjects exceptions directly into generators at the current yield line.
function* safe() { try { yield 1; } catch (e) { yield e.message; } } const it = safe(); it.next(); it.throw(new Error("oops")); // value: "oops"
JavaScript Generators
(continued)Lazy Sequences
Infinite generatorsComputes unending data streams on demand with zero memory leaks.
function* naturals() { let n = 1; while (true) yield n++; } const it = naturals(); it.next().value; // 1take() boundariesExtracts limited arrays from lazy sequences using break counters.
function take(iterable, count) { const res = []; for (const v of iterable) { if (res.length >= count) break; res.push(v); } return res; } take(naturals(), 3); // [1, 2, 3]
JavaScript Generators
(continued)Custom Iterables
Symbol.iterator methodAttaches generators to class object structures to allow for-of loops.
class Range { constructor(start, end) { this.start = start; this.end = end; } *[Symbol.iterator]() { for (let i = this.start; i <= this.end; i++) { yield i; } } } [...new Range(1, 3)]; // [1, 2, 3]
JavaScript Generators
(continued)Delegation with yield*
Iterable forwardingDelegates execution directly to another iterable structure, avoiding loops.
function* combine() { yield* [1, 2]; yield* "ab"; } [...combine()]; // [1, 2, "a", "b"]
JavaScript Generators
(FAQ)FAQ
Regular functions run to completion immediately when invoked. Generator functions, declared with function*, return an iterator instead. The function body runs only when calling .next().
The yield keyword pauses generator function execution and outputs a value to the caller. The generator remains frozen until the caller invokes .next() again.
Use yield* to forward values from another iterable, like an array or generator. This avoids manual loops and passes .next(), .return(), and .throw() down automatically.
The .return(value) method closes the generator early, returning the value. The .throw(error) method injects an exception at the current pause point, letting try/catch handle it.
Yes, they are. Assign a generator function to the object's Symbol.iterator property. The engine handles iterator tracking and value formats automatically under the hood.
JavaScript Generators
(In Practice)Lazy Fibonacci Sequence Generator
Generates an infinite Fibonacci sequence lazily using destructuring assignment and a custom take controller.
- 01Initialize variables to store the two initial sequence values.
- 02Establish an infinite loop that yields numbers on demand.
- 03Yield the current sequence value back to the caller.
- 04Calculate the subsequent numbers using destructuring array assignments.
- 05Pull a subset array of values without triggering infinite processing.
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
function take(generator, count) {
const result = [];
for (const value of generator) {
if (result.length >= count) break;
result.push(value);
}
return result;
}
console.log(take(fibonacci(), 5)); // [1, 1, 2, 3, 5]Generators allow processing infinite data sequences safely by computing next values only when requested.
JavaScript Iterators
Learn iterators, the iteration protocol, and generators for controlling how data is consumed.
TL;DR
- 01Expose a
nextmethod returningvalueanddoneproperties. - 02Implement
Symbol.iteratorto make custom objects natively iterable. - 03Use generator functions to construct custom iterables efficiently.
Tips
- 01Use generator functions to implement the iteration contract automatically without manual state tracking.
- 02Delegate execution to nested iterables using
yield*to simplify generator loop declarations.
Warnings
- 01Remember that exhausted iterators cannot be reused without obtaining a fresh iterator instance.
- 02Convert plain objects using
Object.entries()before attempting loop iteration over them.
JavaScript Iterators
(continued)What Iterators Are
Iterator definitionProvides a next method returning value and done flags.
const arr = [10, 20]; const it = arr[Symbol.iterator](); it.next(); // { value: 10, done: false }Iteration stateTracks progress dynamically, returning done: true when complete.
it.next(); // { value: 20, done: false } it.next(); // { value: undefined, done: true }Underlying supportPowers loops, spreads, and destructuring operations implicitly.
const [x, y] = [10, 20]; // uses iterator
JavaScript Iterators
(continued)Iteration Protocol
Custom iterablesImplements Symbol.iterator to make objects work with for-of.
const obj = { data: [1, 2, 3], [Symbol.iterator]() { let i = 0; return { next: () => ({ value: this.data[i], done: i++ >= this.data.length }) }; } }; for (const n of obj) console.log(n);Required structuresDemands the standard iterator method shape to match engine interfaces.
// Iterator returns: { next() { ... } }
JavaScript Iterators
(continued)Built-in Iterables
Standard collectionsProvides native iteration for strings, arrays, sets, and maps.
for (const char of "Hi!") console.log(char); for (const val of new Set([1, 2])) console.log(val);Map entriesIterates over key-value pairs using array destructuring syntax.
const map = new Map([['a', 1]]); for (const [k, v] of map) console.log(k, v);DOM NodeListsSupports for-of iteration on elements retrieved from DOM queries.
const divs = document.querySelectorAll("div"); for (const div of divs) console.log(div);
JavaScript Iterators
(continued)Generators
Generator functionsPauses function execution using the yield keyword.
function* greet() { const name = yield "name?"; yield `Hello, ${name}!`; } const g = greet(); g.next().value; // "name?" g.next("Ada").value; // "Hello, Ada!"Generator delegationDelegates execution to nested iterables using the yield* operator.
function* combine() { yield* [1, 2]; yield* ['a', 'b']; } console.log([...combine()]); // [1, 2, 'a', 'b']
JavaScript Iterators
(FAQ)FAQ
An iterable is an object defining a Symbol.iterator method. An iterator is the returned object containing a next() method. Arrays are iterables, while array.values() returns an iterator.
Yes, define a Symbol.iterator method returning a next() method on your object. That method must return a {value, done} structure. The object then supports for...of loops.
Iterators hold internal state and become exhausted when they return done: true. Calling next() afterward continues returning true. Retrieve a fresh iterator to loop again.
Use generator functions for complex state tracking. The runtime automatically manages state boundaries and suspends executions. Manual iterators are better for highly specific performance scenarios.
Yes, both operations rely on the standard Symbol.iterator protocol. Any object implementing this protocol will work correctly with destructuring and spread operators.
JavaScript Iterators
(In Practice)Paginated API Response Iterator
Wraps a paginated server endpoint inside a custom async iterator for streaming record sets.
- 01Set up local trackers for page counts and finished statuses.
- 02Expose the iterator protocol handler method on the container.
- 03Define the async next method structure to request records.
- 04Fetch server data records and adjust page markers recursively.
- 05Return the received records list or done flags.
function createPageIterator(fetcher) {
let nextPage = 1;
let isDone = false;
return {
[Symbol.iterator]() {
return {
async next() {
if (isDone) {
return { done: true };
}
const res = await fetcher(nextPage);
if (res.hasMore) {
nextPage++;
} else {
isDone = true;
}
return { value: res.items, done: false };
}
};
}
};
}Custom iteration protocols allow you to stream paginated datasets as if they were simple local loops.
JavaScript Prototypal Inheritance
Understand the prototype chain, Object.create, and how class syntax wraps prototypal inheritance.
TL;DR
- 01Inherit object properties dynamically through a linked chain of prototypes.
- 02Construct prototype linkages directly using the standard
Object.createmethod. - 03Use modern
classdeclarations as syntactic sugar over prototype chains.
Tips
- 01Use the standard
Object.getPrototypeOf()method instead of legacy properties like__proto__to inspect prototypes. - 02Verify own object properties explicitly using
Object.hasOwn()before accessing inherited properties.
Warnings
- 01Avoid setting object prototypes dynamically using
Object.setPrototypeOfbecause it degrades property access performance. - 02Filter properties inside
for...inloops to prevent iteration leaks from inherited enumerable method names.
JavaScript Prototypal Inheritance
(continued)The Prototype Chain
Prototype linkageLinks objects together in a prototype chain for sharing property values.
const animal = { eats: true }; const rabbit = Object.create(animal); rabbit.hops = true; console.log(rabbit.eats); // trueChain lookup rulesWalks up prototype chains until matching properties are found.
// Lookup: rabbit -> animal -> Object.prototypeChain endEnds prototype search trees at Object.prototype, which has null prototype.
const proto = Object.getPrototypeOf( Object.prototype ); console.log(proto); // null
JavaScript Prototypal Inheritance
(continued)Creating Prototypes
Object.createCreates new object instances with explicit prototype mappings.
const base = { greet() { return "hi"; } }; const obj = Object.create(base); console.log(obj.greet()); // "hi"Inspecting prototypesInspects prototype references using getPrototypeOf cleanly.
Object.getPrototypeOf(obj) === base; // trueInheritance without classesModels inheritance associations directly without requiring constructors.
const parent = { val: 42 }; const child = Object.create(parent);
JavaScript Prototypal Inheritance
(continued)Constructor prototype
Constructor functionsAttaches shared functions directly to constructor prototype properties.
function Dog(name) { this.name = name; } Dog.prototype.bark = function() { return `${this.name} barks`; }; const rex = new Dog("Rex");Memory optimizationShares method references across all constructed instances.
// rex.bark links to Dog.prototype.barkinstanceof verificationConfirms prototype links exist in target instance chains.
console.log(rex instanceof Dog); // true
JavaScript Prototypal Inheritance
(continued)Class Sugar
class keywordCompiles class helper blocks to standard prototypes.
class Dog { constructor(name) { this.name = name; } bark() { return `${this.name} barks`; } } typeof Dog; // "function"Subclass extendsSets up prototype chains between parent and child automatically.
class Puppy extends Dog { bark() { return super.bark() + "!"; } }
JavaScript Prototypal Inheritance
(continued)Property Shadowing
hasOwn checkChecks property existence directly on the local object instance.
const base = { color: "red" }; const item = Object.create(base); console.log(Object.hasOwn(item, "color")); // falseProperty overridesShadows prototype property definitions by setting local values.
item.color = "blue"; console.log(item.color); // "blue" console.log(base.color); // "red"
JavaScript Prototypal Inheritance
(FAQ)FAQ
Every JavaScript object holds an internal link pointing to a prototype object. Property lookups walk up this chain recursively until they locate a matching property or reach null.
The __proto__ property is a deprecated legacy accessor. The Object.getPrototypeOf() method is the modern, standardized interface. You should use the functional methods in production code.
No, it is not. The class keyword compiles to a standard constructor function. Methods declared in classes sit directly on the constructor's prototype object at runtime.
Shadowing occurs when an object defines a property matching the name of a prototype property. The object's own property overrides the lookup value without altering the prototype itself.
Use the Object.hasOwn(obj, prop) method. This returns true if the property exists directly on the target instance. It returns false for inherited prototype properties.
JavaScript Prototypal Inheritance
(In Practice)Compiling Class Inheritance to Prototypes
Demonstrates how modern ES6 classes are transformed into prototype constructors under the hood.
- 01Create a base constructor function to assign user properties.
- 02Attach the login method to the user constructor prototype.
- 03Create a subclass admin constructor calling the parent constructor context.
- 04Bind the admin prototype to a new object inheriting from the user prototype.
- 05Reset the admin constructor reference to point to itself correctly.
function createCompilledClass() {
function User(name) {
this.name = name;
}
User.prototype.login = function() {
return this.name + " logged in";
};
function Admin(name, role) {
User.call(this, name);
this.role = role;
}
Admin.prototype = Object.create(
User.prototype
);
Admin.prototype.constructor = Admin;
return { User, Admin };
}Class declarations compile to constructor functions with shared methods placed on their prototype chains.
JavaScript Proxy and Reflect
Learn how Proxy traps and the Reflect API intercept and control object behavior in JavaScript.
TL;DR
- 01Wrap target objects with
Proxywrappers to intercept standard operations. - 02Define handler traps like
getandsetto customize behaviors. - 03Use the
ReflectAPI to forward default actions inside traps.
Tips
- 01Invoke matching
Reflectmethods inside every proxy trap to preserve default language behavior for properties. - 02Create validation layers using a proxy
settrap to reject invalid assignments before updating targets.
Warnings
- 01Always pass the receiver argument to
Reflect.getto keep getters bound to the correct context. - 02Avoid wrapping objects in performance-critical loops because proxy traps introduce function invocation overhead.
JavaScript Proxy and Reflect
(continued)Creating a Proxy
Proxy wrapperWraps target objects using the Proxy constructor with custom handlers.
const target = { name: "Ada" }; const proxy = new Proxy(target, {}); console.log(proxy.name); // "Ada"Handler trapsSpecifies trap functions in handlers to intercept object reads.
const handler = { get(target, prop) { return target[prop]; } }; const proxy = new Proxy({ x: 1 }, handler);Function proxyingWraps executable function objects to intercept call parameters.
function greet(name) { return `Hi ${name}`; } const pr = new Proxy(greet, { apply(t, thisArg, args) { return t(...args); } });
JavaScript Proxy and Reflect
(continued)Common Traps
get trapIntercepts property access lookups and method executions.
const p = new Proxy({ a: 1 }, { get(target, prop) { return prop in target ? target[prop] : "missing"; } });set trapIntercepts property assignments and returns confirmation flags.
const p = new Proxy({}, { set(target, prop, value) { target[prop] = value; return true; } });has trapIntercepts the boolean property presence verification check.
const p = new Proxy({ secret: 1 }, { has(target, prop) { return prop === "secret" ? false : prop in target; } });
JavaScript Proxy and Reflect
(continued)Reflect Default Behavior
Reflect forwardingInvokes default target operations inside custom proxy traps.
const p = new Proxy({ a: 1 }, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); } });Context preservationPasses receivers to Reflect to maintain correct property accessor bindings.
const target = { _v: 10, get v() { return this._v; } };Execution return flagsExposes execution success values as standard true or false flags.
const obj = Object.freeze({ a: 1 }); const ok = Reflect.set(obj, "a", 2); // false
JavaScript Proxy and Reflect
(continued)Practical Use Cases
Input validationEnforces variable type constraints prior to committing assignments.
const validator = { set(target, prop, value) { if (typeof value !== "number") return false; return Reflect.set(target, prop, value); } };Default attributesSupplies default values when missing property keys are queried.
const fallback = { get(target, prop) { return prop in target ? target[prop] : 0; } };
JavaScript Proxy and Reflect
(continued)Common Pitfalls
Losing this contextAvoids method execution failure by forwarding matching receivers.
// Always pass 'receiver' to Reflect.get()Identity checksChecks references carefully since proxies do not equal target references.
const target = {}; const proxy = new Proxy(target, {}); console.log(proxy === target); // false
JavaScript Proxy and Reflect
(FAQ)FAQ
A Proxy wraps target objects to intercept core operations like property reads. You specify custom logic via handler functions called traps. This is useful for validation, logging, and reactivity.
A Proxy intercepts actions on objects. The Reflect API provides matching methods to execute standard behaviors. You call Reflect methods inside traps to forward requests.
Using Reflect methods correctly preserves the this binding context for get accessors. It also returns consistent booleans indicating success, avoiding silent failures in non-strict modes.
The get and set traps intercept property accesses. The has trap handles the in operator. The apply trap intercepts function executions when targets are callable.
Yes, because every trapped interaction calls a handler function. This overhead is fine for configuration boundaries or schemas. Avoid using proxies in tight, performance-critical loops.
JavaScript Proxy and Reflect
(In Practice)Validation Proxy Schema
Wraps an object in a validation proxy to enforce strict data types on property assignments.
- 01Define a strict type validation rules checklist schema.
- 02Instantiate a new Proxy with a custom set trap handler.
- 03Intercept property assignment requests at the set boundary.
- 04Verify that incoming values match the validated schema types.
- 05Apply validated assignments using Reflect set calls.
function createValidatedUser() {
const schema = {
age: "number",
name: "string"
};
return new Proxy({}, {
set(target, prop, value) {
if (prop in schema) {
if (typeof value !== schema[prop]) {
throw new TypeError(
prop + " must be a " + schema[prop]
);
}
}
return Reflect.set(target, prop, value);
}
});
}Set traps validate data before assignments commit, protecting target instances from runtime configuration bugs.
JavaScript Regular Expressions
Learn regex patterns, flags, exec, test, match, and replace for powerful string processing.
TL;DR
- 01Define literal patterns with slashes or construct them using variables.
- 02Verify expressions with
testand extract groups usingmatchAll. - 03Replace target strings dynamically by passing matching replacement patterns.
Tips
- 01Use named capture groups to make complex regular expressions much easier to read and maintain.
- 02Create fresh regex instances or reset
lastIndexto zero when executing global state matches.
Warnings
- 01Remember that global regular expressions maintain state between execution runs via the
lastIndexproperty. - 02Escape user-provided variables with backslashes before constructing dynamic patterns to prevent parsing failures.
JavaScript Regular Expressions
(continued)Creating Patterns
Regex literalDefines static patterns using forward slash brackets.
const pattern = /hello/; console.log(pattern.test("hello world")); // trueRegExp constructorCompiles patterns dynamically at runtime from string variables.
const word = "hello"; const pattern = new RegExp(word); console.log(pattern.test("say hello")); // trueCharacter classesMatches specific characters from defined character sets.
/[aeiou]/.test("hello"); // true /[0-9]/.test("abc123"); // trueString anchorsEnforces starting and ending boundaries on checks.
/^hello/.test("hello world"); // true /world$/.test("hello world"); // true
JavaScript Regular Expressions
(continued)Quantifiers
Zero or moreMatches zero or more occurrences using the asterisk operator.
/a*b/.test("b"); // true /a*b/.test("aaab"); // trueOne or moreMatches one or more occurrences using the plus operator.
/a+b/.test("ab"); // true /a+b/.test("b"); // falseOptional quantifierMarks elements as optional using the question mark operator.
/colou?r/.test("color"); // true /colou?r/.test("colour"); // trueLazy matchingAppends ? to quantifiers to match minimal characters.
const greedy = "<a><b>".match(/<.+>/)[0]; // "<a><b>" const lazy = "<a><b>".match(/<.+?>/)[0]; // "<a>"
JavaScript Regular Expressions
(continued)Flags and Methods
Regex flagsSpecifies search parameters like case-insensitivity or global parsing.
/hello/i.test("HELLO"); // true "hi hi".match(/hi/g); // ["hi", "hi"]exec() detailsReturns match arrays along with capture groups.
const res = /(\w+)@(\w+)/.exec("user@test.com"); // res[1] === "user", res[2] === "test"
JavaScript Regular Expressions
(continued)Replacing and Testing
String replaceReplaces search results with new replacement string values.
"hello world".replace(/hello/, "hi"); // "hi world"String replaceAllReplaces all matching entries globally when using g flag expressions.
"hi hi".replaceAll(/hi/g, "hello"); // "hello hello"
JavaScript Regular Expressions
(continued)Common Regex Patterns
Validation samplesValidates formats like phone numbers or simple emails.
/^\d{3}-\d{3}-\d{4}$/.test("123-456-7890"); // trueWhitespace collapseCollapses duplicate spaces and trims outer edges.
const clean = " a b ".replace(/\s+/g, " ").trim(); // clean === "a b"
JavaScript Regular Expressions
(FAQ)FAQ
Use the RegExp constructor when compiling patterns dynamically from variables. Use literal patterns for static definitions. Literals compile during script loading and catch syntax bugs early.
Greedy quantifiers match as many characters as possible. Lazy quantifiers append a question mark to match as few as possible. For example, .*? performs lazy matches.
Use String.prototype.matchAll() with the g flag. This returns an iterator containing complete match details and capture groups. Regular match() drops capture group indices.
The replace() method defaults to updating only the first match without a global g flag. Use replaceAll() or include the global flag to substitute all instances.
Parentheses wrap sections of patterns to create capture groups. Retrieve them using index offsets like match[1]. Alternatively, use (?<name>) syntax to query named capture groups.
JavaScript Regular Expressions
(In Practice)Parsing URL Strings with Named Groups
Parses database or web addresses using regular expression named capture groups to extract protocol and host names.
- 01Write a validation regular expression pattern specifying named capture groups.
- 02Execute the pattern against the target string address parameters.
- 03Confirm that a valid match details response was retrieved.
- 04Extract captured keys from the matched groups property index.
- 05Return the structured details object back to callers.
function parseConnection(str) {
const regex =
/^(?<proto>https?):\/\/(?<host>[^/]+)$/;
const match = regex.exec(str);
if (!match) return null;
const { proto, host } = match.groups;
return { proto, host };
}Named capture groups document intent directly in patterns, making parsed string details easy to query.
JavaScript Symbols
Use unique Symbol values as collision-free object keys and customize built-in object behavior.
TL;DR
- 01Instantiate unique primitive values with the built-in
Symbolfactory. - 02Configure unique collision-free keys hidden from standard object loop enumerations.
- 03Use well-known symbols to customize core language behaviors like iteration.
Tips
- 01Use local symbols to declare properties that will never collide with third-party keys.
- 02Retrieve shared symbol values across realms using the global
Symbol.for()registry.
Warnings
- 01Avoid calling
Symbolusingnewbecause symbols are primitives rather than constructible classes. - 02Remember that symbol properties are omitted by standard operations like
JSON.stringifyand loops.
JavaScript Symbols
(continued)What Symbols Are
Unique primitiveGenerates unique primitive values on every function call.
const a = Symbol("id"); const b = Symbol("id"); console.log(a === b); // falseDescription debuggingAssigns debugging descriptions which do not affect symbol identity.
const s = Symbol("label"); console.log(s.description); // "label"Primitives typeReturns symbol from typeof checks.
console.log(typeof Symbol()); // "symbol"
JavaScript Symbols
(continued)Object Keys
Symbol propertiesAssigns properties using symbol keys to guarantee collision-free attributes.
const KEY = Symbol("key"); const user = { name: "Ada", [KEY]: 42 };Bracket accessesQueries symbol keys using bracket notation rather than dot accesses.
console.log(user[KEY]); // 42Descriptors listingLists symbol keys using Reflect methods.
Reflect.ownKeys(user); // ["name", Symbol(key)]
JavaScript Symbols
(continued)Omitted Enumeration
Loop exclusionExcludes symbol properties from keys listings and loops.
const obj = { name: "A", [Symbol("id")]: 1 }; console.log(Object.keys(obj)); // ["name"]JSON serializationOmits symbol keys during stringify operations.
console.log(JSON.stringify(obj)); // '{"name":"A"}'Explicit queriesQueries symbols using specialized getOwnPropertySymbols calls.
Object.getOwnPropertySymbols(obj); // [Symbol(id)]
JavaScript Symbols
(continued)Well-Known Symbols
Symbol.iteratorEnables custom iteration behavior on plain objects.
const range = { [Symbol.iterator]() { return { next: () => ({ done: true }) }; } }; [...range];Symbol.toPrimitiveEnforces custom conversions into primitives.
const cash = { amount: 50, [Symbol.toPrimitive](hint) { return hint === "string" ? `$${this.amount}` : this.amount; } };Symbol.hasInstanceOverrides instanceof check mechanics for target configurations.
class Even { static [Symbol.hasInstance](num) { return num % 2 === 0; } } console.log(4 instanceof Even); // true
JavaScript Symbols
(continued)Global Registry
Symbol.forRegisters shared symbol instances in a global scope index.
const a = Symbol.for("app.id"); const b = Symbol.for("app.id"); console.log(a === b); // trueSymbol.keyForReturns registered index strings matching active shared symbols.
console.log(Symbol.keyFor(a)); // "app.id"
JavaScript Symbols
(FAQ)FAQ
A Symbol represents a unique primitive value that avoids key collisions. It allows adding custom properties to objects safely. Well-known symbols hook directly into core operations.
By design, serialization routines like JSON.stringify only process string-keyed properties. This restriction lets symbols act as hidden metadata boundaries. They remain safe from accidental log outputs.
The Symbol() factory guarantees unique instances per call. The Symbol.for() method queries the global symbol registry first. It retrieves matching instances if they already exist.
Well-known symbols are built-in hooks like Symbol.iterator. They enable objects to customize default language behaviors. This includes customizing string casting and enabling iteration protocols.
No, they are not. The Object.getOwnPropertySymbols() method returns all symbol keys from target instances. Anyone holding reference handles can access the property values.
JavaScript Symbols
(In Practice)Private Metadata Attachment
Uses local Symbols to attach internal metadata to objects safely, preventing collision with user properties.
- 01Instantiate a local Symbol identifier to act as the metadata key.
- 02Define a setMetadata function mapping data to the object symbol key.
- 03Assign metadata to the object using bracket notation entries.
- 04Define a getMetadata function to retrieve values using the key.
- 05Return the helper functions while keeping the Symbol hidden.
function createMetadataSystem() {
const METADATA = Symbol("internal_metadata");
function setMetadata(obj, data) {
obj[METADATA] = data;
}
function getMetadata(obj) {
return obj[METADATA];
}
return { setMetadata, getMetadata };
}Symbol properties guarantee collision-free attributes, which makes them ideal for attaching internal metadata safely.
JavaScript WeakMap and WeakRef
Store object-keyed data and hold references without blocking garbage collection in JavaScript.
TL;DR
- 01Store object-keyed properties using weak references to support garbage collection.
- 02Hold object references without preventing the engine garbage collection process.
- 03Use
FinalizationRegistryobjects to configure cleanup callbacks for collected references.
Tips
- 01Attach metadata records to external object keys safely using self-cleaning
WeakMapcaches. - 02Create memory-sensitive caches using
WeakRefto let engines collect references under pressure.
Warnings
- 01Avoid setting primitive keys on
WeakMapbecause keys must always be object references. - 02Do not rely on
FinalizationRegistryfor critical updates since callback execution timing is unpredictable.
JavaScript WeakMap and WeakRef
(continued)What WeakMap Is
WeakMap key valuesStores key-value entries where keys are object references.
const cache = new WeakMap(); const user = { id: 1 }; cache.set(user, { clicks: 3 });Weak referencesAllows garbage collection of key objects when references disappear.
let el = document.querySelector("#widget"); cache.set(el, { clicks: 0 }); el = null; // entry can be collected nowKey constraintsRequires object keys, throwing errors when primitives are passed.
// cache.set("key", 1); // TypeError
JavaScript WeakMap and WeakRef
(continued)Non-Enumerable design
No iterationProhibits access to size, keys, and values properties.
const wm = new WeakMap(); console.log(wm.size); // undefined // [...wm]; // TypeError: wm is not iterableDeterministic safetyHides garbage collector actions to keep behavior predictable.
// Non-enumerable design prevents observation
JavaScript WeakMap and WeakRef
(continued)Private Data Caches
Property encapsulationAttaches private attributes to objects without changing structures.
const privateData = new WeakMap(); class Account { constructor(b) { privateData.set(this, { b }); } getBal() { return privateData.get(this).b; } }Metadata attachmentCaches calculated values keyed to specific element instances.
const sizeCache = new WeakMap(); function getBounding(el) { if (!sizeCache.has(el)) { sizeCache.set(el, el.getBoundingClientRect()); } return sizeCache.get(el); }
JavaScript WeakMap and WeakRef
(continued)WeakRef
Object wrappersWraps object references without blocking garbage collection.
let obj = { data: "large" }; const ref = new WeakRef(obj); ref.deref(); // { data: "large" }deref() checksQueries deref references, verifying presence before accesses.
obj = null; // after GC executes: const target = ref.deref(); if (target) console.log(target.data);
JavaScript WeakMap and WeakRef
(continued)FinalizationRegistry
Cleanup callbacksRegisters callbacks to execute after objects are collected.
const reg = new FinalizationRegistry(held => { console.log("collected:", held); }); reg.register(obj, "label");
JavaScript WeakMap and WeakRef
(FAQ)FAQ
Map objects retain strong references to their keys, preventing garbage collection. WeakMap objects hold weak references, allowing keys to be collected. Keys must be objects, and iteration is prohibited.
Garbage collection timing is non-deterministic. Exposing entry lists would reveal when memory recovery occurred. The specification prohibits size and iteration properties to prevent engine leakage.
A WeakRef holds object references without preventing garbage collection. Call .deref() to retrieve targets. This method returns undefined if the object has already been garbage collected.
Use private fields for instance attributes on classes you write. Reach for WeakMap to store private data on foreign instances or plain objects that do not support private fields.
No, engines may skip callbacks if programs exit early. Treat these execution routines as non-critical optimizations. Always use explicit cleanups or try/finally blocks for essential operations.
JavaScript WeakMap and WeakRef
(In Practice)DOM Element Bounding Rect Cache
Caches DOM bounding rectangles using a WeakMap to automatically purge cache data when nodes are removed.
- 01Create a WeakMap instance to hold the DOM element cache.
- 02Check if the cache contains bounding rects for the query element.
- 03Calculate bounds using the element's bounding rect API.
- 04Store calculated bounds in the cache mapped to the element.
- 05Return cached bounds directly without executing repetitive calculations.
function createBoundingCache() {
const cache = new WeakMap();
function getBounds(element) {
if (!cache.has(element)) {
const rect =
element.getBoundingClientRect();
cache.set(element, rect);
}
return cache.get(element);
}
return { getBounds };
}WeakMap structures automatically clean up cached entries once mapped element nodes are deleted from the DOM.