Arrays are the most commonly used data structure in JavaScript โ ordered lists of values that can be accessed by index, iterated over, and transformed with a rich set of built-in methods. Every JavaScript developer works with arrays constantly: reading API responses, managing UI lists, processing form data, and building data pipelines. In this lesson you will master array creation, index access, mutation methods, and the full set of methods for adding, removing, and transforming elements.
Array Creation Methods
| Method | Example | Result |
|---|---|---|
| Array literal | [1, 2, 3] |
Always use this โ clearest syntax |
Array.from(iterable) |
Array.from('hello') |
['h','e','l','l','o'] |
Array.from({length: n}, fn) |
Array.from({length:3}, (_,i) => i+1) |
[1,2,3] |
Array.of(...items) |
Array.of(1, 2, 3) |
[1,2,3] |
| Spread from Set | [...new Set([1,1,2,2])] |
[1,2] โ deduplication |
Mutation Methods (change the original array)
| Method | Effect | Returns |
|---|---|---|
push(...items) |
Add to end | New length |
pop() |
Remove last element | Removed element |
unshift(...items) |
Add to beginning | New length |
shift() |
Remove first element | Removed element |
splice(start, deleteCount, ...items) |
Remove/insert at any position | Array of removed elements |
reverse() |
Reverse in place | The reversed array |
fill(value, start, end) |
Fill range with value | The modified array |
Non-Mutation Methods (return new array or value)
| Method | Returns | Example |
|---|---|---|
slice(start, end) |
New array โ shallow copy of range | [1,2,3,4].slice(1,3) โ [2,3] |
concat(...arrays) |
New merged array | [1,2].concat([3,4]) โ [1,2,3,4] |
join(sep) |
String โ elements joined by separator | ['a','b'].join('-') โ 'a-b' |
flat(depth) |
New flattened array | [[1,[2]],3].flat(2) โ [1,2,3] |
indexOf(val) |
First index or -1 |
[1,2,3].indexOf(2) โ 1 |
includes(val) |
Boolean | [1,2,3].includes(2) โ true |
at(index) |
Element โ supports negative index | [1,2,3].at(-1) โ 3 |
toReversed() ES2023 |
New reversed array โ original untouched | Non-mutating alternative to reverse() |
toSorted(fn) ES2023 |
New sorted array โ original untouched | Non-mutating alternative to sort() |
typeof [] returns 'object', not 'array'. To correctly check if a value is an array, always use Array.isArray(value). Arrays are zero-indexed: the first element is at index 0 and the last is at arr.length - 1. Accessing an index beyond the array length returns undefined โ no out-of-bounds error.slice, concat, and the ES2023 toSorted/toReversed return new arrays without touching the original โ making your code more predictable and easier to debug. When you must use a mutating method like sort() or reverse(), copy the array first: [...arr].sort() or arr.slice().sort().splice and slice look similar but behave very differently. splice mutates the original array and returns the removed elements. slice does not mutate and returns a new sub-array. Mixing them up is one of the most common JavaScript mistakes. Remember: splice = surgery (cuts the array open); slice = cutting a slice (copy only).Basic Example
// โโ Creating arrays โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const fruits = ['apple', 'banana', 'cherry'];
const numbers = Array.from({ length: 5 }, (_, i) => (i + 1) * 10); // [10,20,30,40,50]
const chars = Array.from('hello'); // ['h','e','l','l','o']
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1,2,3] โ deduplication
console.log(numbers); // [10, 20, 30, 40, 50]
console.log(unique); // [1, 2, 3]
// โโ Index access โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
console.log(fruits[0]); // 'apple'
console.log(fruits.at(-1)); // 'cherry' โ last element (ES2022)
console.log(fruits[99]); // undefined โ no error
// โโ push / pop โ end of array โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const stack = [];
stack.push('a', 'b', 'c'); // add to end
console.log(stack); // ['a','b','c']
const last = stack.pop(); // remove from end
console.log(last, stack); // 'c' ['a','b']
// โโ unshift / shift โ beginning of array โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const queue = ['b', 'c'];
queue.unshift('a'); // add to front
console.log(queue); // ['a','b','c']
const first = queue.shift(); // remove from front
console.log(first, queue); // 'a' ['b','c']
// โโ splice โ surgical insertion and removal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const letters = ['a', 'b', 'c', 'd', 'e'];
// Remove 2 elements starting at index 1
const removed = letters.splice(1, 2);
console.log(removed, letters); // ['b','c'] ['a','d','e']
// Insert at position 1 without removing
letters.splice(1, 0, 'X', 'Y');
console.log(letters); // ['a','X','Y','d','e']
// โโ slice โ non-mutating copy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const original = [1, 2, 3, 4, 5];
const copy = original.slice(); // full shallow copy
const middle = original.slice(1, 4); // [2,3,4]
const last3 = original.slice(-3); // [3,4,5]
console.log(original); // [1,2,3,4,5] โ unchanged
console.log(middle); // [2,3,4]
// โโ concat and spread โ merging arrays โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const a = [1, 2, 3];
const b = [4, 5, 6];
const merged1 = a.concat(b); // [1,2,3,4,5,6]
const merged2 = [...a, ...b]; // [1,2,3,4,5,6] โ preferred
const merged3 = [...a, 99, ...b]; // [1,2,3,99,4,5,6] โ insert in middle
// โโ flat โ flatten nested arrays โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const nested = [[1, 2], [3, [4, 5]]];
console.log(nested.flat()); // [1,2,3,[4,5]] โ one level
console.log(nested.flat(2)); // [1,2,3,4,5] โ two levels
console.log(nested.flat(Infinity)); // fully flat โ any depth
// โโ includes vs indexOf โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const tags = ['javascript', 'css', 'html'];
console.log(tags.includes('css')); // true โ boolean check
console.log(tags.indexOf('css')); // 1 โ index or -1
console.log(tags.indexOf('python')); // -1 โ not found
How It Works
Step 1 โ Arrays Are Dynamic โ No Fixed Size
Unlike arrays in many languages, JavaScript arrays resize automatically. push extends the array; pop shrinks it. You can even set arr.length = 0 to empty an array in place, or arr.length = 10 to pad it with undefined slots. The length property always reflects the current number of elements.
Step 2 โ splice Is the Swiss Army Knife
splice(start, deleteCount, ...insertItems) combines deletion and insertion in one operation. Setting deleteCount to 0 inserts without removing. Omitting insertItems removes without replacing. The method modifies the original array in place and returns only the removed elements.
Step 3 โ slice Creates a Shallow Copy
slice() with no arguments creates a shallow copy of the entire array โ a common technique before calling a mutating method. Shallow means nested objects are not deep-copied โ both arrays will share references to the same inner objects. For deep cloning, use structuredClone(arr).
Step 4 โ Spread Merges More Expressively Than concat
[...a, ...b] is the modern way to merge arrays. Unlike concat, spread can insert values anywhere in the merge: [...a, 99, ...b]. Spread also works on any iterable โ Sets, Maps, strings, NodeLists โ while concat only works with arrays.
Step 5 โ at() Enables Clean Negative Indexing
arr.at(-1) returns the last element cleanly. Before at(), the idiom was arr[arr.length - 1] โ verbose and repeated the array name. at(-2) is the second-to-last, at(-3) the third-to-last, and so on. It works on strings too: 'hello'.at(-1) returns 'o'.
Real-World Example: Shopping Cart
// cart.js
class Cart {
constructor() {
this.items = [];
}
add(product, qty = 1) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.qty += qty;
} else {
this.items.push({ ...product, qty });
}
return this; // enable chaining
}
remove(productId) {
const idx = this.items.findIndex(i => i.id === productId);
if (idx !== -1) this.items.splice(idx, 1);
return this;
}
updateQty(productId, qty) {
if (qty <= 0) return this.remove(productId);
const item = this.items.find(i => i.id === productId);
if (item) item.qty = qty;
return this;
}
get total() { return this.items.reduce((sum, i) => sum + i.price * i.qty, 0); }
get itemCount() { return this.items.reduce((sum, i) => sum + i.qty, 0); }
get isEmpty() { return this.items.length === 0; }
getNames() { return this.items.map(i => `${i.name} x${i.qty}`); }
clear() { this.items = []; return this; }
summary() {
return {
items: this.getNames(),
count: this.itemCount,
total: `$${this.total.toFixed(2)}`,
shipping: this.total >= 35 ? 'Free' : '$5.99',
};
}
}
const cart = new Cart();
cart
.add({ id: 1, name: 'Widget', price: 9.99 })
.add({ id: 2, name: 'Gadget', price: 29.99 })
.add({ id: 1, name: 'Widget', price: 9.99 }, 2); // add 2 more Widgets
console.log(cart.summary());
// { items: ['Widget x3','Gadget x1'], count: 4, total: '$59.96', shipping: 'Free' }
cart.updateQty(1, 1);
cart.remove(2);
console.log(cart.summary());
// { items: ['Widget x1'], count: 1, total: '$9.99', shipping: '$5.99' }
Common Mistakes
Mistake 1 โ Confusing splice and slice
โ Wrong โ splice mutates the array unexpectedly:
const arr = [1, 2, 3, 4, 5];
const sub = arr.splice(1, 3); // removes [2,3,4] from arr!
console.log(arr); // [1, 5] โ mutated!
โ Correct โ use slice to copy a range without mutation:
const arr = [1, 2, 3, 4, 5];
const sub = arr.slice(1, 4); // [2,3,4] โ arr unchanged
console.log(arr); // [1,2,3,4,5]
Mistake 2 โ Sorting an array you still need unsorted
โ Wrong โ sort() mutates the original:
const prices = [30, 10, 50, 20];
const sorted = prices.sort((a, b) => a - b);
console.log(prices); // [10,20,30,50] โ original mutated!
โ Correct โ copy before sorting:
const sorted = [...prices].sort((a, b) => a - b);
console.log(prices); // [30,10,50,20] โ original preserved
Mistake 3 โ Using delete to remove an array element
โ Wrong โ delete leaves an empty slot (undefined) and doesn’t change length:
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, empty, 3] โ length still 3!
โ Correct โ use splice to actually remove the element:
arr.splice(1, 1);
console.log(arr); // [1, 3] โ length is now 2
Quick Reference
| Task | Method | Mutates? |
|---|---|---|
| Add to end | push(item) |
Yes |
| Remove from end | pop() |
Yes |
| Add to start | unshift(item) |
Yes |
| Remove from start | shift() |
Yes |
| Remove/insert anywhere | splice(i, n, ...items) |
Yes |
| Copy range | slice(start, end) |
No |
| Merge arrays | [...a, ...b] |
No |
| Flatten | flat(depth) |
No |
| Check membership | includes(val) |
No |
| Last element | at(-1) |
No |