Skip to main content

Command Palette

Search for a command to run...

JavaScript Arrays: A Comprehensive Guide

Updated
14 min readView as Markdown
JavaScript Arrays: A Comprehensive Guide
G

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for me—it's a passion that drives continuous learning and innovation.

Last updated: September 2026 — fixed four incorrect code samples, replaced the unfulfilled follow-up promise with the actual method coverage, and added sort, at(), the ES2023 immutable methods, flatMap and reduce.

Welcome to our comprehensive guide on JavaScript Arrays. In this article we'll dive deep into JavaScript arrays — how to create them, read and write them, iterate them, and use the methods you will reach for daily. Whether you're refreshing your knowledge or taking your first steps, this guide has something for you.

1. Introduction to Arrays

Arrays are one of the fundamental data structures in JavaScript. They store collections of data — numbers, strings, objects, or a mix. Arrays in JavaScript are versatile and can be manipulated in many ways.

// Creating an array of numbers
let myArray = [1, 2, 3, 4, 5];

Destructuring (ES6):

const [first, second, ...rest] = myArray;
console.log(first, second, rest);   // 1 2 [ 3, 4, 5 ]

The distinction that matters most

Before the methods, one division that saves more debugging time than anything else in this article. Some array methods change the array you call them on. Others return a new array and leave the original alone.

These mutate:

push pop shift unshift splice sort reverse fill copyWithin

These do not:

map filter slice concat flat flatMap toSorted toReversed toSpliced with

Two of the mutating ones catch everyone, because they also return the array — so the assignment gives no hint that anything changed:

const original = [1, 2, 3];
const reversed = original.reverse();

console.log(reversed);              // [ 3, 2, 1 ]
console.log(original);              // [ 3, 2, 1 ]  <-- changed too
console.log(reversed === original); // true — it is the same array

If original was props in a React component or shared state, you just modified it under someone else. Keep this split in mind for the rest of the article.

2. Creating Arrays

You can create arrays in several ways.

// Literal
let numbers = [1, 2, 3];

// Empty, then add
let emptyArray = [];
emptyArray.push("Hello");
emptyArray.push("World");
console.log(emptyArray);   // [ 'Hello', 'World' ]

Spread (ES6):

let newArray = [...emptyArray, "Welcome"];
console.log(newArray);   // [ 'Hello', 'World', 'Welcome' ]

Array.from and Array.of:

Array.from("abc");                        // [ 'a', 'b', 'c' ]
Array.from({ length: 5 }, (_, i) => i * 2); // [ 0, 2, 4, 6, 8 ]
Array.of(7);                              // [ 7 ]

That last line is worth a note. Array.of(7) gives you [7], but Array(7) gives you an array of length 7 with no elements — a quirk of the old constructor that Array.of exists to work around.

Array(7).length;   // 7
Array.of(7);       // [ 7 ]

3. Reading and Writing Array Elements

JavaScript uses zero-based indexing. Access an element with square brackets:

let fruits = ["apple", "banana", "cherry"];
console.log(fruits[0]);   // "apple"

fruits[1] = "blueberry";
console.log(fruits);      // [ 'apple', 'blueberry', 'cherry' ]

Reading past the end gives undefined, not an error:

console.log(fruits[99]);  // undefined

Reading from the end: at()

Brackets cannot take a negative index, so the last element used to need one of these:

fruits[fruits.length - 1];   // "cherry"
fruits.slice(-1)[0];         // "cherry" — works, but builds a throwaway array

at() (ES2022) does it directly:

fruits.at(-1);    // "cherry"
fruits.at(0);     // "apple"
fruits.at(-99);   // undefined — out of range, no error

Note that brackets do not do this at all: fruits[-1] is undefined, because -1 is just a property name that does not exist.

4. Sparse Arrays

JavaScript allows arrays to be sparse — to have gaps where no element exists at all.

let sparseArray = [1, , 3];
console.log(sparseArray.length);   // 3
console.log(sparseArray[1]);       // undefined

A gap is called a hole, and it is not the same as an element holding undefined. The difference is visible:

const holes  = [1, , 3];
const filled = [1, undefined, 3];

