Skip to content
Home
JavaScript Arrays: A Complete Guide

JavaScript Arrays: A Complete Guide

JavaScript JavaScript 8 min read 1540 words Beginner ExcellentWiki Editorial Team

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 756 articles in section Report inaccuracy Back to top