Modern JavaScript from language fundamentals through the seven additions in ECMAScript 2026.
Language core
Objects and prototypes
Model values with objects, prototypes and classes without losing track of identity.
- Property lookup can continue along the prototype chain.
- Object equality compares identity rather than matching fields.
- Use own-property checks when inherited properties are inappropriate.
Arrays and collection methods
Transform and search data with map, filter, reduce, find and the newer non-mutating methods.
- Use map for transformation and filter for selection.
- Supply a deliberate initial accumulator to reduce.
- Prefer copying sort or reverse methods when callers share the array.
Errors and resource cleanup
Create useful errors and release resources predictably with modern cleanup patterns.
- Throw Error objects with actionable messages and useful causes.
- Use finally for cleanup that must run after success or failure.
- Avoid returning from finally because it can override earlier outcomes.
Primitive values and coercion
Distinguish value types, equality rules and explicit conversion from surprising coercion.
- Prefer explicit conversion where input types are uncertain.
- Understand that strict equality still has NaN and signed-zero subtleties.
- Use Object.is when its equality semantics match the task.
this and binding
Predict this from the call site and choose bind, call, apply or arrow functions deliberately.
- Ordinary function this depends on how the function is called.
- Arrow functions capture this from their surrounding scope.
- bind creates a callable with a chosen receiver and optional leading arguments.
Classes and private fields
Define class behavior, inheritance and truly private state with modern syntax.
- Private fields enforce class-scoped access at runtime.
- Static members belong to the constructor rather than its instances.
- Call super before using this in a derived constructor.
Regular expressions
Match structured text with readable patterns, named groups and safe escaping.
- Use named capture groups to make extraction easier to interpret.
- Escape dynamic literal text before constructing a pattern.
- Avoid catastrophic backtracking on untrusted large inputs.
Typed arrays and DataView
Read and write binary data with explicit widths, endianness and shared buffers.
- Typed arrays view an ArrayBuffer with a fixed element format.
- DataView allows explicit byte order for mixed-width binary fields.
- Respect view offsets and lengths when sharing a buffer.
ES2026 additions
Map and WeakMap upsert
Use getOrInsert and getOrInsertComputed to replace has/get/set boilerplate.
- getOrInsert keeps an existing value or inserts a default.
- getOrInsertComputed calls its initializer only when the key is absent.
- Check runtime support and retain valid WeakMap key types.
Iterator.concat
Join iterators lazily without materializing intermediate arrays.
- Consume input iterables sequentially through one lazy iterator.
- Values are produced on demand instead of copied into a combined array.
- An unbounded first input prevents later inputs from being reached.
Uint8Array base64 and hex
Encode and decode binary data directly with the new typed-array methods.
- Use fromBase64 or fromHex to decode supported text formats.
- Use toBase64 or toHex to encode a byte array.
- Validate input format and handle decoding errors at the boundary.
Math.sumPrecise
Sum floating-point values with a numerically stable algorithm.
- Accumulate numeric values with less intermediate rounding error.
- Do not treat precise summation as exact decimal arithmetic.
- Define handling for non-finite inputs in the application.
Array.fromAsync
Collect async iterables and promise values into an array in input order.
- Consume asynchronous iterables through a promise-returning array constructor.
- Await yielded values and any asynchronous mapping operation.
- Remember the completed array still retains all collected values in memory.
JSON.parse source text
Inspect source text for selected JSON values while parsing large or precise data.
- Use the reviver context's source for supported primitive values.
- Recover precise numeric text before converting it into a domain type.
- Do not assume an already-rounded Number preserves the original digits.
Error.isError
Recognize error values across realms without relying on instanceof alone.
- Use the built-in error brand check where supported.
- Recognize genuine Error instances created in another realm.
- Keep validation for non-Error thrown values at catch boundaries.
Set methods
Use union, intersection, difference and symmetricDifference for clear set operations.
- Use union and intersection to combine or retain memberships.
- Use difference and symmetricDifference for exclusion and mismatches.
- These operations produce new sets without editing the original membership.
RegExp.escape
Escape arbitrary text before embedding it in a regular expression safely.
- Use RegExp.escape for literal text inserted into a regex.
- Keep intended regex operators outside the escaped fragment.
- Verify runtime support rather than writing an incomplete escaping pattern.
Promise.try
Normalize synchronous throws and returned promises into one rejection-aware flow.
- Invoke a callback and convert synchronous throws into rejections.
- Adopt a returned promise so callers get one async error path.
- Await or catch the resulting promise instead of ignoring failures.
Array methods
Array.prototype.map
Build a new array by transforming every element with a callback.
- Returns a new array of the same length and never mutates the source.
- The callback receives (element, index, array), so an index is available without a counter.
- Returning nothing yields undefined entries — use filter or flatMap when some elements should be dropped.
Example: map-basics.js
const rows = [
{ id: 1, name: "Ada", hours: 7 },
{ id: 2, name: "Linus", hours: 3 },
];
// One output element per input element.
const options = rows.map(row => ({ value: row.id, label: row.name }));
// The index is the second argument — no counter needed.
const numbered = rows.map((row, index) => (index + 1) + ". " + row.name);
console.log(options.length === rows.length); // true: map never drops elements
console.log(numbered); // ["1. Ada", "2. Linus"]
Rendering a list from data: Turn an array of API records into view models or elements, one per row, without touching the fetched data.
Array.prototype.filter
Build a new array of the elements a predicate keeps.
- Keeps every element whose callback returns a truthy value, in their original order.
- Always returns an array — an empty one when nothing matches, never undefined.
- Filtering before mapping avoids transforming elements that are about to be discarded.
Applying a search or status filter: Narrow a loaded collection to the rows matching a query or a status before paging it, leaving the original list intact.
Array.prototype.reduce
Fold an array into a single value by accumulating one element at a time.
- Takes (accumulator, element, index, array) and an optional initial value.
- Omitting the initial value uses the first element and throws on an empty array — pass one.
- Any array method can be written as a reduce, but a map or filter states the intent more plainly.
Example: reduce-total.js
const invoice = [
{ item: "Keyboard", price: 4500, qty: 1 },
{ item: "Cable", price: 300, qty: 3 },
];
// Pass the initial value: an empty array throws without it.
const total = invoice.reduce((sum, line) => sum + line.price * line.qty, 0);
console.log(total); // 5400
console.log([].reduce((a, b) => a + b, 0)); // 0, not a TypeError
Totals and groupings: Sum an invoice, count occurrences, or group records by a key into an object in a single pass.
Array.prototype.find and findLast
Return the first or last element matching a predicate, or undefined.
- find scans from the start and findLast from the end; both stop at the first match.
- They return the element itself, unlike findIndex and findLastIndex which return its position.
- undefined is also a legal element value, so use findIndex when absence must be distinguished.
Looking up one record by id: Pull a single item out of a loaded collection — the selected row, the default address — without a second request.
Array.prototype.flat and flatMap
Flatten nested arrays by one level, or map and flatten in one pass.
- flat() flattens one level; flat(Infinity) flattens completely.
- flatMap(fn) is map followed by flat(1), and is how a callback returns zero or many items per element.
- Both skip empty slots in sparse arrays.
Expanding one row into many: Turn each order into its line items, or drop an element by returning an empty array from the callback.
Array.prototype.sort and toSorted
Order an array in place, or get an ordered copy.
- sort mutates and returns the same array; toSorted returns a new one and leaves the source alone.
- Without a comparator, elements are compared as strings, so 10 sorts before 9.
- The comparator must return a negative number, zero or a positive number, consistently for every pair.
Sorting a table column: Order rows by date, price or name when a header is clicked, using toSorted so the fetched data keeps its original order.
Array.prototype.slice and splice
Copy a section of an array, or insert and remove elements in place.
- slice(start, end) returns a copy and leaves the source untouched; end is exclusive.
- splice(start, count, ...items) mutates the array and returns the removed elements.
- Negative indexes count from the end, and toSpliced gives splice's result without mutating.
Paging and editing a list: Take one page out of a result set with slice, and remove or insert a row in a draft list with toSpliced.
Array.prototype.includes and indexOf
Test membership, or find the position of a value.
- includes returns a boolean and matches NaN; indexOf returns -1 for absence and never matches NaN.
- Both compare with strict equality, so two equal-looking objects do not match.
- indexOf accepts a starting position, which is how repeated values are found.
Checking a selection: Test whether an id is already selected before adding it, or find where it sits so it can be removed.
Array.prototype.some and every
Test whether any, or all, elements satisfy a predicate.
- Both short-circuit: some stops at the first true, every at the first false.
- every returns true for an empty array and some returns false — the vacuous cases interviews ask about.
- Both return a boolean, so pair them with find when the matching element itself is needed.
Form and permission checks: Enable submit because every field is valid, or warn because some item in a cart is out of stock.
Array.prototype.at
Read an element by index, counting from the end with a negative one.
- at(-1) is the last element, which bracket notation cannot express directly.
- Returns undefined for an out-of-range index rather than throwing.
- Works on strings and typed arrays as well as arrays.
Reading the latest entry: Take the most recent message in a conversation or the final segment of a path without writing list[list.length - 1].
Array.from and Array.of
Build an array from an iterable or array-like value, or from its arguments.
- Array.from takes an optional map function, so from(x, fn) avoids a second pass.
- Array.from({ length: n }, (_, i) => i) is the idiomatic range.
- Array.of(7) is [7] while Array(7) is seven empty slots — the reason Array.of exists.
Converting DOM and Set results: Turn a NodeList, a Set or a Map's entries into a real array so every array method becomes available.
Array.prototype.join
Join elements into a string with a separator.
- null and undefined join as empty strings, not as the text "null".
- The default separator is a comma, which is what toString and string coercion use.
- Joining is clearer and usually faster than repeated concatenation in a loop.
Rendering a list inline: Print tags, authors or active filters as a comma-separated line, or build a path from segments.
String methods
String.prototype.split
Cut a string into an array on a separator or pattern.
- Accepts a string or a regular expression, plus a limit for how many pieces to keep.
- Splitting on "" splits by UTF-16 code unit and breaks emoji — use Intl.Segmenter for characters.
- A capturing group in the pattern keeps the separators in the result.
Parsing structured text: Split a CSV line, a path or a comma-separated tag input into parts before validating each one.
String.prototype.replace and replaceAll
Replace the first match, or every match, with text or a function's result.
- replace with a string pattern replaces only the first occurrence; replaceAll replaces all of them.
- $& and $1 in the replacement refer to the match and its capture groups; $$ is a literal dollar sign.
- A replacer function receives the match and its groups, so each replacement can be computed.
Templating and escaping: Substitute placeholders in a message, or escape characters before putting user text into a URL or a pattern.
String.prototype.slice and substring
Take part of a string by position.
- slice accepts negative indexes; substring clamps them to zero and swaps reversed arguments.
- Neither mutates — strings are immutable, so both return a new string.
- at(-1) reads the last character, which slice(-1) also returns as a one-character string.
Truncating and excerpting: Cut a summary to a card's length, take a filename's extension, or read a fixed-width field from a record.
String.prototype.trim, padStart and padEnd
Remove surrounding whitespace, or pad a string to a fixed width.
- trimStart and trimEnd trim one side only; all three return a new string.
- padStart is how numbers are zero-padded: String(n).padStart(2, "0").
- Padding stops once the target length is reached, so a longer string comes back unchanged.
Cleaning input and aligning output: Trim a pasted email before validating it, and pad minutes and seconds so durations line up in a column.
String.prototype.startsWith, endsWith and includes
Test where, or whether, a substring appears.
- All three take an optional position and return a boolean rather than an index.
- They are case-sensitive, so normalize both sides before comparing user input.
- includes cannot take a regular expression — use test or match for a pattern.
Routing and type-ahead filtering: Decide whether a path belongs to a section, or filter a list as someone types into a search box.
String.prototype.matchAll
Read every match of a pattern, with its capture groups and positions.
- Requires the global flag and returns an iterator of full match objects.
- Named groups are read from match.groups, which is clearer than numbered indexes.
- match with /g returns matched text only and drops the groups and indexes.
Extracting every occurrence: Pull all mentions, links or template placeholders out of a block of text along with where each one starts.
String.prototype.localeCompare
Compare strings the way a reader's language orders them.
- Returns a negative number, zero or a positive number, so it drops straight into sort.
- Options set sensitivity and numeric ordering, so "item10" can sort after "item9".
- Intl.Collator reuses one comparator and is faster when sorting a long list.
Sorting names for people: Order a directory or product list so accented and case-different names fall where a reader of that language expects.
String.prototype.normalize
Put equivalent Unicode sequences into one canonical form before comparing.
- An accented letter can be one code point or a base letter plus a combining mark; the two are not === until normalized.
- NFC is the usual choice for storage and comparison; NFD separates the base and the marks.
- Normalizing then stripping combining marks is how accent-insensitive search is built.
Matching user-entered text: Compare a pasted name against a stored one so two identical-looking strings do not fail to match.
Object & JSON
Object.keys, values and entries
Read an object's own enumerable keys, values or key-value pairs as an array.
- Only own enumerable string-keyed properties are returned; inherited and symbol keys are skipped.
- Integer-like keys come first in ascending order, then string keys in insertion order.
- Object.entries pairs with Object.fromEntries to map or filter an object through array methods.
Iterating a record: Render a settings object as rows, or count how many fields of a form object have been filled in.
Object.assign and object spread
Copy own enumerable properties from one or more sources onto a target.
- Both copy one level deep, so nested objects stay shared with the source.
- Object.assign mutates its target and returns it; spread always builds a new object.
- Spread copies values while assign triggers setters on the target — the reason the two can differ.
Merging defaults with overrides: Build a config from defaults plus user options, or produce the next state object without mutating the previous one.
Object.freeze and Object.seal
Make an object's properties unwritable, or its shape fixed.
- freeze is shallow — nested objects stay mutable unless frozen too.
- Writes fail silently in sloppy mode and throw a TypeError in strict mode and in modules.
- seal is the weaker form: existing properties stay writable, but none can be added or removed.
Protecting shared constants: Keep a shared configuration or lookup table from being edited by accident somewhere else in the app.
Object.fromEntries
Build an object from key-value pairs.
- Accepts any iterable of pairs, including a Map and URLSearchParams.
- Later duplicate keys overwrite earlier ones.
- Object.entries → array method → Object.fromEntries is the standard way to map or filter an object.
Reading query parameters: Turn a URL's search parameters or a form's entries into a plain object to validate and pass around.
Object.create and prototypes
Create an object with an explicit prototype, or with none at all.
- Object.create(null) has no prototype, so there is no inherited toString or __proto__ to collide with.
- A second argument takes property descriptors, which an object literal cannot express.
- getPrototypeOf reads the link; setPrototypeOf changes it and is slow enough to avoid in hot paths.
Safe dictionaries: Hold user-supplied keys — headers, counts by word — without a key such as "constructor" hitting an inherited property.
Object.defineProperty and descriptors
Define a property together with its writability, enumerability and accessors.
- Properties defined this way default to non-writable, non-enumerable and non-configurable.
- A descriptor holds either value and writable, or get and set — never both pairs.
- getOwnPropertyDescriptor reads one back, which is how an accessor is copied faithfully.
Computed and hidden fields: Expose a derived value as a getter, or keep an internal field out of JSON.stringify and for-in loops.
JSON.stringify
Serialize a value to JSON, with optional filtering and indentation.
- undefined, functions and symbols are dropped from objects and become null inside arrays.
- A toJSON method decides a value's own serialized form — how Date becomes a string.
- The replacer chooses which keys to keep; the third argument sets indentation for readable output.
Sending and storing state: Put a request body on the wire or a draft into localStorage, leaving out fields the server must not receive.
JSON.parse and the reviver
Turn JSON text back into values, transforming entries as they are read.
- Throws a SyntaxError on malformed input, so parsing untrusted text belongs in a try/catch.
- The reviver runs for every key from the innermost outward, and returning undefined drops a key.
- Dates arrive as strings — a reviver or a schema parser is what turns them back into Date objects.
Example: safe-parse.js
export function readDraft(key) {
const stored = localStorage.getItem(key);
if (!stored) return null;
try {
return JSON.parse(stored, (field, value) =>
field === "savedAt" ? new Date(value) : value);
} catch {
localStorage.removeItem(key);
return null;
}
}
Reading stored or fetched data: Restore a saved draft from localStorage, rejecting corrupted data instead of letting it break the page.
structuredClone
Deep-clone a value, including Map, Set, Date and typed arrays.
- Handles cycles, which a JSON round-trip cannot.
- Functions, DOM nodes and class identity are not cloned — a function throws a DataCloneError.
- Available in browsers and Node without a library, and is the algorithm postMessage already uses.
Snapshotting state: Take a true copy of a nested draft before an edit so cancelling restores it exactly.
Promise API
Promise.all and Promise.allSettled
Run promises concurrently and wait for all of them.
- all rejects as soon as any input rejects, and the other work keeps running unless it is aborted.
- allSettled never rejects: each result is {status, value} or {status, reason}.
- Both preserve input order in the results, whichever finished first.
Example: load-page-data.js
async function loadDashboard(userId, signal) {
const [user, permissions, alerts] = await Promise.all([
fetch("/api/users/" + userId, { signal }).then(r => r.json()),
fetch("/api/users/" + userId + "/permissions", { signal }).then(r => r.json()),
fetch("/api/alerts", { signal }).then(r => r.json()),
]);
return { user, permissions, alerts };
}
// Promise.all rejects as soon as any input rejects; the rest keep running
// unless the shared AbortController is aborted.
Loading a page's data at once: Fetch the user, their permissions and their notifications together, still rendering partial content with allSettled when one call fails.
Promise.race and Promise.any
Settle on the first promise to finish, or on the first to succeed.
- race settles with the first result, rejection included; any ignores rejections until one fulfils.
- any rejects with an AggregateError only when every input rejects.
- race against a timer is the timeout idiom — pair it with AbortController so the loser is actually cancelled.
Timeouts and mirrors: Give a request a deadline, or take whichever of several mirrors or caches answers first.
Promise.resolve, reject and chaining
Make a settled promise, and know what then returns.
- then always returns a new promise, so a value returned inside it becomes the next resolution.
- Returning a promise inside then adopts it rather than nesting it.
- catch(fn) is then(undefined, fn), and finally passes the result through untouched.
Normalizing sync and async: Wrap a cached value or a callback API so every caller gets a promise and one error path.