console.log(1 in holes);    // false — index 1 does not exist
console.log(1 in filled);   // true  — index 1 exists and holds undefined

console.log(holes.reduce((count) => count + 1, 0));    // 2  <-- holes are skipped
console.log(filled.reduce((count) => count + 1, 0));   // 3

Iteration methods — map, filter, forEach, reduce — walk straight past holes. That makes sparse arrays a quiet source of off-by-one bugs.

Array.from converts holes into real undefined elements:

let denseArray = Array.from(sparseArray);
console.log(denseArray);   // [ 1, undefined, 3 ]
console.log(1 in denseArray);   // true

The practical advice: do not create sparse arrays on purpose. If you want an empty slot, put undefined in it.

5. Array Length

length gives the number of elements:

let numbers = [10, 20, 30, 40, 50];
console.log(numbers.length);   // 5

length is writable, which is an easy way to truncate an array:

numbers.length = 3;
console.log(numbers);   // [ 10, 20, 30 ]

And because it counts slots rather than elements, it is the one measure that sees holes:

const sparse = [1, , 3];
console.log(sparse.length);   // 3 — counts the hole

6. Adding and Removing Elements

Four mutating methods handle the ends of an array:

let numbers = [10, 20, 30];

numbers.push(40);      // add to the end
numbers.pop();         // remove from the end,   returns 40
numbers.unshift(5);    // add to the front
numbers.shift();       // remove from the front, returns 5

console.log(numbers);  // [ 10, 20, 30 ]

All four return something useful: push/unshift return the new length, pop/shift return the element they removed.

splice handles the middle — it removes, inserts, or both:

const items = ["a", "b", "c", "d"];

items.splice(1, 2);            // remove 2 items starting at index 1
console.log(items);            // [ 'a', 'd' ]

items.splice(1, 0, "x", "y");  // remove 0, insert two at index 1
console.log(items);            // [ 'a', 'x', 'y', 'd' ]

Non-mutating alternatives using spread, when you need the original left alone:

const numbers = [10, 20, 30];

const withExtra = [...numbers, 40];       // add to end
const withFirst = [0, ...numbers];        // add to front
const [firstItem, ...withoutFirst] = numbers;   // remove from front
const withoutLast = numbers.slice(0, -1);       // remove from end

console.log(numbers);         // [ 10, 20, 30 ] — untouched
console.log(firstItem);       // 10
console.log(withoutFirst);    // [ 20, 30 ]
console.log(withoutLast);     // [ 10, 20 ]

Note the destructuring line carefully: const [firstItem, ...rest] takes the first element, not the last. Destructuring always reads from the front.

7. Iterating Arrays

const fruits = ["apple", "banana", "cherry"];

// forEach — a callback per element, returns undefined
fruits.forEach(function (fruit) {
    console.log(fruit);
});

// for...of — a real loop, so break and continue work
for (const fruit of fruits) {
    console.log(fruit);
}

// entries() when you need the index too
for (const [i, fruit] of fruits.entries()) {
    console.log(i, fruit);   // 0 apple, 1 banana, 2 cherry
}

Use for...of over forEach when you might need to stop early — break and continue do not exist inside a forEach callback, and return there only exits that one call.

Do not use for...in on arrays. It gives you keys as strings and walks the prototype chain:

for (const i in fruits) {
    console.log(i, typeof i);   // "0" string, "1" string, "2" string
    console.log(i + 1);         // "01", "11", "21"  <-- concatenation
}

8. Multidimensional Arrays

Arrays of arrays represent matrices and grids:

let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

console.log(matrix[1][2]);   // 6

flat() flattens them. The argument is the depth, and it defaults to 1:

console.log(matrix.flat());   // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

const deep = [1, [2, [3, [4]]]];
console.log(deep.flat());           // [ 1, 2, [ 3, [ 4 ] ] ]  — one level only
console.log(deep.flat(2));          // [ 1, 2, 3, [ 4 ] ]
console.log(deep.flat(Infinity));   // [ 1, 2, 3, 4 ]          — all the way down

9. Array Methods

This is the section you will come back to. The methods below are the ones worth knowing by heart.

a. Transforming: map, filter

Both return a new array and leave the original alone.

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

