let Declaration
let variableName = value;let score = 0;
score = 10;
console.log(score);10Note Block-scoped. Cannot be re-declared in the same scope. Preferred for values that change.
16 sections · 146 entries
let variableName = value;let score = 0;
score = 10;
console.log(score);10Note Block-scoped. Cannot be re-declared in the same scope. Preferred for values that change.
const variableName = value;const API_URL = "https://api.example.com";
const cart = ["apple"];
cart.push("banana"); // allowed
console.log(cart);["apple", "banana"]Note Block-scoped. Must be initialized at declaration. The binding is immutable, but object/array contents can still change.
var variableName = value;function demo() {
if (true) {
var leaked = "visible outside block";
}
console.log(leaked);
}
demo();"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.
const [a, b, ...rest] = array;const rgb = [30, 120, 255];
const [red, green, blue] = rgb;
console.log(green);
const [first, , third] = ["a", "b", "c"];
console.log(first, third);120
"a" "c"Note Use commas to skip elements. Works with any iterable, not just arrays.
const { key1, key2: alias } = object;const user = { name: "Lina", role: "admin", id: 42 };
const { name, role, id: userId } = user;
console.log(name, userId);"Lina" 42Note Use colon to rename. Combine with defaults: const { theme = "light" } = settings;
const { outer: { inner } } = obj;const response = {
data: { user: { name: "Kai", scores: [95, 88] } }
};
const { data: { user: { name, scores: [latest] } } } = response;
console.log(name, latest);"Kai" 95Note Deeply nested destructuring can hurt readability. Consider extracting in steps for complex structures.
const merged = [...arr1, ...arr2];
const merged = { ...obj1, ...obj2 };const defaults = { theme: "light", lang: "en" };
const prefs = { theme: "dark", fontSize: 16 };
const config = { ...defaults, ...prefs };
console.log(config);{ theme: "dark", lang: "en", fontSize: 16 }Note Later properties overwrite earlier ones. Only performs a shallow copy -- nested objects are still shared by reference.
function fn(...args) {}function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(5, 10, 15));30Note Rest must be the last parameter. Unlike the old arguments object, rest gives you a real Array with all array methods.
const { key = defaultValue } = obj;
const [a = defaultValue] = arr;const { host = "localhost", port = 3000 } = { port: 8080 };
console.log(host, port);"localhost" 8080Note Defaults only apply when the value is undefined, not when it is null or other falsy values.
[a, b] = [b, a];let x = "hello";
let y = "world";
[x, y] = [y, x];
console.log(x, y);"world" "hello"Note No temporary variable needed. Works with any number of variables: [a, b, c] = [c, a, b];
string | number | boolean | undefined | null | symbol | bigintconst 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; // bigintNote Primitives are immutable and compared by value. There are 7 primitive types in total.
typeof valueconsole.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.
implicit: value + "" | +value | !!value
explicit: String(v) | Number(v) | Boolean(v)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")); // NaNNote The + operator with a string always coerces to string. Prefer explicit conversion (Number(), String()) to avoid surprises.
value ?? fallbackconst inputCount = 0;
console.log(inputCount || 10); // 10 (wrong!)
console.log(inputCount ?? 10); // 0 (correct)
const label = null;
console.log(label ?? "Untitled");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.
obj?.prop
obj?.[expr]
obj?.method()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"
variable ??= value;let config = { timeout: null, retries: 3 };
config.timeout ??= 5000;
config.retries ??= 10;
console.log(config);{ timeout: 5000, retries: 3 }Note Only assigns when the left side is null or undefined. Also available: ||= (assigns on falsy) and &&= (assigns on truthy).
const sym = Symbol(description);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.
false | 0 | -0 | 0n | "" | null | undefined | NaNconst values = [false, 0, -0, 0n, "", null, undefined, NaN];
const truthyOnes = values.filter(Boolean);
console.log(truthyOnes.length);0Note Everything else is truthy, including empty objects {}, empty arrays [], and the string "false". This trips up many developers.
a === b // strict (no coercion)
a == b // loose (with coercion)
Object.is(a, b) // same-value equalityconsole.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)); // falseNote Always use === for comparisons. Object.is() handles edge cases like NaN and -0 that even === gets wrong.
`text ${expression} text`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);"One coffee costs $4.50."
"Line one\nLine two\nLine three"Note Backtick strings support embedded expressions and multiline content without escape characters.
tagFunction`text ${expr} text`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.`);"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.
str.includes(search, startIndex)
str.startsWith(search)
str.endsWith(search)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("#")); // 6Note All search methods are case-sensitive. For case-insensitive checks, lowercase both sides first: str.toLowerCase().includes(term.toLowerCase()).
str.slice(start, end)
str.substring(start, end)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.
str.replace(search, replacement)
str.replaceAll(search, replacement)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(",", " | "));"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.
str.split(separator, limit)
arr.join(separator)const tags = "js,react,node";
const tagArray = tags.split(",");
console.log(tagArray);
console.log(tagArray.join(" + "));["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.
str.trim()
str.trimStart()
str.trimEnd()const input = " [email protected] ";
console.log(input.trim()); // "[email protected]"
console.log(input.trimStart()); // "[email protected] "
console.log(input.trimEnd()); // " [email protected]"Note Essential for cleaning user input. Only removes whitespace characters (spaces, tabs, newlines), not other invisible characters.
str.padStart(targetLength, padChar)
str.padEnd(targetLength, padChar)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.
str.toUpperCase()
str.toLowerCase()
str.toLocaleUpperCase(locale)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.
str.repeat(count)
str.at(index)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.
Number(value)
parseInt(string, radix)
parseFloat(string)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)); // 255Note 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.isNaN(value)
Number.isFinite(value)
Number.isInteger(value)
Number.isSafeInteger(value)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)); // falseNote Always use Number.isNaN() over the global isNaN(). The global version coerces its argument to a number first, giving misleading results.
Math.round(n) | Math.floor(n) | Math.ceil(n) | Math.trunc(n)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.
num.toFixed(digits)
num.toPrecision(precision)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)
Math.random() // [0, 1)// 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().
Math.min(...values)
Math.max(...values)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)); // 100Note For very large arrays (100k+ elements), spread can cause a stack overflow. Use a reduce loop instead.
const big = 123n;
const big = BigInt(value);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)); // 42nNote Cannot mix BigInt and Number in arithmetic (throws TypeError). Convert one type first. No Math methods work with BigInt.
base ** exponent
Math.sqrt(n)
Math.cbrt(n)
Math.pow(base, exp)console.log(2 ** 10); // 1024
console.log(Math.sqrt(144)); // 12
console.log(Math.cbrt(27)); // 3
console.log(Math.abs(-42)); // 42Note The ** operator is cleaner than Math.pow() and works with BigInt too: 2n ** 64n.
new Intl.NumberFormat(locale, options).format(n)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.
const arr = [a, b, c];
const arr = Array.from(iterable);
const arr = Array.of(elements);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.
arr[index]
arr.at(index)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".
arr.push(item) // end
arr.pop() // end
arr.unshift(item) // start
arr.shift() // start
arr.splice(index, deleteCount, ...items)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.
arr.map(callback(element, index, array))const prices = [10, 25, 50];
const withTax = prices.map(p => +(p * 1.08).toFixed(2));
console.log(withTax);[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.
arr.filter(callback(element, index, array))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));["Shirt", "Cap"]Note Returns a new array with elements that pass the test. The callback must return a truthy/falsy value.
arr.reduce(callback(accumulator, current, index, array), initialValue)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);60Note Always provide an initial value (second argument). Without it, reduce uses the first element and can throw on empty arrays.
arr.find(callback)
arr.findIndex(callback)
arr.findLast(callback)
arr.findLastIndex(callback)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)); // 1Note find() returns the first match or undefined. findLast() (ES2023) searches from the end. Both return the element, not a boolean.
arr.sort(compareFn)
arr.toSorted(compareFn)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.
arr.flat(depth)
arr.flatMap(callback)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.
arr.includes(value)
arr.some(callback)
arr.every(callback)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)); // falseNote includes() uses strict equality (===) and works with NaN. some() short-circuits on the first true; every() short-circuits on the first false.
arr.toSorted(fn)
arr.toReversed()
arr.toSpliced(start, deleteCount, ...items)
arr.with(index, value)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).
structuredClone(value)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.
const obj = { key: value };
const obj = Object.create(proto);const user = {
name: "Kai",
age: 30,
greet() {
return `Hi, I'm ${this.name}`;
}
};
console.log(user.greet());"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.
obj.property
obj["property"]
obj[variable]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]); // 5000Note Use bracket notation for dynamic keys or keys with special characters. Dot notation is cleaner for static, valid-identifier keys.
const obj = { name, [expression]: value };const name = "Mira";
const role = "admin";
const user = { name, role };
console.log(user); // { name: "Mira", role: "admin" }
const field = "email";
const form = { [field]: "[email protected]" };
console.log(form); // { email: "[email protected]" }Note Shorthand works when the variable name matches the desired key name. Computed keys are evaluated at runtime.
function fn({ key1, key2 = default }) {}function createUser({ name, role = "viewer", active = true }) {
return { name, role, active, createdAt: Date.now() };
}
const admin = createUser({ name: "Bo", role: "admin" });
console.log(admin);{ 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(obj)
Object.values(obj)
Object.entries(obj)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.
Object.assign(target, ...sources)
{ ...obj1, ...obj2 }const defaults = { theme: "light", lang: "en", debug: false };
const userPrefs = { theme: "dark", debug: true };
const settings = { ...defaults, ...userPrefs };
console.log(settings);{ 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.
Object.freeze(obj)
Object.seal(obj)
Object.isFrozen(obj)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); // 2Note freeze() prevents all changes. seal() allows modifying existing properties but not adding/removing. Both are shallow -- nested objects remain mutable.
Object.hasOwn(obj, prop)
"prop" in objconst 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")); // falseNote Object.hasOwn() (ES2022) replaces obj.hasOwnProperty(). It works even if the object was created with Object.create(null).
Object.fromEntries(iterable)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);{ 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.
structuredClone(obj)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.
function name(params) { ... }function calculateTip(bill, tipPercent = 18) {
return +(bill * tipPercent / 100).toFixed(2);
}
console.log(calculateTip(85)); // 15.3
console.log(calculateTip(85, 20)); // 17Note Declarations are hoisted -- they can be called before they appear in code. This is the only function form that hoists.
const fn = (params) => expression;
const fn = (params) => { ... };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.
function fn(param = defaultValue) {}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" });"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.
function fn(first, ...rest) {}function logTagged(level, ...messages) {
const timestamp = new Date().toISOString();
console.log(`[${level}] ${timestamp}:`, ...messages);
}
logTagged("INFO", "Server started", "on port 3000");[INFO] 2026-04-04T...: Server started on port 3000Note Rest parameters collect remaining arguments into a real Array. Unlike arguments, they work in arrow functions too.
function outer() {
let state = value;
return function inner() { /* access state */ };
}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()); // 12Note 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.
(function() { ... })();
(() => { ... })();const api = (() => {
let requestCount = 0;
return {
fetch(url) {
requestCount++;
console.log(`Request #${requestCount}: ${url}`);
},
getCount() { return requestCount; }
};
})();
api.fetch("/users");
console.log(api.getCount());"Request #1: /users"
1Note IIFEs were essential before modules for avoiding global scope pollution. Still useful for one-time initialization blocks.
function* name() { yield value; }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); // 102Note Generators are lazy -- they produce values on demand. Execution pauses at each yield and resumes when next() is called.
async function* name() { yield await value; }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.
function fn(callback) { callback(); }
function fn() { return function() {}; }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);"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.
if (condition) { ... }
else if (condition) { ... }
else { ... }function getDiscount(memberLevel) {
if (memberLevel === "gold") {
return 0.20;
} else if (memberLevel === "silver") {
return 0.10;
} else {
return 0;
}
}
console.log(getDiscount("gold")); // 0.20Note Conditions are coerced to boolean. Watch out for truthy/falsy gotchas: if (arr.length) works because 0 is falsy.
condition ? valueIfTrue : valueIfFalseconst 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 (expression) {
case value: ... break;
default: ...
}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));"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 (init; condition; update) { ... }const items = ["apple", "banana", "cherry"];
for (let i = 0; i < items.length; i++) {
console.log(`${i + 1}. ${items[i]}`);
}"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 (const element of iterable) { ... }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);
}"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 (const key in object) { ... }const theme = { primary: "#3498db", secondary: "#2ecc71", accent: "#e74c3c" };
for (const colorName in theme) {
console.log(`${colorName}: ${theme[colorName]}`);
}"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 (condition) { ... }
do { ... } while (condition);// 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; // exit loop
continue; // skip to next iterationconst 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; } }
label: for (...) { break label; }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); // 5Note Labels let you break/continue a specific outer loop from inside a nested loop. Rarely needed but invaluable for searching nested structures.
new Promise((resolve, reject) => { ... })
promise.then(onFulfilled, onRejected)
promise.catch(onRejected)
promise.finally(onFinally)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 function name() {
const result = await promise;
}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([p1, p2, p3])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([p1, p2, p3])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([p1, p2, p3])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([p1, p2, p3])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 failuresNote Resolves with the first fulfilled promise. Only rejects if ALL promises reject (with an AggregateError). The opposite of Promise.race() for error handling.
const response = await fetch(url, options);// 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.
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort();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.
try { await ... } catch (err) { ... }// 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.
class ClassName {
constructor(params) { ... }
method() { ... }
}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());"Notebook: $12.50"Note Classes are syntactic sugar over prototypes. They are NOT hoisted -- you must declare before use, unlike function declarations.
get propertyName() { ... }
set propertyName(value) { ... }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); // 0Note Getters and setters look like regular properties from the outside. They are ideal for computed values, validation, and encapsulation.
class C {
static method() { ... }
static property = value;
}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)); // 25Note Static members belong to the class itself, not instances. Access them via ClassName.method(), not instance.method().
class Child extends Parent {
constructor(params) {
super(parentParams);
}
}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));"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.
class C {
#privateField = value;
#privateMethod() { ... }
}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; // SyntaxErrorNote Private fields/methods use the # prefix. They are truly private -- not accessible outside the class, even by subclasses. instanceof checks still work.
class C {
static { /* initialization code */ }
}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.
object instanceof ClassNameclass 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); // falseNote Checks the prototype chain. Can be unreliable across iframes or realms. For built-in types, prefer Array.isArray() over instanceof Array.
const Mixin = (Base) => class extends Base { ... };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.
export const name = value;
export function fn() { ... }
export { a, b, c };// 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.
import { name1, name2 } from "./module.js";
import { name as alias } from "./module.js";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.
export default expression;
import anyName from "./module.js";// 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");[App] StartedNote 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".
const module = await import("./module.js");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.
export { name } from "./module.js";
export { default } from "./module.js";
export * from "./module.js";// 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.url
import.meta.resolve(specifier)// 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.
import * as name from "./module.js";import * as validators from "./validators.js";
const email = "[email protected]";
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.
// In an ES module
const data = await fetchData();// 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 immediatelyNote Only works in ES modules (not CommonJS). Importing modules that use top-level await will block until that await resolves, so use judiciously.
try { ... }
catch (error) { ... }
finally { ... }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);"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.
throw new Error(message);
throw new TypeError(message);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.
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
}
}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}`);
}
}"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.
throw new Error(message, { cause: originalError });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.
TypeError | RangeError | ReferenceError | SyntaxError | URIError | EvalError | AggregateError// 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()).
try { ... } catch { ... }function tryParseJSON(str) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
console.log(tryParseJSON('{"valid": true}')); // { valid: true }
console.log(tryParseJSON("not json")); // nullNote Since ES2019, you can omit the error parameter in catch if you do not need it. Keeps code cleaner for "swallow and fallback" patterns.
process.on('unhandledRejection', handler)
window.addEventListener('unhandledrejection', handler)// 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.
error.stack
error.message
error.namefunction 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.
document.querySelector(selector)
document.querySelectorAll(selector)
document.getElementById(id)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.
document.createElement(tag)
parent.appendChild(child)
parent.append(...nodes)
element.insertAdjacentHTML(position, html)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.
element.addEventListener(event, handler, options)
element.removeEventListener(event, handler)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.
parent.addEventListener(event, (e) => {
if (e.target.matches(selector)) { ... }
});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.
el.classList.add(cls)
el.classList.remove(cls)
el.classList.toggle(cls)
el.classList.contains(cls)
el.classList.replace(old, new)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.
element.dataset.key
// reads data-key="value" from HTML// 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.
element.innerHTML = htmlString;
element.textContent = plainText;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.
el.parentElement
el.children
el.firstElementChild
el.nextElementSibling
el.closest(selector)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 selectorNote Use element-based properties (parentElement, children) over node-based ones (parentNode, childNodes) to skip text/comment nodes.
element.remove()
parent.removeChild(child)// 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.
const clone = structuredClone(value);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.
arr.at(index)
str.at(index)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.
arr.findLast(callback)
arr.findLastIndex(callback)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); // 2Note ES2023. Searches from the end of the array. More readable and efficient than reversing first or using a manual reverse loop.
Object.groupBy(iterable, keyFn)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(iterable, keyFn)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.).
setA.union(setB)
setA.intersection(setB)
setA.difference(setB)
setA.symmetricDifference(setB)
setA.isSubsetOf(setB)
setA.isSupersetOf(setB)
setA.isDisjointFrom(setB)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)); // falseNote ES2025. All methods return new Sets (non-mutating). Works with any iterable argument, not just Sets. Replaces manual loop-based set operations.
const { promise, resolve, reject } = Promise.withResolvers();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.map(fn)
iterator.filter(fn)
iterator.take(n)
iterator.drop(n)
iterator.flatMap(fn)
iterator.toArray()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.
using resource = acquireResource();
await using resource = acquireAsyncResource();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"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.
/pattern/v// 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("α")); // trueNote ES2024. The v flag replaces the u flag with enhanced Unicode support and set operations (&&, --) inside character classes.
function debounce(fn, delay) { ... }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.
function throttle(fn, interval) { ... }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.
function memoize(fn) { ... }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.
const curried = (a) => (b) => (c) => result;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.
class Singleton { static #instance; static getInstance() {} }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); // trueNote Ensures only one instance of a class exists. In modern JavaScript, ES modules are natural singletons -- an exported object is shared across all importers.
class EventEmitter { on(event, handler) {} emit(event, data) {} }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(); // unsubscribesNote The observer pattern decouples event producers from consumers. Returning an unsubscribe function from on() prevents memory leaks.
const pipe = (...fns) => (x) => fns.reduce((v, fn) => fn(v), x);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.
async function retry(fn, attempts, delay) { ... }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.
// Wrong: == with type coercion
// Right: === for strict comparison// 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); // falseNote Rule of thumb: always use === and !==. The only acceptable == use is value == null to check both null and undefined at once.
// Problem: extracting a method loses its 'this'
// Fix: use bind(), arrow function, or keep method callclass 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.
// Bug: var is function-scoped
// Fix: use let (block-scoped)// 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, 2Note 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.
// 0.1 + 0.2 !== 0.3console.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.97Note All numbers in JavaScript are 64-bit floating point (IEEE 754). For financial calculations, use integer arithmetic (cents) or a decimal library.
// Mutating: sort, reverse, splice, push, pop, shift, unshift
// Non-mutating: toSorted, toReversed, toSpliced, with, map, filter, sliceconst 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.
// BUG: forEach ignores async callbacks
// FIX: use for...of or Promise.all with mapconst 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.
// Assigning objects copies the reference, not the valueconst 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 === "object" // true (historic bug)
typeof NaN === "number" // true (NaN is a number type)
typeof [] === "object" // true (arrays are objects)// 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 checkNote typeof is unreliable for null, arrays, and NaN. Use specialized checks: === null, Array.isArray(), Number.isNaN(), or instanceof for specific types.