Skip to content
Home
JavaScript Arrays: A Complete Guide

JavaScript Arrays: A Complete Guide

JavaScript JavaScript 8 min read 1540 words Beginner David Chen

Arrays are the most fundamental data structure in JavaScript. They store ordered collections of elements and come with a rich set of built-in methods for iteration, transformation, and manipulation. Understanding arrays deeply is essential for writing effective JavaScript.

Creating Arrays

// Literal syntax (preferred)
const fruits = ['apple', 'banana', 'orange'];

// Array constructor
const numbers = new Array(1, 2, 3);

// With length
const empty = new Array(5); // [empty × 5]

// Array.of — handles single number correctly
const items = Array.of(5); // [5]

// Array.from — from iterables
const fromSet = Array.from(new Set([1, 2, 3]));
const fromString = Array.from('hello'); // ['h','e','l','l','o']
const mapped = Array.from({length: 5}, (_, i) => i * 2); // [0,2,4,6,8]

// Spread operator
const copy = [...fruits];
const combined = [...fruits, ...numbers];

Array.from with a map function is especially useful for generating sequences. Unlike Array(n).map(...) which produces a sparse array that skips holes, Array.from({length: n}, fn) produces a dense array with defined values.

Adding and Removing Elements

const arr = [1, 2, 3];

// End
arr.push(4);        // [1,2,3,4] — returns new length
const last = arr.pop(); // removes 3, returns 3

// Beginning
arr.unshift(0);     // [0,1,2,3] — returns new length (slow for large arrays)
const first = arr.shift(); // removes 0, returns 0

// Any position — splice
arr.splice(1, 0, 'a', 'b');  // insert at index 1
arr.splice(2, 1);             // remove 1 element at index 2
arr.splice(1, 1, 'x');        // replace 1 element at index 1

// Destructive vs non-destructive
// Mutating: push, pop, shift, unshift, splice, sort, reverse, fill
// Non-mutating (return new array): concat, slice, toSpliced, toSorted, toReversed

Use push and pop for stack behavior (LIFO). Avoid unshift and shift on large arrays — they force reindexing of every element, making them O(n) instead of O(1).

Iteration Methods

const arr = [10, 20, 30, 40];