numbers.map((n) => n * 2);          // [ 2, 4, 6, 8, 10 ]
numbers.filter((n) => n % 2 === 0); // [ 2, 4 ]
numbers;                            // [ 1, 2, 3, 4, 5 ] — untouched

map always returns an array of the same length. If you find yourself using map and ignoring some results, you wanted filter — or flatMap.

b. Searching: find, findIndex, some, every, includes

const users = [
  { name: "Ganesh", age: 30 },
  { name: "Asha", age: 25 },
];

users.find((u) => u.age < 28);       // { name: 'Asha', age: 25 }  — the element
users.findIndex((u) => u.age < 28);  // 1                           — the position
users.some((u) => u.age > 28);       // true  — at least one
users.every((u) => u.age > 20);      // true  — all of them

[1, 2, 3].includes(2);               // true
[1, 2, 3].indexOf(2);                // 1

includes and indexOf answer nearly the same question. Two differences: includes returns a boolean, and it finds NaN, which indexOf cannot:

[NaN].includes(NaN);   // true
[NaN].indexOf(NaN);    // -1

c. flatMap

map followed by one level of flat. Use it when each item can produce zero, one, or many results:

[1, 2, 3].flatMap((n) => [n, n * 2]);   // [ 1, 2, 2, 4, 3, 6 ]

Returning an empty array drops the item, so flatMap can filter and map in one pass:

const input = ["1", "not a number", "3"];
const parsed = input.flatMap((s) => (Number.isNaN(Number(s)) ? [] : [Number(s)]));
console.log(parsed);   // [ 1, 3 ]

d. reduce, properly

reduce walks the array carrying an accumulator and returns whatever the accumulator ends up as. What confuses people is that the accumulator can be any type — that is the whole power of it.

// To a number
[1, 2, 3].reduce((sum, n) => sum + n, 0);   // 6

// To an object — counting occurrences
["yes", "no", "yes"].reduce((counts, vote) => {
  counts[vote] = (counts[vote] ?? 0) + 1;
  return counts;
}, {});
// { yes: 2, no: 1 }

// To an array — flattening
[[1, 2], [3]].reduce((out, part) => out.concat(part), []);   // [ 1, 2, 3 ]

Always pass the initial value — the second argument. Without it, reduce uses the first element as the starting accumulator, which throws on an empty array:

[].reduce((a, b) => a + b);      // TypeError: Reduce of empty array with no initial value
[].reduce((a, b) => a + b, 0);   // 0

And know when not to use it. If a plain loop or a filter().map() chain reads more clearly, use that instead. reduce is for collapsing a list into one differently-shaped thing.

e. sort — and its two traps

Trap one: sort does not sort numbers.

[10, 9, 1].sort();   // [ 1, 10, 9 ]

That is not a bug. With no comparator, sort converts every element to a string and sorts alphabetically — and "10" comes before "9" for the same reason "apple" comes before "banana".

Always pass a comparator for numbers:

[10, 9, 1].sort((a, b) => a - b);   // [ 1, 9, 10 ]   ascending
[10, 9, 1].sort((a, b) => b - a);   // [ 10, 9, 1 ]   descending

The comparator returns negative if a comes first, positive if b does, and 0 for a tie. a - b produces exactly that.

Sorting objects follows the same shape:

users.sort((a, b) => a.age - b.age);                  // by number
users.sort((a, b) => a.name.localeCompare(b.name));   // by string

Use localeCompare for text rather than < and > — it handles accents and non-English scripts correctly, which raw comparison does not.

Trap two: sort mutates. See the next section.

f. The immutable versions (ES2023)

Four methods were added to give the mutating ones a safe counterpart. Same behaviour, new array, original untouched:

Mutates Returns a new array
sort() toSorted()
reverse() toReversed()
splice() toSpliced()
arr[i] = x with(i, x)
const scores = [30, 10, 20];

console.log(scores.toSorted());   // [ 10, 20, 30 ]
console.log(scores);              // [ 30, 10, 20 ]  <-- untouched

console.log([1, 2, 3].toReversed());       // [ 3, 2, 1 ]
console.log([1, 2, 3].with(1, 99));        // [ 1, 99, 3 ]
console.log([1, 2, 3, 4].toSpliced(1, 2)); // [ 1, 4 ]

