← All stacks
JS

JavaScript

16 sections · 146 entries

Variables & Constants

let Declaration

syntax
let variableName = value;
example
let score = 0;
score = 10;
console.log(score);
output
10

Note Block-scoped. Cannot be re-declared in the same scope. Preferred for values that change.

const Declaration

syntax
const variableName = value;
example
const API_URL = "https://api.example.com";
const cart = ["apple"];
cart.push("banana"); // allowed
console.log(cart);
output
["apple", "banana"]

Note Block-scoped. Must be initialized at declaration. The binding is immutable, but object/array contents can still change.

var Declaration (Legacy)

syntax
var variableName = value;
example
function demo() {
  if (true) {
    var leaked = "visible outside block";
  }
  console.log(leaked);
}
demo();
output
"visible outside block"

Note Function-scoped, not block-scoped. Hoisted to the top of the function. Avoid in modern code -- use let or const instead.

Array Destructuring

syntax
const [a, b, ...rest] = array;
example
const rgb = [30, 120, 255];
const [red, green, blue] = rgb;
console.log(green);

const [first, , third] = ["a", "b", "c"];
console.log(first, third);
output
120
"a" "c"

Note Use commas to skip elements. Works with any iterable, not just arrays.

Object Destructuring

syntax
const { key1, key2: alias } = object;
example
const user = { name: "Lina", role: "admin", id: 42 };
const { name, role, id: userId } = user;
console.log(name, userId);
output
"Lina" 42

Note Use colon to rename. Combine with defaults: const { theme = "light" } = settings;

Nested Destructuring

syntax
const { outer: { inner } } = obj;
example
const response = {
  data: { user: { name: "Kai", scores: [95, 88] } }
};
const { data: { user: { name, scores: [latest] } } } = response;
console.log(name, latest);
output
"Kai" 95

Note Deeply nested destructuring can hurt readability. Consider extracting in steps for complex structures.

Spread Operator

syntax
const merged = [...arr1, ...arr2];
const merged = { ...obj1, ...obj2 };
example
const defaults = { theme: "light", lang: "en" };
const prefs = { theme: "dark", fontSize: 16 };
const config = { ...defaults, ...prefs };
console.log(config);
output
{ theme: "dark", lang: "en", fontSize: 16 }

Note Later properties overwrite earlier ones. Only performs a shallow copy -- nested objects are still shared by reference.

Rest Parameters

syntax
function fn(...args) {}
example
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(5, 10, 15));
output
30

Note Rest must be the last parameter. Unlike the old arguments object, rest gives you a real Array with all array methods.

Destructuring with Defaults

syntax
const { key = defaultValue } = obj;
const [a = defaultValue] = arr;
example
const { host = "localhost", port = 3000 } = { port: 8080 };
console.log(host, port);
output
"localhost" 8080

Note Defaults only apply when the value is undefined, not when it is null or other falsy values.

Variable Swapping

syntax
[a, b] = [b, a];
example
let x = "hello";
let y = "world";
[x, y] = [y, x];
console.log(x, y);
output
"world" "hello"

Note No temporary variable needed. Works with any number of variables: [a, b, c] = [c, a, b];

Data Types

Primitive Types

syntax
string | number | boolean | undefined | null | symbol | bigint
example
const name = "Mira";       // string
const age = 28;            // number
const active = true;       // boolean
const missing = undefined; // undefined
const empty = null;        // null
const id = Symbol("id");   // symbol
const big = 900719925474099267n; // bigint

Note Primitives are immutable and compared by value. There are 7 primitive types in total.

typeof Operator

syntax
typeof value
example
console.log(typeof "hello");    // "string"
console.log(typeof 42);         // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"
console.log(typeof null);       // "object" (!)  
console.log(typeof []);         // "object"
console.log(typeof (() => {})); // "function"

Note typeof null === "object" is a historic bug that will never be fixed. Use value === null for null checks. Arrays report as "object" -- use Array.isArray() instead.

Type Coercion

syntax
implicit: value + "" | +value | !!value
explicit: String(v) | Number(v) | Boolean(v)
example
console.log("5" + 3);    // "53" (string concat)
console.log("5" - 3);    // 2   (numeric)
console.log(+"42");       // 42  (to number)
console.log(!!"hello");   // true (to boolean)
console.log(Number(""));  // 0
console.log(Number("ab")); // NaN

Note The + operator with a string always coerces to string. Prefer explicit conversion (Number(), String()) to avoid surprises.

Nullish Coalescing (??)

syntax
value ?? fallback
example
const inputCount = 0;
console.log(inputCount || 10);  // 10 (wrong!)
console.log(inputCount ?? 10);  // 0  (correct)

const label = null;
console.log(label ?? "Untitled");
output
10
0
"Untitled"

Note Only triggers on null/undefined, unlike || which triggers on all falsy values (0, "", false, NaN). Use ?? when 0 or empty string are valid values.

Optional Chaining (?.)

syntax
obj?.prop
obj?.[expr]
obj?.method()
example
const user = { profile: { avatar: "pic.png" } };
console.log(user?.profile?.avatar);  // "pic.png"
console.log(user?.settings?.theme);  // undefined
console.log(user?.getName?.());      // undefined (no error)

Note Short-circuits to undefined when a link in the chain is null/undefined. Combine with ?? for defaults: user?.name ?? "Guest"

Nullish Assignment (??=)

syntax
variable ??= value;
example
let config = { timeout: null, retries: 3 };
config.timeout ??= 5000;
config.retries ??= 10;
console.log(config);
output
{ timeout: 5000, retries: 3 }

Note Only assigns when the left side is null or undefined. Also available: ||= (assigns on falsy) and &&= (assigns on truthy).

Symbols

syntax
const sym = Symbol(description);
example
const STATUS = Symbol("status");
const order = { [STATUS]: "shipped", id: 101 };

console.log(order[STATUS]);       // "shipped"
console.log(Object.keys(order));  // ["id"]

Note Symbols are unique and hidden from normal enumeration. Use Symbol.for("key") to create shared/global symbols across modules.

Falsy Values

syntax
false | 0 | -0 | 0n | "" | null | undefined | NaN
example
const values = [false, 0, -0, 0n, "", null, undefined, NaN];
const truthyOnes = values.filter(Boolean);
console.log(truthyOnes.length);
output
0

Note Everything else is truthy, including empty objects {}, empty arrays [], and the string "false". This trips up many developers.

Equality: == vs === vs Object.is

syntax
a === b   // strict (no coercion)
a == b    // loose (with coercion)
Object.is(a, b)  // same-value equality
example
console.log(0 == false);         // true
console.log(0 === false);        // false
console.log(NaN === NaN);        // false
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(0, -0));    // false

Note Always use === for comparisons. Object.is() handles edge cases like NaN and -0 that even === gets wrong.

Strings

Template Literals

syntax
`text ${expression} text`
example
const item = "coffee";
const price = 4.5;
console.log(`One ${item} costs $${price.toFixed(2)}.`);

const multiline = `Line one
Line two
Line three`;
console.log(multiline);
output
"One coffee costs $4.50."
"Line one\nLine two\nLine three"

Note Backtick strings support embedded expressions and multiline content without escape characters.

Tagged Templates

syntax
tagFunction`text ${expr} text`
example
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] !== undefined ? `[${values[i]}]` : "");
  }, "");
}
const user = "Kai";
console.log(highlight`Welcome, ${user}! You have ${3} alerts.`);
output
"Welcome, [Kai]! You have [3] alerts."

Note Tagged templates let you process template literal parts before producing the final string. Used in libraries like styled-components and GraphQL.

String Searching

syntax
str.includes(search, startIndex)
str.startsWith(search)
str.endsWith(search)
example
const msg = "Order #1234 shipped";
console.log(msg.includes("shipped"));   // true
console.log(msg.startsWith("Order"));   // true
console.log(msg.endsWith("shipped"));   // true
console.log(msg.indexOf("#"));          // 6

Note All search methods are case-sensitive. For case-insensitive checks, lowercase both sides first: str.toLowerCase().includes(term.toLowerCase()).

Extracting Substrings

syntax
str.slice(start, end)
str.substring(start, end)
example
const path = "/users/profile/avatar.png";
console.log(path.slice(1));          // "users/profile/avatar.png"
console.log(path.slice(-10));        // "avatar.png"
console.log(path.slice(7, 14));      // "profile"

Note slice() supports negative indices (counts from end). substring() does not. Prefer slice() -- it is more predictable.

Replacing Text

syntax
str.replace(search, replacement)
str.replaceAll(search, replacement)
example
const template = "Hello {name}, welcome to {place}!";
const filled = template
  .replace("{name}", "Ava")
  .replace("{place}", "Berlin");
console.log(filled);

