# Arrays

## 1. Arrays Are Objects

An array is a specialized object with numeric-string keys and automatic `length` management, whose prototype is `Array.prototype`. `typeof [] === "object"`; use `Array.isArray()` to detect arrays specifically.

## 2. Mutating vs Non-Mutating Methods

This is the single most important distinction to memorize — mixing these up causes some of the most common array bugs.

| Mutates the original array | Returns a new array / value |
|---|---|
| `push`, `pop` | `concat` |
| `shift`, `unshift` | `slice` |
| `splice` | `map` |
| `sort` | `filter` |
| `reverse` | `reduce` |
| `fill` | `flat`, `flatMap` |
| `copyWithin` | spread `[...arr]` |

```js
const nums = [3, 1, 2];

const sorted = nums.sort();   // MUTATES nums in place, also returns it
console.log(nums);            // [1, 2, 3] — original changed!

const doubled = nums.map(n => n * 2); // does NOT mutate nums
console.log(nums);             // [1, 2, 3] — unchanged
console.log(doubled);          // [2, 4, 6]
```

**Rule of thumb:** if you need to keep the original array intact (common in React/Redux state updates), always use the non-mutating column, or copy first: `[...nums].sort()`.

## 3. The Core Iteration Methods

| Method | Purpose | Returns |
|---|---|---|
| `map(fn)` | transform each element | new array, same length |
| `filter(fn)` | keep elements matching a condition | new array, ≤ length |
| `reduce(fn, initial)` | fold all elements into one value | any single value |
| `forEach(fn)` | side effects only, no return use | `undefined` |
| `find(fn)` | first matching element | element or `undefined` |
| `findIndex(fn)` | index of first match | number or `-1` |
| `some(fn)` | does at least one element match? | boolean |
| `every(fn)` | do all elements match? | boolean |

```js
const orders = [
  { id: 1, total: 50, paid: true },
  { id: 2, total: 30, paid: false },
  { id: 3, total: 90, paid: true },
];

const paidTotal = orders
  .filter(o => o.paid)
  .reduce((sum, o) => sum + o.total, 0); // 140

const hasUnpaid = orders.some(o => !o.paid); // true
const allPaid = orders.every(o => o.paid);   // false
```

## 4. `reduce` Deep Dive

`reduce` is the most general iteration method — `map` and `filter` can both be implemented on top of it:

```js
// map via reduce
const doubled = [1, 2, 3].reduce((acc, n) => { acc.push(n * 2); return acc; }, []);

// filter via reduce
const evens = [1, 2, 3, 4].reduce((acc, n) => { if (n % 2 === 0) acc.push(n); return acc; }, []);

// grouping — a common real-world reduce use case
const grouped = orders.reduce((acc, o) => {
  const key = o.paid ? "paid" : "unpaid";
  (acc[key] ??= []).push(o);
  return acc;
}, {});
```

Always pass an explicit **initial value** as the second argument — omitting it uses the first array element as the initial accumulator, which breaks on empty arrays and is a common source of bugs.

## 5. Copying, Slicing, Flattening

```js
const original = [1, [2, 3], [4, [5, 6]]];

original.slice(1, 3);      // [[2, 3], [4, [5, 6]]] — shallow copy of a range, non-mutating
original.flat();            // [1, 2, 3, 4, [5, 6]] — flattens ONE level by default
original.flat(Infinity);    // [1, 2, 3, 4, 5, 6] — flattens ALL levels
original.flatMap(x => [x, x]); // map + flatten one level, in a single pass
```

## 6. Destructuring and Spread

```js
const [first, second, ...rest] = [1, 2, 3, 4]; // first=1, second=2, rest=[3,4]
const combined = [...[1, 2], ...[3, 4]];         // [1, 2, 3, 4]
const [, , third] = [1, 2, 3];                   // skip elements with empty slots — third=3
```

## 7. Under the Hood: Elements Kinds — Arrays Are Not All Stored the Same Way

A JavaScript array is not one fixed data structure internally — V8 tracks an **elements kind** per array and silently switches representation as the array's contents change, trading flexibility for speed wherever possible.

### The elements kind ladder

![Elements kind ladder: PACKED_SMI, PACKED_DOUBLE, PACKED_ELEMENTS, then DICTIONARY_ELEMENTS](diagrams/01-elements-kind-ladder.svg)

