JavaScript Arrays: A Comprehensive Guide

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,reverseandsplicechange the original and return it, so the assignment hides what happened.toSorted,toReversed,toSplicedandwithare the safe versions.sortsorts 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….