const csv = "a,b,c,d";
console.log(csv.replaceAll(",", " | "));
output
"Hello Ava, welcome to Berlin!"
"a | b | c | d"

Note replace() only swaps the first match unless you use a regex with the /g flag. replaceAll() replaces every occurrence.

Split and Join

syntax
str.split(separator, limit)
arr.join(separator)
example
const tags = "js,react,node";
const tagArray = tags.split(",");
console.log(tagArray);
console.log(tagArray.join(" + "));
output
["js", "react", "node"]
"js + react + node"

Note split("") splits into individual characters. split() with no arguments returns the entire string in a single-element array.

Trimming Whitespace

syntax
str.trim()
str.trimStart()
str.trimEnd()
example
const input = "  hello@example.com  ";
console.log(input.trim());       // "hello@example.com"
console.log(input.trimStart());  // "hello@example.com  "
console.log(input.trimEnd());    // "  hello@example.com"

Note Essential for cleaning user input. Only removes whitespace characters (spaces, tabs, newlines), not other invisible characters.

Padding Strings

syntax
str.padStart(targetLength, padChar)
str.padEnd(targetLength, padChar)
example
const orderNum = "42";
console.log(orderNum.padStart(6, "0"));  // "000042"

const label = "Price";
console.log(label.padEnd(12, "."));      // "Price......."

Note Useful for formatting IDs, aligning columns in console output, or zero-padding numbers.

Case Conversion

syntax
str.toUpperCase()
str.toLowerCase()
str.toLocaleUpperCase(locale)
example
const status = "Pending";
console.log(status.toUpperCase());  // "PENDING"
console.log(status.toLowerCase());  // "pending"

// Locale-aware (e.g., Turkish "i" -> "I" with dot)
console.log("istanbul".toLocaleUpperCase("tr"));

Note Always use locale-aware methods when dealing with internationalized text. The Turkish i is a classic bug source.

Repeat and Character Access

syntax
str.repeat(count)
str.at(index)
example
const border = "=".repeat(30);
console.log(border);

const word = "JavaScript";
console.log(word.at(0));   // "J"
console.log(word.at(-1));  // "t"

Note at() supports negative indices to count from the end. Bracket notation str[-1] returns undefined, not the last character.

Numbers & Math

Parsing Numbers

syntax
Number(value)
parseInt(string, radix)
parseFloat(string)
example
console.log(Number("42"));        // 42
console.log(Number("3.14abc"));   // NaN
console.log(parseInt("3.14abc")); // 3
console.log(parseFloat("3.14abc")); // 3.14
console.log(parseInt("ff", 16));  // 255

Note Number() is stricter -- rejects partially numeric strings. parseInt() stops at the first non-numeric character. Always pass the radix to parseInt() to avoid octal surprises.

Number Checking

syntax
Number.isNaN(value)
Number.isFinite(value)
Number.isInteger(value)
Number.isSafeInteger(value)
example
console.log(Number.isNaN(NaN));          // true
console.log(Number.isNaN("hello"));      // false
console.log(isNaN("hello"));             // true (!) 
console.log(Number.isInteger(4.0));       // true
console.log(Number.isSafeInteger(2**53)); // false

Note Always use Number.isNaN() over the global isNaN(). The global version coerces its argument to a number first, giving misleading results.

Rounding

syntax
Math.round(n) | Math.floor(n) | Math.ceil(n) | Math.trunc(n)
example
const price = 19.872;
console.log(Math.round(price));  // 20
console.log(Math.floor(price));  // 19
console.log(Math.ceil(price));   // 20
console.log(Math.trunc(price));  // 19
console.log(Math.trunc(-3.7));   // -3 (not -4)

Note trunc() simply removes decimals (towards zero). floor() rounds towards negative infinity. They differ for negative numbers: floor(-3.2) = -4 but trunc(-3.2) = -3.

Formatting Decimals

syntax
num.toFixed(digits)
num.toPrecision(precision)
example
const total = 29.5;
console.log(total.toFixed(2));       // "29.50"
console.log((0.1 + 0.2).toFixed(2)); // "0.30"

const big = 123456.789;
console.log(big.toPrecision(6));     // "123457"

Note toFixed() returns a STRING, not a number. Wrap in Number() or use + to convert back: +total.toFixed(2)

Random Numbers

syntax
Math.random()  // [0, 1)
example
// Random float between 0 and 1
console.log(Math.random());

// Random integer from min to max (inclusive)
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 100));

Note Math.random() is NOT cryptographically secure. For tokens or passwords, use crypto.getRandomValues() or crypto.randomUUID().

Min, Max, and Clamping

syntax
Math.min(...values)
Math.max(...values)
example
const scores = [88, 45, 99, 72, 61];
console.log(Math.min(...scores)); // 45
console.log(Math.max(...scores)); // 99

// Clamp a value between bounds
function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
console.log(clamp(150, 0, 100)); // 100

Note For very large arrays (100k+ elements), spread can cause a stack overflow. Use a reduce loop instead.

BigInt

syntax
const big = 123n;
const big = BigInt(value);
example
const huge = 9007199254740993n;
console.log(huge + 1n);   // 9007199254740994n
console.log(huge * 2n);   // 18014398509481986n

// Convert between types
console.log(Number(100n)); // 100
console.log(BigInt(42));   // 42n

Note Cannot mix BigInt and Number in arithmetic (throws TypeError). Convert one type first. No Math methods work with BigInt.

Powers and Roots

syntax
base ** exponent
Math.sqrt(n)
Math.cbrt(n)
Math.pow(base, exp)
example
console.log(2 ** 10);         // 1024
console.log(Math.sqrt(144));  // 12
console.log(Math.cbrt(27));   // 3
console.log(Math.abs(-42));   // 42

Note The ** operator is cleaner than Math.pow() and works with BigInt too: 2n ** 64n.

Locale Number Formatting

syntax
new Intl.NumberFormat(locale, options).format(n)
example
const price = 1234567.89;
console.log(new Intl.NumberFormat("en-US", {
  style: "currency", currency: "USD"
}).format(price));
// "$1,234,567.89"

console.log(new Intl.NumberFormat("de-DE").format(price));
// "1.234.567,89"

Note Intl.NumberFormat handles thousands separators, currency symbols, percentages, and units correctly for any locale.

Arrays

Creating Arrays

syntax
const arr = [a, b, c];
const arr = Array.from(iterable);
const arr = Array.of(elements);
example
const fruits = ["apple", "banana", "cherry"];
const range = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(range);  // [1, 2, 3, 4, 5]

const filled = new Array(3).fill(0);
console.log(filled); // [0, 0, 0]

Note Array(5) creates 5 empty slots, not [5]. Use Array.of(5) for a single-element array. Be careful with fill() on objects -- all slots share the same reference.

Accessing Elements

syntax
arr[index]
arr.at(index)
example
const colors = ["red", "green", "blue", "yellow"];
console.log(colors[0]);     // "red"
console.log(colors.at(-1));  // "yellow"
console.log(colors.at(-2));  // "blue"

Note at() supports negative indexing. Bracket notation with negatives (arr[-1]) returns undefined because it looks for a property named "-1".

Adding and Removing Elements

syntax
arr.push(item)    // end
arr.pop()         // end
arr.unshift(item) // start
arr.shift()       // start
arr.splice(index, deleteCount, ...items)
example
const tasks = ["code", "test"];
tasks.push("deploy");       // ["code", "test", "deploy"]
tasks.unshift("plan");      // ["plan", "code", "test", "deploy"]
const removed = tasks.pop(); // "deploy"

// Insert at index 2
tasks.splice(2, 0, "review");
console.log(tasks); // ["plan", "code", "review", "test"]

Note push/pop are O(1). unshift/shift are O(n) because all indices must be renumbered. splice() mutates the original array.

map()

syntax
arr.map(callback(element, index, array))
example
const prices = [10, 25, 50];
const withTax = prices.map(p => +(p * 1.08).toFixed(2));
console.log(withTax);
output
[10.8, 27, 54]

Note map() returns a new array of the same length. If you do not need the return value, use forEach() instead.

filter()

syntax
arr.filter(callback(element, index, array))
example
const products = [
  { name: "Shirt", price: 25 },
  { name: "Jacket", price: 120 },
  { name: "Cap", price: 15 },
];
const affordable = products.filter(p => p.price < 50);
console.log(affordable.map(p => p.name));
output
["Shirt", "Cap"]

Note Returns a new array with elements that pass the test. The callback must return a truthy/falsy value.

reduce()

syntax
arr.reduce(callback(accumulator, current, index, array), initialValue)
example
const items = [
  { name: "Book", price: 12 },
  { name: "Pen", price: 3 },
  { name: "Bag", price: 45 },
];
const total = items.reduce((sum, item) => sum + item.price, 0);
console.log(total);
output
60

Note Always provide an initial value (second argument). Without it, reduce uses the first element and can throw on empty arrays.