| Elements kind | What it holds | Storage |
|---|---|---|
| `PACKED_SMI_ELEMENTS` | only small integers, no holes | flat buffer of raw tagged SMIs — fastest possible |
| `PACKED_DOUBLE_ELEMENTS` | numbers including non-integers, no holes | flat buffer of raw doubles |
| `PACKED_ELEMENTS` | any mix of value types, no holes | flat buffer of generic tagged values (objects, strings, mixed numbers) |
| `HOLEY_*` variants of the above | same content types, but with gaps | same buffer, PLUS a hole-check on every single read |
| `DICTIONARY_ELEMENTS` | extremely sparse arrays | an actual hash table (same structure as the Hashing topic), not a flat buffer at all |

```js
const a = [1, 2, 3];        // PACKED_SMI_ELEMENTS — fastest tier
a.push(3.14);                 // now contains a non-integer -> demoted to PACKED_DOUBLE_ELEMENTS
a.push("text");                // now contains a non-number -> demoted to PACKED_ELEMENTS
```

### Transitions only go one direction

```js
const b = [1, 2, 3];         // PACKED_SMI_ELEMENTS
b[10] = 1;                    // creates indices 3-9 as HOLES -> demoted to HOLEY_SMI_ELEMENTS
b.length = 3;                  // shrinking back does NOT undo the demotion — still holey internally
```

Once an array is demoted to a more general elements kind, it **never gets promoted back**, even if every element that caused the demotion is later removed. This is why the array-methods table in section 2 matters beyond just "does it mutate": operations that introduce holes (`delete arr[i]`, sparse index assignment) or mix types permanently shift the array onto a slower access path for its entire remaining lifetime.

### Why `delete arr[i]` is different from `splice`

```js
const c = [1, 2, 3, 4];
delete c[1];        // leaves a HOLE at index 1 — length stays 4, elements kind becomes HOLEY_*
c.splice(1, 1);       // actually removes the element, shifts everything after it left, length becomes 3
```

`delete` never shrinks the array or shifts anything — it just punches a hole, forcing every subsequent read of that array to carry a hole-check (`HOLEY_*` kinds check "is this slot actually empty?" on every access, which `PACKED_*` kinds skip entirely). `splice` costs more up front (O(n) shifting) but keeps the array dense and fast afterward.

### `push`/`pop` vs `shift`/`unshift`, precisely

A flat array buffer supports appending/removing at the **end** by just adjusting a length pointer — O(1) amortized, identical in spirit to a hash table's amortized resize (Map/Set topics). Removing or inserting at the **front** requires physically shifting every remaining element's index by one slot — a real O(n) memory move, not just a pointer adjustment. This is a direct, concrete consequence of arrays being stored as contiguous buffers rather than linked structures.

### Dictionary-mode arrays: when a "sparse" array becomes a hash table

```js
const sparse = [];
sparse[0] = "a";
sparse[100000] = "z"; // 99,999 holes between them
```

Allocating a contiguous buffer for 100,001 slots to hold two real values would waste enormous memory. Past a sparseness threshold, V8 gives up on the flat-buffer representation entirely and stores the array's elements in a **dictionary** (hash table) keyed by index — the same fundamental trade-off Objects make when demoted to dictionary mode (see the Objects topic). `sparse.length` still reports `100001` correctly, but iteration and access no longer behave like a simple buffer walk underneath.

## 8. Common Pitfalls

1. **`sort()` sorts as strings by default** — `[10, 2, 1].sort()` gives `[1, 10, 2]`, not `[1, 2, 10]`. Always pass a comparator for numbers: `.sort((a, b) => a - b)`.
2. **Mutating an array you meant to copy** — e.g. calling `.sort()` or `.reverse()` on a state array in React without spreading first.
3. **`forEach` cannot be `break`-ed or awaited meaningfully** — use `for...of` (supports `break`, `continue`, `await`) when you need early exit or sequential async work.
4. **`reduce` without an initial value on an empty array** throws a `TypeError`.
5. **`delete arr[i]` leaves a hole** instead of shrinking the array — use `splice(i, 1)` to actually remove an element.
6. **Sparse arrays behaving inconsistently** — holes are skipped by `map`/`forEach`/`filter` but counted by `length`.

## 9. Quick Reference

```
map/filter/reduce/slice/concat/flat  → non-mutating, safe to chain
push/pop/shift/unshift/splice/sort/reverse/fill → mutating, use with care
[...arr]                              → cheap shallow copy before mutating methods
```
