JavaScript Arrays: A Complete Guide
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) amortizedshift/unshift— O(n) (reindexes all elements)splice— O(n) in worst case- Access by index — O(1)
forEach/map/filter— O(n) with callback overheadsort— 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
| Method | Time Complexity | Returns | Mutates |
|---|---|---|---|
| push | O(1) amortized | new length | yes |
| pop | O(1) | removed element | yes |
| shift | O(n) | removed element | yes |
| unshift | O(n) | new length | yes |
| splice | O(n) | removed elements | yes |
| slice | O(n) | new array | no |
| indexOf | O(n) | index or -1 | no |
| sort | O(n log n) | sorted array | yes |
| forEach | O(n) | undefined | no |
| map | O(n) | new array | no |
| filter | O(n) | new array | no |
| reduce | O(n) | accumulator | no |
| flat | O(n) | new array | no |
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.