Polyglot
Languages Web (HTML / CSS / JS) data structures
Web (HTML / CSS / JS) § data-structures

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

MapObject
Key typesanystring, symbol
Iteration orderinsertionmostly insertion (with caveats for integer keys)
.sizeyesuse Object.keys().length
Iterationdirectvia Object.keys() etc.
Performancesubstantial for substantial frequent add/removesubstantial for substantial small fixed-shape
JSONnot nativeyes

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 Array for sequences.
  • Use Object for records (string-keyed).
  • Use Map for substantial collections; non-string keys.
  • Use Set for unique-value collections.
  • Use WeakMap/WeakSet for object-keyed metadata.
  • Use typed arrays for binary data.
  • Use Object.fromEntries and Object.entries for 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.hasOwn over hasOwnProperty.
  • Use Array.isArray over instanceof Array.
  • Reach for Temporal (Stage 3) or date-fns over native Date.

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.