// forEach — side effects
arr.forEach((value, index, array) => {
    console.log(`arr[${index}] = ${value}`);
---);

// for...of — works with break/continue
for (const value of arr) {
    if (value > 25) break;
    console.log(value);
---

// for...in — NEVER use on arrays (iterates enumerable properties)
for (const index in arr) {
    console.log(arr[index]); // works but slow and unreliable
---

// Traditional for loop — most control
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
---

forEach does not support break or continue. Use for...of when you need early termination. Use for loops when index-based logic is required or when performance is critical in hot paths.

Transformation Methods

const numbers = [1, 2, 3, 4, 5];

// map — 1:1 transformation
const doubled = numbers.map(n => n * 2); // [2,4,6,8,10]

// filter — keep matching elements
const evens = numbers.filter(n => n % 2 === 0); // [2,4]

// reduce — accumulate to single value
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

// reduce with complex accumulator
const grouped = numbers.reduce((acc, n) => {
    const key = n % 2 === 0 ? 'even' : 'odd';
    acc[key] = (acc[key] || 0) + 1;
    return acc;
---, {}); // {odd: 3, even: 2}

// flatMap — map then flatten one level
const pairs = ['hello', 'world'].flatMap(s => s.split(''));
// ['h','e','l','l','o','w','o','r','l','d']

// Chaining
const result = numbers
    .filter(n => n > 2)
    .map(n => n * 10)
    .reduce((a, b) => a + b, 0);
// 120

Chaining is elegant but creates intermediate arrays. For very large datasets, single-pass approaches with reduce are more memory-efficient. flatMap is useful when each element produces zero, one, or multiple output elements in a single pass.

Search Methods

// find — first match (returns element or undefined)
const found = users.find(user => user.id === 3);

// findIndex — first match index
const idx = users.findIndex(user => user.id === 3);

// filter — all matches
const adults = users.filter(user => user.age >= 18);

// includes — primitive search
const hasBanana = fruits.includes('banana');

// indexOf/lastIndexOf — find indices of primitives
const pos = [1, 2, 3, 2, 1].indexOf(2); // 1
const last = [1, 2, 3, 2, 1].lastIndexOf(2); // 3

// some — any element satisfies condition
const hasNegative = numbers.some(n => n < 0);

// every — all elements satisfy condition
const allPositive = numbers.every(n => n > 0);

find and findIndex accept callback functions for complex search criteria. includes uses SameValueZero equality (treats NaN correctly). some and every short-circuit on the first decisive result.

Sort

// Default sort — converts to strings!
[1, 10, 2, 20].sort(); // [1, 10, 2, 20] — wrong!

// Numeric sort
numbers.sort((a, b) => a - b);       // ascending
numbers.sort((a, b) => b - a);       // descending

// Object sort
users.sort((a, b) => a.name.localeCompare(b.name));

// Stable sort — guaranteed stable since ES2019
// Equal elements retain their original order

The default sort compares string representations, which produces unexpected results for numbers. Always provide a comparator function. Modern JavaScript engines use Timsort, giving O(n log n) worst-case performance and O(n) for nearly-sorted data.

Destructuring and Spread

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// Default values
const [a = 10, b = 20] = [undefined, 5];
// a=10, b=5

// Swapping without temp variable
[a, b] = [b, a];

// Function parameters
function sum([a, b, c]) {
    return a + b + c;
---

// Rest parameters
function multiply(multiplier, ...numbers) {
    return numbers.map(n => n * multiplier);
---

Destructuring with rest patterns creates shallow copies. Deeply nested destructuring is possible but often hurts readability — prefer extracting step by step for complex structures.

Performance Considerations

// Preallocate for large arrays
const n = 1000000;
const arr = new Array(n);
for (let i = 0; i < n; i++) arr[i] = i;

// Avoid spreading large arrays — call stack limit!
// Bad: Math.max(...hugeArray)
// Good: hugeArray.reduce((a, b) => Math.max(a, b), -Infinity)

// Typed Arrays for binary data
const buffer = new ArrayBuffer(1024);
const int32 = new Int32Array(buffer);

// Sparse arrays — avoid holes
const sparse = [1,,,4]; // creates holes
sparse[5] = 6;          // now [1, empty × 2, 4, empty × 1, 6]
// Holes behave differently in methods
console.log(0 in sparse);  // true (index 0 has value)
console.log(1 in sparse);  // false (hole)

Method Performance Notes

  • push/pop — O(1) amortized
  • shift/unshift — O(n) (reindexes all elements)
  • splice — O(n) in worst case
  • Access by index — O(1)
  • forEach/map/filter — O(n) with callback overhead
  • sort — O(n log n)

Multidimensional Arrays

// Creating
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

// Access
matrix[1][2]; // 6 (row 1, col 2)

// Row-major iteration
for (let r = 0; r < matrix.length; r++) {
    for (let c = 0; c < matrix[r].length; c++) {
        console.log(matrix[r][c]);
    }
---

Typed Arrays for Binary Data

For binary data, JavaScript provides typed arrays that represent views over a shared ArrayBuffer:

// Create a buffer of 16 bytes
const buffer = new ArrayBuffer(16);

// Create different views over the same buffer
const int32View = new Int32Array(buffer);
const float64View = new Float64Array(buffer);
const uint8View = new Uint8Array(buffer);

int32View[0] = 42;
console.log(uint8View[0]); // 42 (same memory, different interpretation)

// Typed arrays are not regular arrays — they have fixed length
console.log(int32View.length); // 4 (16 bytes / 4 bytes per int32)
console.log(int32View.BYTES_PER_ELEMENT); // 4

Set Operations on Arrays

const A = [1, 2, 3, 4, 5];
const B = [4, 5, 6, 7, 8];

// Union
const union = [...new Set([...A, ...B])];

// Intersection
const intersection = A.filter(x => B.includes(x));

// Difference (A - B)
const difference = A.filter(x => !B.includes(x));

// Symmetric difference
const symmetricDiff = [
    ...A.filter(x => !B.includes(x)),
    ...B.filter(x => !A.includes(x)),
];

Array Performance Characteristics

MethodTime ComplexityReturnsMutates
pushO(1) amortizednew lengthyes
popO(1)removed elementyes
shiftO(n)removed elementyes
unshiftO(n)new lengthyes
spliceO(n)removed elementsyes
sliceO(n)new arrayno
indexOfO(n)index or -1no
sortO(n log n)sorted arrayyes
forEachO(n)undefinedno
mapO(n)new arrayno
filterO(n)new arrayno
reduceO(n)accumulatorno
flatO(n)new arrayno

FAQ

Q: How do I check if something is an array? A: Use Array.isArray(value). Do NOT use typeof — it returns ‘object’ for arrays.

Q: What is the difference between forEach and map? A: forEach iterates for side effects and returns undefined. map creates a new array with transformed values. Use forEach when you want to do something with each element; use map when you want a derived array.

Q: How do I remove duplicates from an array? A: [...new Set(arr)] for primitives. For objects, use Array.filter with a Set-based seen tracker or lodash.uniqBy.

Q: How do I convert an array-like object to an array? A: Use Array.from(arrayLike) or [...arrayLike]. Array.from supports a map function as second argument.

Q: What is the maximum array length? A: 2^32 - 1 (4294967295). Real limits are lower — typically a few hundred million elements before memory is exhausted.

Q: Are JavaScript arrays contiguous? A: JavaScript arrays are objects, not contiguous memory blocks. V8 optimizes contiguous integer-keyed storage, but holes or non-integer keys cause deoptimization to dictionary mode.

For a comprehensive overview, read our article on Javascript Async Await.

For a comprehensive overview, read our article on Javascript Closures Guide.

Section: JavaScript 1540 words 8 min read Beginner 1251 articles in section Report inaccuracy Back to top
DC
David Chen Technology Editor

Technology Editor at ExcellentWiki covering programming, engineering, and consumer technology topics.

View author profile →