find() and findIndex()

syntax
arr.find(callback)
arr.findIndex(callback)
arr.findLast(callback)
arr.findLastIndex(callback)
example
const users = [
  { id: 1, name: "Ava" },
  { id: 2, name: "Bo" },
  { id: 3, name: "Ava" },
];
console.log(users.find(u => u.name === "Ava"));     // { id: 1, name: "Ava" }
console.log(users.findLast(u => u.name === "Ava")); // { id: 3, name: "Ava" }
console.log(users.findIndex(u => u.id === 2));       // 1

Note find() returns the first match or undefined. findLast() (ES2023) searches from the end. Both return the element, not a boolean.

Sorting

syntax
arr.sort(compareFn)
arr.toSorted(compareFn)
example
const nums = [40, 1, 5, 200];
// WRONG: default sort is lexicographic
console.log([...nums].sort());             // [1, 200, 40, 5]
// CORRECT: numeric sort
console.log([...nums].sort((a, b) => a - b)); // [1, 5, 40, 200]

// Non-mutating sort (ES2023)
const sorted = nums.toSorted((a, b) => b - a);
console.log(sorted); // [200, 40, 5, 1]
console.log(nums);   // [40, 1, 5, 200] (unchanged)

Note sort() MUTATES the array and defaults to string comparison. Always pass a compare function for numbers. Use toSorted() for a non-mutating alternative.

flat() and flatMap()

syntax
arr.flat(depth)
arr.flatMap(callback)
example
const nested = [[1, 2], [3, [4, 5]]];
console.log(nested.flat());    // [1, 2, 3, [4, 5]]
console.log(nested.flat(2));   // [1, 2, 3, 4, 5]

const sentences = ["hello world", "good morning"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["hello", "world", "good", "morning"]

Note flatMap() is equivalent to map() followed by flat(1) but more efficient. Use flat(Infinity) to flatten any depth.

includes(), some(), every()

syntax
arr.includes(value)
arr.some(callback)
arr.every(callback)
example
const perms = ["read", "write", "delete"];
console.log(perms.includes("write"));  // true

const ages = [22, 17, 30, 15];
console.log(ages.some(a => a < 18));   // true
console.log(ages.every(a => a >= 18)); // false

Note includes() uses strict equality (===) and works with NaN. some() short-circuits on the first true; every() short-circuits on the first false.

Non-Mutating Alternatives (ES2023)

syntax
arr.toSorted(fn)
arr.toReversed()
arr.toSpliced(start, deleteCount, ...items)
arr.with(index, value)
example
const original = [3, 1, 4, 1, 5];

const reversed = original.toReversed();
console.log(reversed);  // [5, 1, 4, 1, 3]

const replaced = original.with(2, 99);
console.log(replaced);  // [3, 1, 99, 1, 5]

console.log(original);  // [3, 1, 4, 1, 5] (untouched)

Note These return new arrays, leaving the original unchanged. Ideal for immutable state patterns (React, Redux, etc).

Deep Cloning Arrays

syntax
structuredClone(value)
example
const original = [
  { name: "Ava", scores: [90, 85] },
  { name: "Leo", scores: [78, 92] },
];
const clone = structuredClone(original);
clone[0].scores.push(100);

console.log(original[0].scores); // [90, 85] (not affected)
console.log(clone[0].scores);    // [90, 85, 100]

Note structuredClone handles nested objects, arrays, Maps, Sets, Dates, RegExps, and more. Does NOT clone functions, DOM nodes, or prototypes.

Objects

Creating Objects

syntax
const obj = { key: value };
const obj = Object.create(proto);
example
const user = {
  name: "Kai",
  age: 30,
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};
console.log(user.greet());
output
"Hi, I'm Kai"

Note Method shorthand greet() {} is preferred over greet: function() {}. Arrow functions should not be used as methods because they do not bind their own this.

Accessing Properties

syntax
obj.property
obj["property"]
obj[variable]
example
const config = { "max-retries": 3, timeout: 5000 };
console.log(config.timeout);          // 5000
console.log(config["max-retries"]);   // 3

const key = "timeout";
console.log(config[key]);             // 5000

Note Use bracket notation for dynamic keys or keys with special characters. Dot notation is cleaner for static, valid-identifier keys.

Property Shorthand and Computed Keys

syntax
const obj = { name, [expression]: value };
example
const name = "Mira";
const role = "admin";
const user = { name, role };
console.log(user); // { name: "Mira", role: "admin" }

const field = "email";
const form = { [field]: "mira@example.com" };
console.log(form); // { email: "mira@example.com" }

Note Shorthand works when the variable name matches the desired key name. Computed keys are evaluated at runtime.

Object Destructuring in Function Parameters

syntax
function fn({ key1, key2 = default }) {}
example
function createUser({ name, role = "viewer", active = true }) {
  return { name, role, active, createdAt: Date.now() };
}

const admin = createUser({ name: "Bo", role: "admin" });
console.log(admin);
output
{ name: "Bo", role: "admin", active: true, createdAt: ... }

Note Destructuring in parameters makes function signatures self-documenting. Add = {} as a default to allow calling with no arguments: function fn({ x } = {}).

Object.keys / values / entries

syntax
Object.keys(obj)
Object.values(obj)
Object.entries(obj)
example
const inventory = { apples: 5, bananas: 12, cherries: 30 };

console.log(Object.keys(inventory));    // ["apples", "bananas", "cherries"]
console.log(Object.values(inventory));  // [5, 12, 30]

for (const [fruit, count] of Object.entries(inventory)) {
  console.log(`${fruit}: ${count}`);
}

Note These only return own enumerable string-keyed properties. Symbol keys and inherited properties are excluded.

Merging Objects

syntax
Object.assign(target, ...sources)
{ ...obj1, ...obj2 }
example
const defaults = { theme: "light", lang: "en", debug: false };
const userPrefs = { theme: "dark", debug: true };

const settings = { ...defaults, ...userPrefs };
console.log(settings);
output
{ theme: "dark", lang: "en", debug: true }

Note Both approaches are shallow. Later sources overwrite earlier ones. Use structuredClone for deep merging or a library for recursive merge.

Freezing and Sealing Objects

syntax
Object.freeze(obj)
Object.seal(obj)
Object.isFrozen(obj)
example
const API = Object.freeze({
  BASE_URL: "https://api.example.com",
  VERSION: 2,
});
API.VERSION = 3;            // silently fails (throws in strict mode)
console.log(API.VERSION);   // 2

Note freeze() prevents all changes. seal() allows modifying existing properties but not adding/removing. Both are shallow -- nested objects remain mutable.

Checking Property Existence

syntax
Object.hasOwn(obj, prop)
"prop" in obj
example
const user = { name: "Lina", age: 28 };

console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "email")); // false
console.log("toString" in user);           // true (inherited)
console.log(Object.hasOwn(user, "toString")); // false

Note Object.hasOwn() (ES2022) replaces obj.hasOwnProperty(). It works even if the object was created with Object.create(null).

Object.fromEntries()

syntax
Object.fromEntries(iterable)
example
const params = new URLSearchParams("page=2&sort=name&order=asc");
const queryObj = Object.fromEntries(params);
console.log(queryObj);

// Transform values
const prices = { shirt: 25, jacket: 120, cap: 15 };
const discounted = Object.fromEntries(
  Object.entries(prices).map(([item, price]) => [item, price * 0.9])
);
console.log(discounted);
output
{ page: "2", sort: "name", order: "asc" }
{ shirt: 22.5, jacket: 108, cap: 13.5 }

Note The inverse of Object.entries(). Takes any iterable of [key, value] pairs including Maps.

Deep Cloning Objects

syntax
structuredClone(obj)
example
const original = {
  user: { name: "Kai", tags: ["admin", "editor"] },
  updatedAt: new Date(),
};
const clone = structuredClone(original);
clone.user.tags.push("viewer");

console.log(original.user.tags); // ["admin", "editor"]

Note Handles circular references, Dates, Maps, Sets, ArrayBuffers, and more. Does NOT clone functions, Errors, or DOM nodes.

Functions

Function Declaration

syntax
function name(params) { ... }
example
function calculateTip(bill, tipPercent = 18) {
  return +(bill * tipPercent / 100).toFixed(2);
}
console.log(calculateTip(85));     // 15.3
console.log(calculateTip(85, 20)); // 17

Note Declarations are hoisted -- they can be called before they appear in code. This is the only function form that hoists.

Arrow Functions

syntax
const fn = (params) => expression;
const fn = (params) => { ... };
example
const double = n => n * 2;
console.log(double(7)); // 14

const greet = (name, greeting = "Hello") => `${greeting}, ${name}!`;
console.log(greet("Ava")); // "Hello, Ava!"

