Data structures
JavaScript admits substantial built-in data structures: Array (ordered, mixed-type, dynamic), Object (string/symbol-keyed associative), Map (any-key associative), Set (unique-value collection), WeakMap/WeakSet (object-keyed weak references), typed arrays (Int8Array, Uint32Array, Float64Array, etc. — substantial for binary data), Date, RegExp, Promise. The principal contemporary discipline favours Map and Set over plain objects for collections (admit any key type, substantial methods); arrays for sequences. The combination — Array for sequences with substantial methods, Object for records, Map/Set for substantial collections, typed arrays for binary data, the Date for time, the Promise for async — is the substance of JavaScript’s data-structure surface.
Arrays
Ordered, dynamically-sized collections:
const arr = [1, 2, 3];
const empty = [];
const filled = new Array(5).fill(0); // [0, 0, 0, 0, 0]
const fromIter = Array.from("hello"); // ["h", "e", "l", "l", "o"]
const fromMap = Array.from({ length: 5 }, (_, i) => i * 2); // [0, 2, 4, 6, 8]
const fromSpread = [..."hello"]; // ["h", "e", "l", "l", "o"]
arr.length; // 3
arr[0]; // 1
arr.at(-1); // 3 (admits negative indexing)
Mutation methods
const arr = [1, 2, 3];
arr.push(4); // [1, 2, 3, 4]
arr.pop(); // returns 4; arr = [1, 2, 3]
arr.unshift(0); // [0, 1, 2, 3]
arr.shift(); // returns 0
arr.splice(1, 1); // remove 1 at index 1
arr.splice(1, 0, "new"); // insert at index 1
arr.sort(); // mutating sort
arr.reverse(); // mutating reverse
arr.fill(0); // [0, 0, 0]
Non-mutating methods
const arr = [3, 1, 4, 1, 5];
arr.toSorted(); // [1, 1, 3, 4, 5] (ES2023)
arr.toReversed(); // [5, 1, 4, 1, 3]
arr.toSpliced(1, 1); // [3, 4, 1, 5]
arr.with(1, 99); // [3, 99, 4, 1, 5]
// Pre-2023 alternatives:
[...arr].sort();
[...arr].reverse();
Iteration methods
const arr = [1, 2, 3, 4, 5];
arr.forEach(x => console.log(x));
arr.map(x => x * 2); // [2, 4, 6, 8, 10]
arr.filter(x => x > 2); // [3, 4, 5]
arr.reduce((acc, x) => acc + x, 0); // 15
arr.reduceRight((acc, x) => acc + x, 0); // 15
arr.find(x => x > 2); // 3
arr.findLast(x => x > 2); // 5 (ES2023)
arr.findIndex(x => x > 2); // 2
arr.findLastIndex(x => x > 2); // 4
arr.some(x => x > 4); // true
arr.every(x => x > 0); // true
arr.includes(3); // true
arr.indexOf(3); // 2
arr.lastIndexOf(3); // 2
arr.flat(); // [1, 2, 3, 4, 5]
arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6, ...]
arr.join(", "); // "1, 2, 3, 4, 5"
arr.concat([6, 7]); // [1, 2, 3, 4, 5, 6, 7]
arr.slice(1, 3); // [2, 3]
// Spread for substantial concat:
[...arr1, ...arr2, 99];
// New iterator helpers (ES2024+, gradual support):
arr.values(); // iterator
arr.keys(); // iterator over indices
arr.entries(); // iterator over [index, value]
Common patterns
// Sum:
arr.reduce((a, b) => a + b, 0);
// Max:
Math.max(...arr);
arr.reduce((a, b) => Math.max(a, b), -Infinity);
// Unique:
[...new Set(arr)];
// Group:
arr.reduce((acc, x) => {
const key = x.category;
(acc[key] ??= []).push(x);
return acc;
}, {});
// Or with Object.groupBy (ES2024):
Object.groupBy(arr, x => x.category);
// Chunked:
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
Objects
Plain objects admit string and symbol keys:
const obj = {
name: "Alice",
age: 30,
email: "alice@b.c"
};
// Property access:
obj.name;
obj["name"]; // bracket notation
obj["with-dashes"]; // for keys not valid identifiers
// Modification:
obj.role = "admin";
obj["status"] = "active";
delete obj.email; // removes key
// Inspection:
Object.keys(obj); // ["name", "age", "role", "status"]
Object.values(obj);
Object.entries(obj); // [["name", "Alice"], ...]
Object.fromEntries([["a", 1], ["b", 2]]); // { a: 1, b: 2 }
// Spread / merge:
const updated = { ...obj, age: 31 };
const merged = Object.assign({}, obj1, obj2);
// Has key:
"name" in obj; // checks prototype chain
Object.hasOwn(obj, "name"); // own property only (modern)
obj.hasOwnProperty("name"); // legacy
// Freeze:
Object.freeze(obj); // shallow immutable
Object.isFrozen(obj); // true
Computed keys
const key = "name";
const obj = {
[key]: "Alice",
[`${key}_upper`]: "ALICE",
[Symbol("private")]: "hidden"
};
Iteration
// Keys:
for (const key in obj) { // includes inherited; rarely conventional
console.log(key);
}
// Modern (own keys only):
for (const key of Object.keys(obj)) {
console.log(key);
}
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// Just values:
for (const value of Object.values(obj)) {
console.log(value);
}
Map
The conventional contemporary collection — admits any key type, preserves insertion order:
const map = new Map();
map.set("name", "Alice");
map.set(42, "the answer");
map.set({ id: 1 }, "object key"); // any key type
map.get("name"); // "Alice"
map.has("name"); // true
map.size; // 3
map.delete("name");
map.clear();
// Initialisation:
const map2 = new Map([
["a", 1],
["b", 2]
]);
// From entries:
const map3 = new Map(Object.entries(obj));
// To object:
const obj2 = Object.fromEntries(map);
Iteration
for (const [key, value] of map) {
console.log(key, value);
}
map.forEach((value, key) => {
console.log(key, value);
});
[...map.keys()]; // array of keys
[...map.values()]; // array of values
[...map.entries()]; // array of [key, value]
Map vs Object
Map | Object | |
|---|---|---|
| Key types | any | string, symbol |
| Iteration order | insertion | mostly insertion (with caveats for integer keys) |
.size | yes | use Object.keys().length |
| Iteration | direct | via Object.keys() etc. |
| Performance | substantial for substantial frequent add/remove | substantial for substantial small fixed-shape |
| JSON | not native | yes |
The conventional contemporary discipline:
Map— for substantial collections with substantial mutation.Object— for records and configuration.
Set
Unique-value collections:
const set = new Set([1, 2, 3, 2, 1]); // {1, 2, 3}
set.add(4);
set.has(2); // true
set.delete(1);
set.size; // 3
set.clear();
// Initialisation:
const fromArr = new Set([1, 2, 3]);
const fromStr = new Set("hello"); // {"h", "e", "l", "o"}
// To array:
const arr = [...set];
const arr = Array.from(set);
// Iteration:
for (const value of set) {
console.log(value);
}
set.forEach(value => console.log(value));
Set operations (ES2025)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.union(b); // {1, 2, 3, 4}
a.intersection(b); // {2, 3}
a.difference(b); // {1}
a.symmetricDifference(b); // {1, 4}
a.isSubsetOf(b); // false
a.isSupersetOf(b); // false
a.isDisjointFrom(b); // false
For older runtimes:
// Union:
const union = new Set([...a, ...b]);
// Intersection:
const intersection = new Set([...a].filter(x => b.has(x)));
// Difference:
const diff = new Set([...a].filter(x => !b.has(x)));
WeakMap and WeakSet
Object-keyed weak references — admit substantial garbage collection of keys:
const cache = new WeakMap();
let key = { id: 1 };
cache.set(key, "cached value");
key = null; // original reference gone
// Eventually, the WeakMap entry is garbage-collected
// Note: WeakMap does not admit iteration, .size, .clear()
The conventional uses are caches and metadata-on-objects where the object’s lifetime determines the entry’s lifetime.
Typed arrays
For binary data:
const buf = new ArrayBuffer(16); // 16 bytes
const view32 = new Int32Array(buf); // 4 elements
const view16 = new Uint16Array(buf); // 8 elements
view32[0] = 0x12345678;
console.log(view16[0], view16[1]); // depends on endianness
// Common typed arrays:
new Int8Array([1, 2, 3]);
new Uint8Array(10);
new Uint8ClampedArray(10); // for canvas pixel data
new Int16Array(10);
new Uint16Array(10);
new Int32Array(10);
new Uint32Array(10);
new Float32Array(10);
new Float64Array(10);
new BigInt64Array(10);
new BigUint64Array(10);
// DataView for endianness control:
const view = new DataView(buf);
view.getInt32(0, true); // little-endian
view.setInt32(0, 0x12345678, false); // big-endian
The mechanism admits substantial performance for substantial binary work — Web Audio, WebGL, ImageData, etc.
Date
const now = new Date();
const specific = new Date("2026-01-15T10:00:00Z");
const fromComponents = new Date(2026, 0, 15); // month is 0-indexed!
const fromMs = new Date(1736937600000);
now.getTime(); // ms since epoch
now.getFullYear();
now.getMonth(); // 0-11
now.getDate(); // 1-31
now.getDay(); // 0-6 (Sun=0)
now.getHours();
now.getMinutes();
now.getSeconds();
now.toISOString(); // "2026-01-15T10:00:00.000Z"
now.toLocaleDateString();
now.toLocaleString("ja-JP");
Date.now(); // current ms (no instance)
The native Date admits substantial pitfalls — mutable, 0-indexed months, timezone confusion. The conventional contemporary alternatives:
Temporal(Stage 3 as of 2026) — modern API; via@js-temporal/polyfill.date-fns— substantial functional library.dayjs— lightweight, immutable.Luxon— substantial timezone support.
Regular expressions
const re = /pattern/flags;
const re2 = new RegExp("pattern", "flags");
// Test:
re.test("input"); // boolean
// Match:
"hello".match(/l/g); // ["l", "l"]
"hello".match(/(\w)(\w)/); // captures
"hello".matchAll(/l/g); // iterator (ES2020)
// Replace:
"hello".replace(/l/g, "L"); // "heLLo"
"hello".replaceAll("l", "L"); // "heLLo"
// Named groups:
const m = "2026-01-15".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups.year; // "2026"
// Common flags:
/hello/i; // case-insensitive
/hello/g; // global
/hello/m; // multi-line
/hello/s; // dotall (. matches newline)
/hello/u; // unicode
/hello/y; // sticky
Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done"), 1000);
});
promise.then(value => console.log(value));
promise.catch(err => console.error(err));
promise.finally(() => console.log("done"));
// Static methods:
Promise.resolve(42); // already-resolved
Promise.reject(new Error("oops")); // already-rejected
Promise.all([p1, p2, p3]); // all or first rejection
Promise.allSettled([p1, p2, p3]); // all results (success or failure)
Promise.race([p1, p2, p3]); // first to settle
Promise.any([p1, p2, p3]); // first to fulfill
Treated more substantially in Async and promises.
JSON
JSON.stringify({ name: "Alice", age: 30 }); // '{"name":"Alice","age":30}'
JSON.stringify(obj, null, 2); // pretty-printed
JSON.parse('{"name":"Alice"}'); // { name: "Alice" }
// With reviver/replacer:
JSON.stringify(obj, (key, value) => {
if (key === "password") return undefined; // exclude
return value;
});
JSON.parse(text, (key, value) => {
if (key === "createdAt") return new Date(value);
return value;
});
Common patterns
Counting
const counts = items.reduce((acc, item) => {
acc[item] = (acc[item] ?? 0) + 1;
return acc;
}, {});
// With Map:
const counts = items.reduce((map, item) => {
map.set(item, (map.get(item) ?? 0) + 1);
return map;
}, new Map());
Group by
const byCategory = items.reduce((acc, item) => {
(acc[item.category] ??= []).push(item);
return acc;
}, {});
// ES2024:
const byCategory = Object.groupBy(items, item => item.category);
const byCategory = Map.groupBy(items, item => item.category);
Deduplication
const unique = [...new Set(arr)];
// By key:
const seen = new Set();
const unique = arr.filter(x => {
if (seen.has(x.id)) return false;
seen.add(x.id);
return true;
});
// With Map:
const unique = [...new Map(arr.map(x => [x.id, x])).values()];
Sorting
arr.sort(); // string compare; mutating
arr.sort((a, b) => a - b); // numeric ascending
arr.sort((a, b) => b - a); // descending
people.sort((a, b) => a.age - b.age); // by field
people.sort((a, b) => a.name.localeCompare(b.name));
// Multi-key:
items.sort((a, b) =>
a.category.localeCompare(b.category) ||
a.priority - b.priority
);
// Non-mutating (ES2023):
const sorted = arr.toSorted((a, b) => a - b);
Filtering and transforming
const result = users
.filter(u => u.active)
.map(u => ({ id: u.id, name: u.name }))
.sort((a, b) => a.name.localeCompare(b.name))
.slice(0, 10);
Object transformation
// Map values:
const lengths = Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, v.length])
);
// Filter keys:
const filtered = Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v != null)
);
Frozen constants
const STATUS = Object.freeze({
ACTIVE: "active",
INACTIVE: "inactive",
BANNED: "banned"
});
// Or with `as const` in TypeScript.
Map for memoisation
function memoize(fn) {
const cache = new Map();
return function (key) {
if (cache.has(key)) return cache.get(key);
const value = fn(key);
cache.set(key, value);
return value;
};
}
WeakMap for object-bound metadata
const elementData = new WeakMap();
function attachData(el, data) {
elementData.set(el, data);
}
function getData(el) {
return elementData.get(el);
}
// When `el` is removed from DOM and no longer referenced, the entry is GC'd.
Set as cache
const seen = new Set();
function isSeen(item) {
if (seen.has(item)) return true;
seen.add(item);
return false;
}
Array slice / spread
const head = arr.slice(0, 3); // first 3
const tail = arr.slice(-3); // last 3
const middle = arr.slice(2, -2);
const without = [...arr.slice(0, idx), ...arr.slice(idx + 1)]; // remove at idx (immutable)
Tuple-like arrays
function divmod(a, b) {
return [Math.floor(a / b), a % b]; // tuple as array
}
const [q, r] = divmod(17, 5);
Type-checking
Array.isArray(value); // [], not Array.from
typeof value === "object" && value !== null && !Array.isArray(value); // plain object
value instanceof Map;
value instanceof Set;
value instanceof Date;
A note on the conventional discipline
The contemporary JavaScript data-structures advice:
- Use
Arrayfor sequences. - Use
Objectfor records (string-keyed). - Use
Mapfor substantial collections; non-string keys. - Use
Setfor unique-value collections. - Use
WeakMap/WeakSetfor object-keyed metadata. - Use typed arrays for binary data.
- Use
Object.fromEntriesandObject.entriesfor substantial object transformations. - Use
Object.groupBy(ES2024) for grouping. - Use spread (
...) for immutable updates. - Use
to*non-mutating methods (ES2023) —toSorted,toReversed,toSpliced,with. - Use
Object.hasOwnoverhasOwnProperty. - Use
Array.isArrayoverinstanceof Array. - Reach for
Temporal(Stage 3) or date-fns over nativeDate.
The combination — Array for sequences, Object for records, Map/Set for substantial collections, WeakMap/WeakSet for object-bound metadata, typed arrays for binary data, the substantial standard methods, the non-mutating to* variants — is the substance of JavaScript’s data-structure surface. The discipline produces clear, expressive code with substantial built-in functionality.