Native Data Structures (JavaScript)
Choosing a native collection
- Fixed schema / DTO / JSON: use
Object (avoid Map, no JSON.stringify)
- Dynamic string-to-value dict: use
Map or Object.create(null) (avoid plain {} if keys are user input)
- Non-string keys, insertion order,
.size: use Map (avoid Object)
- Ordered list, index access, transform: use
Array (avoid Set, no indexes)
- Unique values, fast membership: use
Set (avoid Array.includes on large/dynamic sets)
- Attach data to objects without retaining them: use
WeakMap (avoid Map, leaks)
- Track object presence without retaining: use
WeakSet (avoid Set, leaks)
- Numeric / binary data, I/O, crypto: use TypedArray /
ArrayBuffer (avoid number[] for hot binary work)
- Hot queue / deque / stack ends: do not rely on
Array.shift/unshift — use a custom ring or linked structure
Complexity and code characteristics
Choose a structure for the property you need to control — not only for speed.
Big-O (typical average case; n = collection size):
Object field access: O(1) for fixed shapes; dynamic/delete-heavy objects degrade toward dictionary mode
Array index get/set, push/pop: O(1); includes/indexOf/splice/shift/unshift: O(n)
Map get/set/has/delete, Set add/has/delete: amortized O(1); iteration: O(n)
WeakMap/WeakSet get/set/has/delete: amortized O(1); no iteration
- TypedArray index get/set: O(1); grow/copy: O(n)
- Prefer the structure whose hot operation is O(1); measure before optimizing rare paths
Readability and semantics:
- Let the type state the intent:
Set = unique membership; Map = keyed registry; Array = ordered sequence; plain Object = record/DTO
- Prefer a named collection over encoding the same idea in ad-hoc flags, parallel arrays, or stringly keys
- Use array methods (
.map, .filter, .reduce) when they match the transform; use explicit loops when control flow or performance matters
- Avoid over-abstracting: a small static
Array allowlist is clearer than a Set used once
Naming:
- Plural names for collections (
users, sessions); singular for entities (user)
- Name by role, not by type:
seenIds (Set), socketToSession (Map) — not set1 / mapData
- Boolean membership reads as
has/is: if (seen.has(id)), blocked.has(user)
- Include units in size names (
sizeBytes, timeoutMs) when the collection holds measured quantities
Stability and contracts:
- Treat inputs as immutable at API boundaries; copy or freeze when you must share
- Prefer stable iteration order when callers rely on it (
Map/Set/Array insertion order)
- Keep record shapes and array element kinds stable over time (see per-structure V8 hints)
- Document whether a returned collection is live (shared) or a snapshot
Testability:
- Prefer pure functions over collections: same inputs, same outputs; inject maps/sets as arguments when state is required
- Favor deterministic structures in tests: plain data over WeakMap (non-enumerable), over insertion-timed WeakRef behavior
- Assert on observable contents (
[...set], Object.fromEntries(map), length/size) rather than private fields
- Small fixtures: build with literals/
Object.freeze when the shape should not change under test
Encapsulation, safety, operability:
- Hide representation behind a small API (
add/has, getters) so internals can change without call-site churn
- Use
Object.create(null) or Map for untrusted keys (prototype-pollution safety)
- Use WeakMap/WeakSet when lifetime must follow the key (leak control)
- Cap growth (max size, TTL, eviction) for long-lived Maps/Sets; unbounded collections are operability bugs
- Pick serializable structures (
Object/Array) at I/O boundaries; convert Map/Set explicitly
Object
Use for records with a known, stable shape (config, entities, options).
const user = { id: 1, name: 'Ada', active: true };
// Pure dictionary (no prototype pollution via __proto__)
const dict = Object.create(null);
dict[key] = value;
Hints:
- Keep a stable shape (hidden class): same keys, types, and key order; init once in a factory/constructor
- Prefer fixed access (
obj.x) over dynamic keys (obj[key]) when the schema is known
- Do not
delete fields; set null for refs, undefined for primitives
- Objects that share a property should share the full shape — avoid polymorphic property reads
for...in is slow on hot paths; prefer Object.keys / Object.entries
Array
Use for ordered sequences, stacks (push/pop), and small membership checks.
const ids = [10, 20, 30];
ids.push(40);
const last = ids.pop();
const ALLOWED = ['read', 'write'];
if (ALLOWED.includes(role)) {
/* ... */
}
Hints:
- Keep arrays dense and monomorphic (one element kind): SMI ints, doubles, or objects — do not mix
- Prefer SMI-friendly integers (31-bit signed) for indexes/counters (
PACKED_SMI_ELEMENTS); a float, NaN, or hole promotes the whole array
- Avoid holey arrays (
new Array(n), deletes, skips); build with push or fill sequentially
- Avoid
shift() / unshift() in hot paths; use a queue or index cursors
- Prefer
for / for...of in hot loops; avoid array destructuring in hot assignments
Map
Use for dynamic key-value stores with any key type and predictable iteration.
const sessions = new Map();
sessions.set(socket, { userId: 42 });
sessions.set('fallback', null);
for (const [key, value] of sessions) {
// insertion order
}
sessions.delete(socket);
console.log(sessions.size);
Hints:
- Dynamic keys on
Map avoid dictionary-mode / shape pollution on {}
- Values read on hot paths should still use stable shapes
NaN is a valid single key; object keys compare by reference
- No
JSON.stringify — use Object.fromEntries(map) only if keys are strings
Set
Use for unique values and fast membership.
const seen = new Set();
for (const id of incoming) {
if (seen.has(id)) continue;
seen.add(id);
process(id);
}
const unique = [...new Set(items)];
Hints:
- Uniqueness is by reference for objects, not deep value
- Insertion-order iteration; no index access
- Array/Set conversion allocates — do it at boundaries, not in hot loops
WeakMap / WeakSet
Weak collections hold keys weakly so entries can be GC'd when the key is otherwise unreachable. Keys must be objects or symbols. Not iterable; no .size.
const meta = new WeakMap();
const tracked = new WeakSet();
const attach = (obj, data) => {
meta.set(obj, data);
tracked.add(obj);
};
Hints:
- Prefer WeakMap side tables over adding ad-hoc fields to host objects
- Cannot enumerate — keep a strong list only if you must iterate
WeakRef / FinalizationRegistry are for lifetime hooks, not general storage
Typed arrays
Use for contiguous numeric/binary data: network frames, files, crypto, image/audio, WASM.
Uint8Array — bytes, I/O, hashing
Int16Array / Uint16Array — PCM, compact IDs
Uint32Array / Int32Array — indexes, bitsets, WASM i32
Float32Array / Float64Array — geometry, signals, tensors
BigInt64Array / BigUint64Array — 64-bit IDs, timestamps
DataView — endian-aware protocol parsing
ArrayBuffer / SharedArrayBuffer — backing store; SAB for workers
const buf = new ArrayBuffer(16);
const bytes = new Uint8Array(buf);
const view = new DataView(buf);
view.setUint32(0, 0xdeadbeef, true);
bytes[4] = 0xff;
const frame = bytes.subarray(0, 8); // shared memory; `slice` copies
Hints:
- Fixed length — size ahead or allocate larger and copy to grow
- Prefer typed arrays over
number[] for tight numeric loops (stable element type)
- Use
DataView for wire endianness; multi-byte typed views are platform-endian
- Reuse/pool buffers in hot paths; Node
Buffer is a Uint8Array — prefer Uint8Array in shared code unless you need Buffer APIs
Conventions
- Keep hot code monomorphic: stable argument count, types, return types, and object shapes
- Prefer SMI integers for indexes/sizes; do not mix ints and floats in one variable
- Reduce GC pressure: reuse arrays, objects, and buffers when safe
- Move reusable callbacks outside loops; keep
try/catch and spread out of hot loops
1---2name: js-data-structures3description: Choose and use native JavaScript collections for Big-O, readability, semantics, naming, stability, or testability. Use when working with Map, Object, Set, Array, WeakMap, WeakSet, TypedArray, or deciding among built-in collections. For custom queues, lists, heaps, tries, and related structures, use the data-structures skill.4---56# Native Data Structures (JavaScript)78## Choosing a native collection910- Fixed schema / DTO / JSON: use `Object` (avoid `Map`, no `JSON.stringify`)11- Dynamic string-to-value dict: use `Map` or `Object.create(null)` (avoid plain `{}` if keys are user input)12- Non-string keys, insertion order, `.size`: use `Map` (avoid `Object`)13- Ordered list, index access, transform: use `Array` (avoid `Set`, no indexes)14- Unique values, fast membership: use `Set` (avoid `Array.includes` on large/dynamic sets)15- Attach data to objects without retaining them: use `WeakMap` (avoid `Map`, leaks)16- Track object presence without retaining: use `WeakSet` (avoid `Set`, leaks)17- Numeric / binary data, I/O, crypto: use TypedArray / `ArrayBuffer` (avoid `number[]` for hot binary work)18- Hot queue / deque / stack ends: do not rely on `Array.shift`/`unshift` — use a custom ring or linked structure1920## Complexity and code characteristics2122Choose a structure for the property you need to control — not only for speed.2324**Big-O (typical average case; n = collection size):**2526- `Object` field access: O(1) for fixed shapes; dynamic/`delete`-heavy objects degrade toward dictionary mode27- `Array` index get/set, `push`/`pop`: O(1); `includes`/`indexOf`/`splice`/`shift`/`unshift`: O(n)28- `Map` get/set/has/delete, `Set` add/has/delete: amortized O(1); iteration: O(n)29- `WeakMap`/`WeakSet` get/set/has/delete: amortized O(1); no iteration30- TypedArray index get/set: O(1); grow/copy: O(n)31- Prefer the structure whose hot operation is O(1); measure before optimizing rare paths3233**Readability and semantics:**3435- Let the type state the intent: `Set` = unique membership; `Map` = keyed registry; `Array` = ordered sequence; plain `Object` = record/DTO36- Prefer a named collection over encoding the same idea in ad-hoc flags, parallel arrays, or stringly keys37- Use array methods (`.map`, `.filter`, `.reduce`) when they match the transform; use explicit loops when control flow or performance matters38- Avoid over-abstracting: a small static `Array` allowlist is clearer than a `Set` used once3940**Naming:**4142- Plural names for collections (`users`, `sessions`); singular for entities (`user`)43- Name by role, not by type: `seenIds` (Set), `socketToSession` (Map) — not `set1` / `mapData`44- Boolean membership reads as `has`/`is`: `if (seen.has(id))`, `blocked.has(user)`45- Include units in size names (`sizeBytes`, `timeoutMs`) when the collection holds measured quantities4647**Stability and contracts:**4849- Treat inputs as immutable at API boundaries; copy or freeze when you must share50- Prefer stable iteration order when callers rely on it (`Map`/`Set`/`Array` insertion order)51- Keep record shapes and array element kinds stable over time (see per-structure V8 hints)52- Document whether a returned collection is live (shared) or a snapshot5354**Testability:**5556- Prefer pure functions over collections: same inputs, same outputs; inject maps/sets as arguments when state is required57- Favor deterministic structures in tests: plain data over WeakMap (non-enumerable), over insertion-timed WeakRef behavior58- Assert on observable contents (`[...set]`, `Object.fromEntries(map)`, length/size) rather than private fields59- Small fixtures: build with literals/`Object.freeze` when the shape should not change under test6061**Encapsulation, safety, operability:**6263- Hide representation behind a small API (`add`/`has`, getters) so internals can change without call-site churn64- Use `Object.create(null)` or `Map` for untrusted keys (prototype-pollution safety)65- Use WeakMap/WeakSet when lifetime must follow the key (leak control)66- Cap growth (max size, TTL, eviction) for long-lived Maps/Sets; unbounded collections are operability bugs67- Pick serializable structures (`Object`/`Array`) at I/O boundaries; convert `Map`/`Set` explicitly6869## Object7071Use for records with a known, stable shape (config, entities, options).7273```javascript74const user = { id: 1, name: 'Ada', active: true };7576// Pure dictionary (no prototype pollution via __proto__)77const dict = Object.create(null);78dict[key] = value;79```8081**Hints:**8283- Keep a stable shape (hidden class): same keys, types, and key order; init once in a factory/constructor84- Prefer fixed access (`obj.x`) over dynamic keys (`obj[key]`) when the schema is known85- Do not `delete` fields; set `null` for refs, `undefined` for primitives86- Objects that share a property should share the full shape — avoid polymorphic property reads87- `for...in` is slow on hot paths; prefer `Object.keys` / `Object.entries`8889## Array9091Use for ordered sequences, stacks (`push`/`pop`), and small membership checks.9293```javascript94const ids = [10, 20, 30];95ids.push(40);96const last = ids.pop();9798const ALLOWED = ['read', 'write'];99if (ALLOWED.includes(role)) {100 /* ... */101}102```103104**Hints:**105106- Keep arrays dense and monomorphic (one element kind): SMI ints, doubles, or objects — do not mix107- Prefer SMI-friendly integers (31-bit signed) for indexes/counters (`PACKED_SMI_ELEMENTS`); a float, `NaN`, or hole promotes the whole array108- Avoid holey arrays (`new Array(n)`, deletes, skips); build with `push` or fill sequentially109- Avoid `shift()` / `unshift()` in hot paths; use a queue or index cursors110- Prefer `for` / `for...of` in hot loops; avoid array destructuring in hot assignments111112## Map113114Use for dynamic key-value stores with any key type and predictable iteration.115116```javascript117const sessions = new Map();118sessions.set(socket, { userId: 42 });119sessions.set('fallback', null);120121for (const [key, value] of sessions) {122 // insertion order123}124sessions.delete(socket);125console.log(sessions.size);126```127128**Hints:**129130- Dynamic keys on `Map` avoid dictionary-mode / shape pollution on `{}`131- Values read on hot paths should still use stable shapes132- `NaN` is a valid single key; object keys compare by reference133- No `JSON.stringify` — use `Object.fromEntries(map)` only if keys are strings134135## Set136137Use for unique values and fast membership.138139```javascript140const seen = new Set();141for (const id of incoming) {142 if (seen.has(id)) continue;143 seen.add(id);144 process(id);145}146147const unique = [...new Set(items)];148```149150**Hints:**151152- Uniqueness is by reference for objects, not deep value153- Insertion-order iteration; no index access154- Array/Set conversion allocates — do it at boundaries, not in hot loops155156## WeakMap / WeakSet157158Weak collections hold keys weakly so entries can be GC'd when the key is otherwise unreachable. Keys must be objects or symbols. Not iterable; no `.size`.159160```javascript161const meta = new WeakMap();162const tracked = new WeakSet();163164const attach = (obj, data) => {165 meta.set(obj, data);166 tracked.add(obj);167};168```169170**Hints:**171172- Prefer WeakMap side tables over adding ad-hoc fields to host objects173- Cannot enumerate — keep a strong list only if you must iterate174- `WeakRef` / `FinalizationRegistry` are for lifetime hooks, not general storage175176## Typed arrays177178Use for contiguous numeric/binary data: network frames, files, crypto, image/audio, WASM.179180- `Uint8Array` — bytes, I/O, hashing181- `Int16Array` / `Uint16Array` — PCM, compact IDs182- `Uint32Array` / `Int32Array` — indexes, bitsets, WASM i32183- `Float32Array` / `Float64Array` — geometry, signals, tensors184- `BigInt64Array` / `BigUint64Array` — 64-bit IDs, timestamps185- `DataView` — endian-aware protocol parsing186- `ArrayBuffer` / `SharedArrayBuffer` — backing store; SAB for workers187188```javascript189const buf = new ArrayBuffer(16);190const bytes = new Uint8Array(buf);191const view = new DataView(buf);192view.setUint32(0, 0xdeadbeef, true);193bytes[4] = 0xff;194const frame = bytes.subarray(0, 8); // shared memory; `slice` copies195```196197**Hints:**198199- Fixed length — size ahead or allocate larger and copy to grow200- Prefer typed arrays over `number[]` for tight numeric loops (stable element type)201- Use `DataView` for wire endianness; multi-byte typed views are platform-endian202- Reuse/pool buffers in hot paths; Node `Buffer` is a `Uint8Array` — prefer `Uint8Array` in shared code unless you need Buffer APIs203204## Conventions205206- Keep hot code monomorphic: stable argument count, types, return types, and object shapes207- Prefer SMI integers for indexes/sizes; do not mix ints and floats in one variable208- Reduce GC pressure: reuse arrays, objects, and buffers when safe209- Move reusable callbacks outside loops; keep `try/catch` and spread out of hot loops