// Return an object literal (wrap in parentheses)
const makeUser = (name, id) => ({ name, id });
console.log(makeUser("Bo", 1));

Note Arrow functions have no own this, arguments, or super. They inherit this from the surrounding scope, making them unsuitable as object methods or constructors.

Default Parameters

syntax
function fn(param = defaultValue) {}
example
function fetchData(url, options = {}) {
  const { method = "GET", timeout = 3000 } = options;
  console.log(`${method} ${url} (timeout: ${timeout}ms)`);
}
fetchData("/api/users");
fetchData("/api/users", { method: "POST" });
output
"GET /api/users (timeout: 3000ms)"
"POST /api/users (timeout: 3000ms)"

Note Defaults are evaluated at call time, not definition time. Each call creates a fresh default value, so objects/arrays as defaults are safe.

Rest Parameters in Functions

syntax
function fn(first, ...rest) {}
example
function logTagged(level, ...messages) {
  const timestamp = new Date().toISOString();
  console.log(`[${level}] ${timestamp}:`, ...messages);
}
logTagged("INFO", "Server started", "on port 3000");
output
[INFO] 2026-04-04T...: Server started on port 3000

Note Rest parameters collect remaining arguments into a real Array. Unlike arguments, they work in arrow functions too.

Closures

syntax
function outer() {
  let state = value;
  return function inner() { /* access state */ };
}
example
function createCounter(initial = 0) {
  let count = initial;
  return {
    increment() { return ++count; },
    decrement() { return --count; },
    value()     { return count; },
  };
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.value());     // 12

Note A closure is a function that retains access to its outer scope's variables even after the outer function has returned. Fundamental for data privacy and stateful functions.

IIFE (Immediately Invoked Function Expression)

syntax
(function() { ... })();
(() => { ... })();
example
const api = (() => {
  let requestCount = 0;
  return {
    fetch(url) {
      requestCount++;
      console.log(`Request #${requestCount}: ${url}`);
    },
    getCount() { return requestCount; }
  };
})();
api.fetch("/users");
console.log(api.getCount());
output
"Request #1: /users"
1

Note IIFEs were essential before modules for avoiding global scope pollution. Still useful for one-time initialization blocks.

Generator Functions

syntax
function* name() { yield value; }
example
function* idGenerator(start = 1) {
  let id = start;
  while (true) {
    yield id++;
  }
}
const ids = idGenerator(100);
console.log(ids.next().value); // 100
console.log(ids.next().value); // 101
console.log(ids.next().value); // 102

Note Generators are lazy -- they produce values on demand. Execution pauses at each yield and resumes when next() is called.

Async Generator Functions

syntax
async function* name() { yield await value; }
example
async function* fetchPages(baseUrl, maxPages = 3) {
  for (let page = 1; page <= maxPages; page++) {
    const res = await fetch(`${baseUrl}?page=${page}`);
    yield await res.json();
  }
}

// Usage
for await (const pageData of fetchPages("/api/posts")) {
  console.log(`Got ${pageData.items.length} items`);
}

Note Consume with for-await-of. Ideal for paginated APIs, streaming data, or any sequence of async operations.

Higher-Order Functions

syntax
function fn(callback) { callback(); }
function fn() { return function() {}; }
example
function withLogging(fn) {
  return function(...args) {
    console.log(`Calling ${fn.name} with`, args);
    const result = fn(...args);
    console.log(`Result:`, result);
    return result;
  };
}

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(3, 4);
output
"Calling add with [3, 4]"
"Result: 7"

Note Functions that accept or return other functions. The backbone of functional patterns like decorators, middleware, and composition.

Control Flow

if / else if / else

syntax
if (condition) { ... }
else if (condition) { ... }
else { ... }
example
function getDiscount(memberLevel) {
  if (memberLevel === "gold") {
    return 0.20;
  } else if (memberLevel === "silver") {
    return 0.10;
  } else {
    return 0;
  }
}
console.log(getDiscount("gold")); // 0.20

Note Conditions are coerced to boolean. Watch out for truthy/falsy gotchas: if (arr.length) works because 0 is falsy.

Ternary Operator

syntax
condition ? valueIfTrue : valueIfFalse
example
const age = 20;
const category = age >= 18 ? "adult" : "minor";
console.log(category); // "adult"

// Nested (use sparingly)
const tier = age >= 65 ? "senior" : age >= 18 ? "adult" : "minor";

Note Great for simple inline conditions. Avoid deeply nested ternaries -- they quickly become unreadable. Use if/else for complex logic.

switch Statement

syntax
switch (expression) {
  case value: ... break;
  default: ...
}
example
function getStatusText(code) {
  switch (code) {
    case 200: return "OK";
    case 301: return "Moved Permanently";
    case 404: return "Not Found";
    case 500: return "Internal Server Error";
    default:  return `Unknown (${code})`;
  }
}
console.log(getStatusText(404));
output
"Not Found"

Note Uses strict equality (===). Forgetting break causes fall-through to the next case. Using return instead of break is a clean pattern in functions.

for Loop

syntax
for (init; condition; update) { ... }
example
const items = ["apple", "banana", "cherry"];
for (let i = 0; i < items.length; i++) {
  console.log(`${i + 1}. ${items[i]}`);
}
output
"1. apple"
"2. banana"
"3. cherry"

Note Classic loop when you need the index. For simple iteration, prefer for...of. Cache .length in the initializer if the array is very large and not changing.

for...of Loop

syntax
for (const element of iterable) { ... }
example
const scores = [88, 92, 75, 100];
let sum = 0;
for (const score of scores) {
  sum += score;
}
console.log(`Average: ${sum / scores.length}`);

// Works with strings too
for (const char of "hello") {
  process(char);
}
output
"Average: 88.75"

Note Works on any iterable: arrays, strings, Maps, Sets, generators. Does NOT work on plain objects -- use Object.entries() or for...in for those.

for...in Loop

syntax
for (const key in object) { ... }
example
const theme = { primary: "#3498db", secondary: "#2ecc71", accent: "#e74c3c" };
for (const colorName in theme) {
  console.log(`${colorName}: ${theme[colorName]}`);
}
output
"primary: #3498db"
"secondary: #2ecc71"
"accent: #e74c3c"

Note Iterates over enumerable string properties, including inherited ones. Avoid for arrays (use for...of). Use Object.hasOwn() to filter inherited keys.

while and do...while

syntax
while (condition) { ... }
do { ... } while (condition);
example
// Retry until success (with limit)
let attempts = 0;
let success = false;
while (!success && attempts < 5) {
  attempts++;
  success = Math.random() > 0.7;
}
console.log(`Succeeded after ${attempts} attempt(s): ${success}`);

// do...while always runs at least once
let input;
do {
  input = prompt("Enter a number > 10:");
} while (Number(input) <= 10);

Note do...while guarantees at least one iteration. Useful for retry logic and input validation loops.

break and continue

syntax
break;     // exit loop
continue;  // skip to next iteration
example
const data = [3, -1, 7, 0, 5, -3, 9];
const positives = [];

for (const n of data) {
  if (n < 0) continue;  // skip negatives
  if (positives.length >= 3) break;  // stop after 3
  positives.push(n);
}
console.log(positives); // [3, 7, 5]

Note break exits the innermost loop. Use labeled breaks to exit outer loops: outer: for (...) { for (...) { break outer; } }

Labeled Statements

syntax
label: for (...) { break label; }
example
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

let found = null;
search: for (const row of matrix) {
  for (const cell of row) {
    if (cell === 5) {
      found = cell;
      break search;
    }
  }
}
console.log(found); // 5

Note Labels let you break/continue a specific outer loop from inside a nested loop. Rarely needed but invaluable for searching nested structures.

Async Programming

Promise Basics

syntax
new Promise((resolve, reject) => { ... })
promise.then(onFulfilled, onRejected)
promise.catch(onRejected)
promise.finally(onFinally)
example
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000)
  .then(() => console.log("1 second passed"))
  .catch(err => console.error(err))
  .finally(() => console.log("Done"));

Note A Promise is always in one of three states: pending, fulfilled, or rejected. Once settled, it cannot change state.

async / await