with() quietly removes the most boilerplate. Replacing one item without mutating used to be:

const updated = items.map((item, i) => (i === 2 ? newItem : item));

Now it is items.with(2, newItem).

If you write React, Redux, or anything that compares state by reference, these four should be your default. [...arr].sort() still works — this is shorter and says what it means.

g. Joining and slicing

const parts = ["2026", "09", "09"];

parts.join("-");           // "2026-09-09"
parts.join("");            // "20260909"

const letters = ["a", "b", "c", "d"];
letters.slice(1, 3);       // [ 'b', 'c' ]   — non-mutating, unlike splice
letters.slice(-2);         // [ 'c', 'd' ]
letters.concat(["e"]);     // [ 'a', 'b', 'c', 'd', 'e' ]

slice and splice are one letter apart and behave completely differently. slice copies and returns; splice cuts the original open. When in doubt, slice is the safe one.

For the full list of methods, MDN's Array reference is the canonical source. The ones above are what you will reach for daily.

10. Array-Like Objects

Array-like objects have numeric indices and a length property but none of the array methods. The classic examples are the arguments object and DOM node lists.

function sum() {
    // arguments is array-like — it has length and indices, but no .reduce
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

console.log(sum(1, 2, 3));   // 6

Array.from converts an array-like into a real array — but note it must be called inside the function, since arguments only exists there:

function sumFrom() {
    const args = Array.from(arguments);   // now a real array
    return args.reduce((total, n) => total + n, 0);
}

console.log(sumFrom(1, 2, 3));   // 6

In modern code you rarely need any of this. Rest parameters give you a real array directly:

function sumRest(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}

console.log(sumRest(1, 2, 3));   // 6

There is another reason to prefer rest: arrow functions have no arguments object at all. Referring to it inside one is a ReferenceError, so if you write arrows — and most modern code does — rest parameters are the only option.

For DOM collections, spread or Array.from still earn their place:

const nodes = document.querySelectorAll("li");
const items = [...nodes];        // now a real array
items.map((li) => li.textContent);

11. Strings as Arrays

Strings can be indexed like arrays, and share several method names:

let greeting = "Hello, World!";

console.log(greeting[0]);       // "H"
console.log(greeting.at(-1));   // "!"   — at() works on strings too
console.log(greeting.length);   // 13

Spread splits a string into characters, and does it correctly for emoji, which index access does not:

console.log([...greeting].length);   // 13
console.log("👋".length);             // 2 — two UTF-16 code units
console.log([..."👋"].length);        // 1 — one character

Strings are immutable, though, so this is where the resemblance ends. greeting[0] = "J" silently does nothing, and there is no push or sort. To manipulate a string as an array, convert, work, and join back:

[...greeting].reverse().join("");   // "!dlroW ,olleH"

12. Summary

We've covered creating arrays, reading and writing elements, sparse arrays and holes, length, adding and removing, iteration, multidimensional arrays, the methods worth knowing, array-like objects, and strings.

Three things worth carrying away:

  • Know which methods mutate. sort, reverse and splice change the original and return it, so the assignment hides what happened. toSorted, toReversed, toSpliced and with are the safe versions.

  • sort sorts as strings by default. [10, 9, 1].sort() is [1, 10, 9]. Always pass a comparator for numbers.

  • Holes are not undefined. Iteration methods skip them. Do not create sparse arrays on purpose.

Next in the series: JavaScript Functions.

Hope you like it, if yes ❤️ like & 📤share.

Thanks for your time.

Happy coding….

← Mastering JavaScript Objects JavaScript Functions →

JavaScript - Basics to Advance

Part 10 of 12

This series will cover everything you need to know about JavaScript from basics to advanced. Please stick with us and take your JS code quality and productivity to another level #JavaScript #JS

Up next

Javascript Functions: A Comprehensive Guide

Last updated: September 2026 — corrected the claim about matching argument counts and the definition of fn.length; added this and its binding rules, call/apply/bind, default and rest parameters, IIFEs

More from this blog

H

Hacker's Haven

28 posts

I'm a Senior Software Developer with expertise in programming languages and tech. Follow me for detailed articles and tutorials on software development tools and frameworks.