syntax
async function name() {
  const result = await promise;
}
example
async function loadUser(userId) {
  const response = await fetch(`/api/users/${userId}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const user = await response.json();
  return user;
}

// Usage
try {
  const user = await loadUser(42);
  console.log(user.name);
} catch (err) {
  console.error("Failed to load user:", err.message);
}

Note await pauses execution inside an async function until the promise settles. Top-level await works in ES modules. Always handle errors with try/catch or .catch().

Promise.all()

syntax
Promise.all([p1, p2, p3])
example
async function loadDashboard(userId) {
  const [profile, posts, notifications] = await Promise.all([
    fetch(`/api/users/${userId}`).then(r => r.json()),
    fetch(`/api/posts?author=${userId}`).then(r => r.json()),
    fetch(`/api/notifications/${userId}`).then(r => r.json()),
  ]);
  return { profile, posts, notifications };
}

Note Runs all promises in parallel and resolves when ALL succeed. Rejects immediately if ANY promise rejects -- other pending promises are not cancelled, just ignored.

Promise.allSettled()

syntax
Promise.allSettled([p1, p2, p3])
example
const results = await Promise.allSettled([
  fetch("/api/service-a"),
  fetch("/api/service-b"),
  fetch("/api/service-c"),
]);

const successful = results.filter(r => r.status === "fulfilled");
const failed = results.filter(r => r.status === "rejected");
console.log(`${successful.length} succeeded, ${failed.length} failed`);

Note Never rejects. Each result is { status: "fulfilled", value } or { status: "rejected", reason }. Use when you want all results regardless of failures.

Promise.race()

syntax
Promise.race([p1, p2, p3])
example
function fetchWithTimeout(url, ms = 5000) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timeout")), ms)
  );
  return Promise.race([fetch(url), timeout]);
}

try {
  const response = await fetchWithTimeout("/api/data", 3000);
  console.log(await response.json());
} catch (err) {
  console.error(err.message); // "Timeout" or network error
}

Note Settles as soon as the first promise settles (fulfilled OR rejected). Common use: implementing timeouts.

Promise.any()

syntax
Promise.any([p1, p2, p3])
example
async function fetchFromMirrors(path) {
  return Promise.any([
    fetch(`https://mirror1.example.com${path}`),
    fetch(`https://mirror2.example.com${path}`),
    fetch(`https://mirror3.example.com${path}`),
  ]);
}
// Returns the first successful response; ignores individual failures

Note Resolves with the first fulfilled promise. Only rejects if ALL promises reject (with an AggregateError). The opposite of Promise.race() for error handling.

Fetch API

syntax
const response = await fetch(url, options);
example
// GET request
const res = await fetch("/api/products");
const products = await res.json();

// POST request
const created = await fetch("/api/products", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Widget", price: 29.99 }),
});

if (!created.ok) throw new Error(`HTTP ${created.status}`);

Note fetch() does NOT reject on HTTP error status codes (404, 500). Always check response.ok or response.status. The body can only be consumed once.

AbortController

syntax
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort();
example
async function searchWithCancel(query) {
  const controller = new AbortController();

  // Auto-cancel after 5 seconds
  const timeoutId = setTimeout(() => controller.abort(), 5000);

  try {
    const res = await fetch(`/api/search?q=${query}`, {
      signal: controller.signal,
    });
    clearTimeout(timeoutId);
    return await res.json();
  } catch (err) {
    if (err.name === "AbortError") {
      console.log("Request was cancelled");
    } else {
      throw err;
    }
  }
}

Note Essential for cancelling stale requests (e.g., in search-as-you-type). The AbortSignal can be shared across multiple fetch calls to cancel them all at once.

Async Error Handling Patterns

syntax
try { await ... } catch (err) { ... }
example
// Wrapper to avoid repetitive try/catch
async function safeAwait(promise) {
  try {
    const data = await promise;
    return [data, null];
  } catch (err) {
    return [null, err];
  }
}

const [user, error] = await safeAwait(fetch("/api/me").then(r => r.json()));
if (error) {
  console.error("Failed:", error.message);
} else {
  console.log("User:", user.name);
}

Note Unhandled promise rejections crash Node.js and show warnings in browsers. Always catch async errors. The tuple pattern [data, error] is popular for cleaner control flow.

Classes & OOP

Class Declaration

syntax
class ClassName {
  constructor(params) { ... }
  method() { ... }
}
example
class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  format() {
    return `${this.name}: $${this.price.toFixed(2)}`;
  }
}

const item = new Product("Notebook", 12.5);
console.log(item.format());
output
"Notebook: $12.50"

Note Classes are syntactic sugar over prototypes. They are NOT hoisted -- you must declare before use, unlike function declarations.

Getters and Setters

syntax
get propertyName() { ... }
set propertyName(value) { ... }
example
class Temperature {
  #celsius;

  constructor(celsius) {
    this.#celsius = celsius;
  }

  get fahrenheit() {
    return this.#celsius * 9 / 5 + 32;
  }

  set fahrenheit(f) {
    this.#celsius = (f - 32) * 5 / 9;
  }

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

const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius);    // 0

Note Getters and setters look like regular properties from the outside. They are ideal for computed values, validation, and encapsulation.

Static Methods and Properties

syntax
class C {
  static method() { ... }
  static property = value;
}
example
class MathUtils {
  static PI = 3.14159265;

  static clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  static lerp(a, b, t) {
    return a + (b - a) * t;
  }
}

console.log(MathUtils.clamp(150, 0, 100)); // 100
console.log(MathUtils.lerp(0, 50, 0.5));  // 25

Note Static members belong to the class itself, not instances. Access them via ClassName.method(), not instance.method().

Inheritance (extends)

syntax
class Child extends Parent {
  constructor(params) {
    super(parentParams);
  }
}
example
class Shape {
  constructor(color) {
    this.color = color;
  }
  describe() {
    return `A ${this.color} shape`;
  }
}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);
    this.radius = radius;
  }
  get area() {
    return Math.PI * this.radius ** 2;
  }
  describe() {
    return `A ${this.color} circle (r=${this.radius})`;
  }
}

const c = new Circle("blue", 5);
console.log(c.describe());
console.log(c.area.toFixed(2));
output
"A blue circle (r=5)"
"78.54"

Note super() must be called in the child constructor before accessing this. Methods can be overridden. Use super.method() to call the parent version.

Private Fields and Methods

syntax
class C {
  #privateField = value;
  #privateMethod() { ... }
}
example
class BankAccount {
  #balance = 0;

  constructor(initial) {
    this.#balance = initial;
  }

  deposit(amount) {
    this.#validateAmount(amount);
    this.#balance += amount;
  }

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

  #validateAmount(amount) {
    if (amount <= 0) throw new Error("Amount must be positive");
  }
}

const acct = new BankAccount(100);
acct.deposit(50);
console.log(acct.balance);   // 150
// acct.#balance;  // SyntaxError

Note Private fields/methods use the # prefix. They are truly private -- not accessible outside the class, even by subclasses. instanceof checks still work.

Static Initialization Block

syntax
class C {
  static { /* initialization code */ }
}
example
class Config {
  static defaults;
  static env;

  static {
    Config.env = typeof window !== "undefined" ? "browser" : "node";
    Config.defaults = Config.env === "browser"
      ? { timeout: 10000, retries: 2 }
      : { timeout: 30000, retries: 5 };
  }
}

console.log(Config.env);
console.log(Config.defaults);

Note Static blocks run once when the class is evaluated. Useful for complex static initialization that cannot be done in a single expression.

instanceof and Type Checking

syntax
object instanceof ClassName
example
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

const pet = new Dog();
console.log(pet instanceof Dog);    // true
console.log(pet instanceof Animal); // true
console.log(pet instanceof Cat);    // false

Note Checks the prototype chain. Can be unreliable across iframes or realms. For built-in types, prefer Array.isArray() over instanceof Array.

Mixins Pattern

syntax
const Mixin = (Base) => class extends Base { ... };
example
const Serializable = (Base) => class extends Base {
  toJSON() {
    return JSON.stringify({ ...this });
  }
};

const Timestamped = (Base) => class extends Base {
  constructor(...args) {
    super(...args);
    this.createdAt = new Date();
  }
};

class User extends Timestamped(Serializable(Object)) {
  constructor(name) {
    super();
    this.name = name;
  }
}

const user = new User("Kai");
console.log(user.toJSON());

Note JavaScript has single inheritance only. Mixins simulate multiple inheritance by composing class factories. Order matters -- later mixins override earlier ones.

Modules

Named Exports

syntax
export const name = value;
export function fn() { ... }
export { a, b, c };
example
// mathUtils.js
export const PI = 3.14159;

export function areaOfCircle(radius) {
  return PI * radius ** 2;
}

export function circumference(radius) {
  return 2 * PI * radius;
}

Note Named exports can be many per module. They must be imported by exact name (or renamed with 'as'). Prefer named exports for better tree-shaking.

Named Imports

syntax
import { name1, name2 } from "./module.js";
import { name as alias } from "./module.js";
example
import { areaOfCircle, circumference as circ } from "./mathUtils.js";

console.log(areaOfCircle(10));
console.log(circ(10));

Note Imports are live bindings -- they reflect the current value in the exporting module. They are also read-only; you cannot reassign an imported binding.

Default Export and Import

syntax
export default expression;
import anyName from "./module.js";
example
// logger.js
export default class Logger {
  constructor(prefix) {
    this.prefix = prefix;
  }
  log(msg) {
    console.log(`[${this.prefix}] ${msg}`);
  }
}

// app.js
import Logger from "./logger.js";
const log = new Logger("App");
log.log("Started");
output
[App] Started

Note Only one default export per module. Can be imported with any name. You can combine default and named imports: import Logger, { version } from "./logger.js".

Dynamic Import

syntax
const module = await import("./module.js");
example
async function loadEditor() {
  const { EditorView } = await import("./editor.js");
  return new EditorView(document.getElementById("editor"));
}

// Conditional loading
if (needsCharting) {
  const { renderChart } = await import("./charts.js");
  renderChart(data);
}

Note Returns a promise that resolves to the module namespace. Ideal for code-splitting, lazy loading, and conditional imports. Works at runtime, not just at the top of a file.

Re-exporting

syntax
export { name } from "./module.js";
export { default } from "./module.js";
export * from "./module.js";
example
// components/index.js (barrel file)
export { Button } from "./Button.js";
export { Modal } from "./Modal.js";
export { Tooltip } from "./Tooltip.js";

// Consumer
import { Button, Modal } from "./components/index.js";

Note Barrel files simplify imports but can hurt tree-shaking if not handled carefully. Modern bundlers mitigate this, but be aware of the tradeoff.

import.meta

syntax
import.meta.url
import.meta.resolve(specifier)
example
// Get the URL of the current module
console.log(import.meta.url);
// "file:///project/src/utils.js"

// Resolve a relative path
const configPath = new URL("./config.json", import.meta.url);
console.log(configPath.href);

Note import.meta provides module-specific metadata. In Node.js, use import.meta.dirname and import.meta.filename (Node 21+) as replacements for __dirname and __filename.

Namespace Import

syntax
import * as name from "./module.js";
example
import * as validators from "./validators.js";

const email = "test@example.com";
console.log(validators.isEmail(email));
console.log(validators.isNotEmpty(email));

Note Imports all named exports as a single object. The object is frozen (read-only). Useful when a module has many exports you want to use together.

Top-Level await

syntax
// In an ES module
const data = await fetchData();
example
// config.js (ES module)
const response = await fetch("/api/config");
export const config = await response.json();

// app.js
import { config } from "./config.js";
console.log(config.appName); // Available immediately

Note Only works in ES modules (not CommonJS). Importing modules that use top-level await will block until that await resolves, so use judiciously.

Error Handling

try / catch / finally

syntax
try { ... }
catch (error) { ... }
finally { ... }
example
function parseConfig(jsonString) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error("Invalid config JSON:", error.message);
    return {};
  } finally {
    console.log("Config parsing attempted.");
  }
}

const config = parseConfig('{"debug": true}');
console.log(config);
output
"Config parsing attempted."
{ debug: true }

Note finally always runs, whether the try succeeded or catch was triggered. It even runs if try or catch contains a return statement.

Throwing Errors

syntax
throw new Error(message);
throw new TypeError(message);
example
function withdraw(account, amount) {
  if (amount <= 0) {
    throw new RangeError("Withdrawal amount must be positive");
  }
  if (amount > account.balance) {
    throw new Error("Insufficient funds");
  }
  account.balance -= amount;
  return account.balance;
}

Note Always throw Error objects (not strings) so you get a stack trace. Use specific error types (TypeError, RangeError) when appropriate.

Custom Error Classes

syntax
class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = "CustomError";
  }
}
example
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} with id ${id} not found`);
    this.name = "NotFoundError";
    this.resource = resource;
    this.id = id;
  }
}

try {
  throw new ValidationError("email", "Invalid email format");
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Field: ${err.field}, Message: ${err.message}`);
  }
}
output
"Field: email, Message: Invalid email format"

Note Custom errors let you add context (which field, which resource) and enable precise catch handling with instanceof checks.

Error Cause (error.cause)

syntax
throw new Error(message, { cause: originalError });
example
async function loadUserData(userId) {
  try {
    const res = await fetch(`/api/users/${userId}`);
    return await res.json();
  } catch (err) {
    throw new Error("Failed to load user data", { cause: err });
  }
}

try {
  await loadUserData(42);
} catch (err) {
  console.error(err.message);        // "Failed to load user data"
  console.error(err.cause?.message);  // original error message
}

Note The cause option (ES2022) lets you wrap errors while preserving the original. Essential for debugging errors that propagate through layers.

Built-in Error Types

syntax
TypeError | RangeError | ReferenceError | SyntaxError | URIError | EvalError | AggregateError
example
// TypeError - wrong type for an operation
try { null.toString(); }
catch (e) { console.log(e.constructor.name); } // "TypeError"

// RangeError - value out of allowed range
try { new Array(-1); }
catch (e) { console.log(e.constructor.name); } // "RangeError"

// ReferenceError - undeclared variable
try { console.log(undeclaredVar); }
catch (e) { console.log(e.constructor.name); } // "ReferenceError"

Note Catching specific types helps you handle known errors differently from unexpected ones. AggregateError wraps multiple errors (used by Promise.any()).

Optional Catch Binding

syntax
try { ... } catch { ... }
example
function tryParseJSON(str) {
  try {
    return JSON.parse(str);
  } catch {
    return null;
  }
}

console.log(tryParseJSON('{"valid": true}'));  // { valid: true }
console.log(tryParseJSON("not json"));         // null

Note Since ES2019, you can omit the error parameter in catch if you do not need it. Keeps code cleaner for "swallow and fallback" patterns.

Centralized Async Error Handling

syntax
process.on('unhandledRejection', handler)
window.addEventListener('unhandledrejection', handler)
example
// Browser global handler
window.addEventListener("unhandledrejection", (event) => {
  console.error("Unhandled rejection:", event.reason);
  event.preventDefault(); // prevents console error
  reportToService(event.reason);
});

// Node.js global handler
process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled rejection at:", promise, "reason:", reason);
});

Note Global handlers are your safety net -- they should log and report, not silently swallow errors. Always prefer local try/catch for known async operations.

Working with Stack Traces

syntax
error.stack
error.message
error.name
example
function deepFunction() {
  throw new Error("Something broke");
}

try {
  deepFunction();
} catch (err) {
  console.log(err.name);     // "Error"
  console.log(err.message);  // "Something broke"
  console.log(err.stack);    // Full stack trace string
}

Note err.stack is not standardized but is supported everywhere in practice. It includes the error message and the call chain leading to where the error was created.

DOM Manipulation

Selecting Elements

syntax
document.querySelector(selector)
document.querySelectorAll(selector)
document.getElementById(id)
example
const header = document.querySelector("h1");
const buttons = document.querySelectorAll(".btn");
const main = document.getElementById("main-content");

// querySelectorAll returns a static NodeList
buttons.forEach(btn => {
  console.log(btn.textContent);
});

Note querySelector returns the first match or null. querySelectorAll returns a static NodeList (does not auto-update). Use Array.from() if you need full array methods.

Creating and Inserting Elements

syntax
document.createElement(tag)
parent.appendChild(child)
parent.append(...nodes)
element.insertAdjacentHTML(position, html)
example
const card = document.createElement("div");
card.className = "card";

const title = document.createElement("h2");
title.textContent = "New Item";

const desc = document.createElement("p");
desc.textContent = "Description goes here.";

card.append(title, desc);
document.getElementById("container").appendChild(card);

Note append() accepts multiple nodes and strings. appendChild() only accepts one node. Prefer textContent over innerHTML to prevent XSS when inserting user-provided text.

Event Listeners

syntax
element.addEventListener(event, handler, options)
element.removeEventListener(event, handler)
example
const button = document.querySelector("#submit-btn");

function handleClick(event) {
  event.preventDefault();
  console.log("Button clicked!", event.target);
}

button.addEventListener("click", handleClick);

// One-time listener
button.addEventListener("click", () => {
  console.log("This fires only once");
}, { once: true });

Note The third argument can be a boolean (useCapture) or an options object { capture, once, passive, signal }. Use { once: true } for one-shot handlers.

Event Delegation

syntax
parent.addEventListener(event, (e) => {
  if (e.target.matches(selector)) { ... }
});
example
const todoList = document.querySelector("#todo-list");

todoList.addEventListener("click", (event) => {
  const deleteBtn = event.target.closest(".delete-btn");
  if (deleteBtn) {
    const item = deleteBtn.closest(".todo-item");
    item.remove();
  }
});

Note Attach one listener to a parent instead of many on children. Works for dynamically added elements too. Use closest() to find the nearest matching ancestor.

classList Operations

syntax
el.classList.add(cls)
el.classList.remove(cls)
el.classList.toggle(cls)
el.classList.contains(cls)
el.classList.replace(old, new)
example
const panel = document.querySelector(".panel");

panel.classList.add("visible", "animated");
panel.classList.remove("hidden");
panel.classList.toggle("expanded");

if (panel.classList.contains("visible")) {
  console.log("Panel is visible");
}

panel.classList.replace("animated", "static");

Note classList methods are cleaner and safer than manipulating className directly. toggle() returns true if the class was added, false if removed.

Data Attributes (dataset)

syntax
element.dataset.key
// reads data-key="value" from HTML
example
// HTML: <button data-action="save" data-item-id="42">Save</button>
const btn = document.querySelector("button");

console.log(btn.dataset.action);  // "save"
console.log(btn.dataset.itemId);  // "42" (camelCase conversion)

btn.dataset.status = "pending"; // sets data-status="pending"

Note data-kebab-case becomes camelCase in JavaScript: data-item-id -> dataset.itemId. All dataset values are strings; parse numbers manually.

innerHTML vs textContent

syntax
element.innerHTML = htmlString;
element.textContent = plainText;
example
const output = document.querySelector("#output");

// textContent: safe, treats everything as text
output.textContent = "<b>Hello</b>"; // shows literal "<b>Hello</b>"

// innerHTML: parses HTML (XSS risk with user input!)
output.innerHTML = "<b>Hello</b>";   // shows bold Hello

// Safe alternative for HTML
const tmpl = document.createElement("template");
tmpl.innerHTML = "<b>Hello</b>";
output.append(tmpl.content.cloneNode(true));

Note NEVER use innerHTML with unsanitized user input -- it is the #1 source of XSS vulnerabilities. Use textContent for plain text and createElement for dynamic HTML.

DOM Traversal

syntax
el.parentElement
el.children
el.firstElementChild
el.nextElementSibling
el.closest(selector)
example
const item = document.querySelector(".active-item");

console.log(item.parentElement);        // parent
console.log(item.children);             // HTMLCollection of children
console.log(item.nextElementSibling);   // next sibling element
console.log(item.closest(".container")); // nearest ancestor matching selector

Note Use element-based properties (parentElement, children) over node-based ones (parentNode, childNodes) to skip text/comment nodes.

Removing Elements

syntax
element.remove()
parent.removeChild(child)
example
// Modern: element removes itself
const notification = document.querySelector(".notification");
notification.remove();

// Remove all children
const container = document.querySelector("#list");
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

// Faster: clear all children
container.replaceChildren();

Note el.remove() is cleaner than parentNode.removeChild(el). Use replaceChildren() with no arguments to efficiently clear all children.

Modern Features

structuredClone()

syntax
const clone = structuredClone(value);
example
const original = {
  name: "Ava",
  scores: [95, 88, 72],
  metadata: { joined: new Date("2024-01-15") },
};
const clone = structuredClone(original);
clone.scores.push(100);
clone.metadata.joined.setFullYear(2025);

console.log(original.scores);          // [95, 88, 72]
console.log(original.metadata.joined); // 2024-01-15 (unchanged)

Note Built-in deep clone that handles Dates, Maps, Sets, RegExps, ArrayBuffers, circular references. Cannot clone functions, DOM nodes, or Error objects.

Array/String .at() Method

syntax
arr.at(index)
str.at(index)
example
const stack = ["first", "second", "third", "last"];
console.log(stack.at(0));   // "first"
console.log(stack.at(-1));  // "last"
console.log(stack.at(-2));  // "third"

console.log("hello".at(-1)); // "o"

Note Works on Arrays, Strings, and TypedArrays. The main advantage over bracket notation is support for negative indices.

findLast() and findLastIndex()

syntax
arr.findLast(callback)
arr.findLastIndex(callback)
example
const transactions = [
  { type: "credit", amount: 500 },
  { type: "debit", amount: 200 },
  { type: "credit", amount: 300 },
  { type: "debit", amount: 150 },
];

const lastCredit = transactions.findLast(t => t.type === "credit");
console.log(lastCredit); // { type: "credit", amount: 300 }

const lastCreditIdx = transactions.findLastIndex(t => t.type === "credit");
console.log(lastCreditIdx); // 2

Note ES2023. Searches from the end of the array. More readable and efficient than reversing first or using a manual reverse loop.

Object.groupBy()

syntax
Object.groupBy(iterable, keyFn)
example
const people = [
  { name: "Ava", department: "Engineering" },
  { name: "Bo", department: "Design" },
  { name: "Cara", department: "Engineering" },
  { name: "Dan", department: "Design" },
  { name: "Eve", department: "Marketing" },
];

const byDept = Object.groupBy(people, p => p.department);
console.log(byDept.Engineering);
// [{ name: "Ava", ... }, { name: "Cara", ... }]
console.log(Object.keys(byDept));
// ["Engineering", "Design", "Marketing"]

Note ES2024. Returns a null-prototype object. For Map output, use Map.groupBy() instead. Replaces the common reduce-based grouping pattern.

Map.groupBy()

syntax
Map.groupBy(iterable, keyFn)
example
const items = [
  { name: "Apple", price: 1.2 },
  { name: "Steak", price: 15 },
  { name: "Bread", price: 2.5 },
  { name: "Salmon", price: 12 },
];

const byRange = Map.groupBy(items, item =>
  item.price > 10 ? "expensive" : "affordable"
);
console.log(byRange.get("expensive"));
// [{ name: "Steak"... }, { name: "Salmon"... }]

Note ES2024. Similar to Object.groupBy but returns a Map, which supports any key type (objects, symbols, etc.).

Set Methods (ES2025)

syntax
setA.union(setB)
setA.intersection(setB)
setA.difference(setB)
setA.symmetricDifference(setB)
setA.isSubsetOf(setB)
setA.isSupersetOf(setB)
setA.isDisjointFrom(setB)
example
const frontend = new Set(["js", "css", "html", "ts"]);
const backend = new Set(["js", "python", "go", "ts"]);

console.log(frontend.union(backend));
// Set {"js", "css", "html", "ts", "python", "go"}

console.log(frontend.intersection(backend));
// Set {"js", "ts"}

console.log(frontend.difference(backend));
// Set {"css", "html"}

console.log(frontend.isSubsetOf(backend)); // false

Note ES2025. All methods return new Sets (non-mutating). Works with any iterable argument, not just Sets. Replaces manual loop-based set operations.

Promise.withResolvers()

syntax
const { promise, resolve, reject } = Promise.withResolvers();
example
function createDeferred() {
  const { promise, resolve, reject } = Promise.withResolvers();
  return { promise, resolve, reject };
}

const deferred = createDeferred();

// Resolve from elsewhere
setTimeout(() => deferred.resolve("Done!"), 1000);

const result = await deferred.promise;
console.log(result); // "Done!"

Note ES2024. Eliminates the common pattern of extracting resolve/reject from the Promise constructor. Useful for event-based resolution and deferred patterns.

Iterator Helpers (ES2025)

syntax
iterator.map(fn)
iterator.filter(fn)
iterator.take(n)
iterator.drop(n)
iterator.flatMap(fn)
iterator.toArray()
example
function* naturals() {
  let n = 1;
  while (true) yield n++;
}

// Get first 5 even squares
const result = naturals()
  .map(n => n ** 2)
  .filter(n => n % 2 === 0)
  .take(5)
  .toArray();

console.log(result); // [4, 16, 36, 64, 100]

Note ES2025. Chainable lazy operations on any iterator. Unlike array methods, these process elements one at a time, making them memory-efficient for large or infinite sequences.

Explicit Resource Management (using)

syntax
using resource = acquireResource();
await using resource = acquireAsyncResource();
example
class TempFile {
  constructor(path) {
    this.path = path;
    console.log(`Created: ${path}`);
  }

  [Symbol.dispose]() {
    console.log(`Cleaned up: ${this.path}`);
  }
}

{
  using file = new TempFile("/tmp/data.txt");
  // Use file here...
} // Automatically calls file[Symbol.dispose]() when block exits
output
"Created: /tmp/data.txt"
"Cleaned up: /tmp/data.txt"

Note TC39 Stage 3 (expected in ES2025/2026). Similar to Python's 'with' or C#'s 'using'. Guarantees cleanup even if an error is thrown. Use Symbol.asyncDispose for async cleanup.

RegExp v Flag (Unicode Sets)

syntax
/pattern/v
example
// Match any emoji using Unicode property
const emojiPattern = /\p{Emoji_Presentation}/v;
console.log(emojiPattern.test("hello")); // false
console.log(emojiPattern.test("hi [U+D83D][U+DC4B]")); // true

// Set operations in character classes
const greekLetters = /[\p{Script=Greek}&&\p{Letter}]/v;
console.log(greekLetters.test("α")); // true

Note ES2024. The v flag replaces the u flag with enhanced Unicode support and set operations (&&, --) inside character classes.

Common Patterns

Debounce

syntax
function debounce(fn, delay) { ... }
example
function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage: only fire after user stops typing for 300ms
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input",
  debounce((e) => {
    console.log("Searching:", e.target.value);
  }, 300)
);

Note Debouncing delays execution until activity stops for a given period. Ideal for search inputs, window resize handlers, and form validation.

Throttle

syntax
function throttle(fn, interval) { ... }
example
function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage: fire at most once per 200ms during scroll
window.addEventListener("scroll",
  throttle(() => {
    console.log("Scroll position:", window.scrollY);
  }, 200)
);

Note Throttling ensures a function runs at most once per interval, even if triggered constantly. Use for scroll, mousemove, and resize events.

Memoization

syntax
function memoize(fn) { ... }
example
function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalc = memoize((n) => {
  console.log("Computing...");
  return n ** 2 + Math.sqrt(n);
});

console.log(expensiveCalc(100)); // "Computing..." then 10010
console.log(expensiveCalc(100)); // 10010 (cached, no log)

Note Caches results for repeated calls with the same arguments. Use JSON.stringify for simple keys. For complex args, consider a WeakMap-based approach.

Currying

syntax
const curried = (a) => (b) => (c) => result;
example
const createFormatter = (currency) => (locale) => (amount) =>
  new Intl.NumberFormat(locale, {
    style: "currency", currency
  }).format(amount);

const formatUSD = createFormatter("USD")("en-US");
const formatEUR = createFormatter("EUR")("de-DE");

console.log(formatUSD(1234.5));  // "$1,234.50"
console.log(formatEUR(1234.5));  // "1.234,50 €"

Note Currying transforms a function of multiple arguments into a chain of single-argument functions. Enables partial application and reusable configurations.

Singleton Pattern

syntax
class Singleton { static #instance; static getInstance() {} }
example
class Database {
  static #instance;
  #connection;

  constructor() {
    if (Database.#instance) {
      throw new Error("Use Database.getInstance()");
    }
    this.#connection = "connected";
  }

  static getInstance() {
    if (!Database.#instance) {
      Database.#instance = new Database();
    }
    return Database.#instance;
  }

  query(sql) {
    console.log(`[${this.#connection}] Executing: ${sql}`);
  }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true

Note Ensures only one instance of a class exists. In modern JavaScript, ES modules are natural singletons -- an exported object is shared across all importers.

Observer / Event Emitter Pattern

syntax
class EventEmitter { on(event, handler) {} emit(event, data) {} }
example
class EventBus {
  #handlers = new Map();

  on(event, handler) {
    if (!this.#handlers.has(event)) {
      this.#handlers.set(event, new Set());
    }
    this.#handlers.get(event).add(handler);
    return () => this.off(event, handler); // unsubscribe fn
  }

  off(event, handler) {
    this.#handlers.get(event)?.delete(handler);
  }

  emit(event, data) {
    this.#handlers.get(event)?.forEach(fn => fn(data));
  }
}

const bus = new EventBus();
const unsub = bus.on("user:login", (user) => console.log(`Welcome, ${user}`));
bus.emit("user:login", "Ava"); // "Welcome, Ava"
unsub(); // unsubscribes

Note The observer pattern decouples event producers from consumers. Returning an unsubscribe function from on() prevents memory leaks.

Pipe / Compose

syntax
const pipe = (...fns) => (x) => fns.reduce((v, fn) => fn(v), x);
example
const pipe = (...fns) => (x) => fns.reduce((v, fn) => fn(v), x);

const sanitize = pipe(
  (str) => str.trim(),
  (str) => str.toLowerCase(),
  (str) => str.replace(/[^a-z0-9\s-]/g, ""),
  (str) => str.replace(/\s+/g, "-")
);

console.log(sanitize("  Hello World! @2026  "));
// "hello-world-2026"

Note pipe() applies functions left-to-right; compose() applies right-to-left. Pipe is more intuitive for data transformation chains.

Retry with Exponential Backoff

syntax
async function retry(fn, attempts, delay) { ... }
example
async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxAttempts) throw err;
      const delay = baseDelay * 2 ** (attempt - 1);
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

// Usage
const data = await retry(() => fetch("/api/flaky-endpoint").then(r => r.json()));

Note Exponential backoff (1s, 2s, 4s...) prevents overwhelming failing services. Add jitter (random offset) in production to avoid thundering herd.

Common Mistakes

== vs === Confusion

syntax
// Wrong: == with type coercion
// Right: === for strict comparison
example
// These all evaluate to true (unexpected!)
console.log("" == false);    // true
console.log(0 == "");        // true
console.log(null == undefined); // true
console.log("0" == false);   // true

// Use strict equality
console.log("" === false);   // false
console.log(0 === "");       // false
console.log(null === undefined); // false

Note Rule of thumb: always use === and !==. The only acceptable == use is value == null to check both null and undefined at once.

Losing 'this' Context

syntax
// Problem: extracting a method loses its 'this'
// Fix: use bind(), arrow function, or keep method call
example
class Timer {
  count = 0;

  // BUG: 'this' is undefined in the callback
  startBroken() {
    setInterval(function () {
      this.count++; // TypeError!
    }, 1000);
  }

  // FIX 1: arrow function inherits 'this'
  startFixed() {
    setInterval(() => {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
}

// FIX 2: bind the method
const timer = new Timer();
const increment = timer.startFixed.bind(timer);

Note Arrow functions, .bind(), or storing this in a variable (const self = this) all solve this. Arrow functions are the cleanest modern solution.

Closure in Loops (var trap)

syntax
// Bug: var is function-scoped
// Fix: use let (block-scoped)
example
// BUG: all callbacks log 3
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3

// FIX: let is block-scoped, each iteration gets its own i
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2

Note With var, all iterations share the same variable. By the time callbacks run, the loop is done and i has its final value. This is the #1 reason to use let.

Floating Point Precision

syntax
// 0.1 + 0.2 !== 0.3
example
console.log(0.1 + 0.2);         // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

// FIX 1: Compare with tolerance
function nearlyEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}
console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true

// FIX 2: Work in integers (cents instead of dollars)
const priceInCents = 199; // $1.99
const total = priceInCents * 3; // 597 cents = $5.97

Note All numbers in JavaScript are 64-bit floating point (IEEE 754). For financial calculations, use integer arithmetic (cents) or a decimal library.

Unexpected Array Mutation

syntax
// Mutating: sort, reverse, splice, push, pop, shift, unshift
// Non-mutating: toSorted, toReversed, toSpliced, with, map, filter, slice
example
const original = [3, 1, 2];
const sorted = original.sort(); // BUG: mutates original!
console.log(original); // [1, 2, 3] (changed!)
console.log(sorted === original); // true (same reference!)

// FIX 1: Copy first
const safeSort = [...original].sort();

// FIX 2: Use non-mutating method (ES2023)
const safeSorted = original.toSorted();

Note sort(), reverse(), splice() all mutate. This is a major bug source especially in React/Vue state. Use toSorted(), toReversed(), toSpliced() for safe alternatives.

async/await with forEach (Does Not Work)

syntax
// BUG: forEach ignores async callbacks
// FIX: use for...of or Promise.all with map
example
const userIds = [1, 2, 3];

// BUG: forEach does NOT await each call
userIds.forEach(async (id) => {
  const user = await fetch(`/api/users/${id}`);
  console.log(await user.json()); // fires unpredictably
});

// FIX 1: Sequential processing
for (const id of userIds) {
  const user = await fetch(`/api/users/${id}`);
  console.log(await user.json());
}

// FIX 2: Parallel processing
const users = await Promise.all(
  userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json()))
);

Note forEach returns undefined and does not await the callback. The loop completes immediately while async callbacks run in the background with no error handling.

Object/Array Reference Sharing

syntax
// Assigning objects copies the reference, not the value
example
const defaults = { theme: "light", notifications: { email: true, sms: false } };

// BUG: both point to the same object
const userSettings = defaults;
userSettings.theme = "dark";
console.log(defaults.theme); // "dark" (changed!)

// FIX: shallow copy
const settings = { ...defaults };

// FIX: deep copy (for nested objects)
const deepSettings = structuredClone(defaults);

Note Assignment (=) with objects copies the reference, not the data. Spread (...) is only a shallow copy. Use structuredClone() for nested structures.

typeof null and Other Gotchas

syntax
typeof null === "object"  // true (historic bug)
typeof NaN === "number"   // true (NaN is a number type)
typeof [] === "object"    // true (arrays are objects)
example
// All of these are surprising but correct
console.log(typeof null);        // "object"
console.log(typeof NaN);         // "number"
console.log(typeof []);          // "object"
console.log(typeof function(){}); // "function"

// Better checks
console.log(value === null);           // null check
console.log(Number.isNaN(value));      // NaN check
console.log(Array.isArray(value));     // array check

Note typeof is unreliable for null, arrays, and NaN. Use specialized checks: === null, Array.isArray(), Number.isNaN(), or instanceof for specific types.