Mert Özen Further With Every Line
Frontend JavaScript Basics — #10

JavaScript Arrays: push, pop, slice, splice, Spread and the sort Trap

Mert Özen Nov 3, 2026 9 min 4 views
JavaScript Arrays: push, pop, slice, splice, Spread and the sort Trap

We learn how to create arrays, access them with at(), push/pop/shift/unshift, slice vs splice, copying with spread, forEach, why sort gets numbers wrong, and the reference trap.

Version note: The code in this post targets ES2026 and current browsers (Chrome, Edge, Firefox, Safari).

Predict First

This post is about arrays, the structure that holds values as an ordered list. Before we start, look at these three lines:

const numbers = [10, 9, 1, 100];
numbers.sort();
console.log(numbers);

sort sorts, as the name says. So which order shows up in the console? If you said [1, 9, 10, 100], you're not alone. Settle on your guess before you keep reading; we'll explain the answer in the sort section, once we see what the engine is actually comparing.

Creating Arrays and Accessing Elements

Think of an array as the cars of a train. The cars are lined up in order, each has a number, and the numbering starts at zero. The most common way to create an array is with square brackets. Paste this into your browser's console and try it:

const fruits = ["apple", "pear", "banana"];
const empty = [];
const mixed = [42, "text", true, null];

console.log(fruits.length);          // 3
console.log(empty.length);           // 0
console.log(typeof fruits);          // "object"
console.log(Array.isArray(fruits));  // true

You can put values of any type into an array, and even mix them. In practice, though, an array usually holds things of the same kind: products, users, prices. Don't let typeof saying "object" surprise you; as we saw earlier in the post on data types, arrays are actually a special kind of object. To find out whether a value is an array, use Array.isArray.

Accessing elements works the way you know from strings: an index in square brackets, and at(), added in ES2022, to count from the end. The difference is that, unlike a string, you can change an array's elements:

const fruits = ["apple", "pear", "banana"];
console.log(fruits[0]);     // "apple"
console.log(fruits.at(-1)); // "banana"
console.log(fruits[5]);     // undefined

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

Something may have caught your eye here: fruits is a const, yet we could change what's inside it. This is exactly the difference between reassignment and mutation that we saw earlier in the post on variables. const stops the fruits label from being stuck onto a different array; it doesn't care what happens inside the array the label is stuck to.

Adding and Removing Elements

There are four methods for adding or removing elements at the start and end of an array. An easy way to remember them: push and pop work with the end, unshift and shift with the start.

const queue = ["Alan", "Ada"];

queue.push("Grace");        // adds to the end
console.log(queue);       // ["Alan", "Ada", "Grace"]

queue.unshift("Linus");   // adds to the start
console.log(queue);       // ["Linus", "Alan", "Ada", "Grace"]

const last = queue.pop();    // removes the last one
const first = queue.shift(); // removes the first one
console.log(last, first);    // "Grace" "Linus"
console.log(queue);          // ["Alan", "Ada"]

Pay attention to the return values. pop and shift give back the element they removed, which is why we could store it in a variable. push and unshift return the array's new length. All four change the original array.

A small performance note: adding to and removing from the end is fast, because the other elements don't need to be touched. When you add or remove at the start, though, the engine has to shift the number of every remaining element by one. You won't notice this up to a few thousand elements, but in an array with millions of elements it matters.

Searching an Array: includes and indexOf

Two methods you remember from strings exist on arrays too, and they work the same way:

const colors = ["red", "green", "blue"];
console.log(colors.includes("green")); // true
console.log(colors.indexOf("blue"));   // 2
console.log(colors.indexOf("purple")); // -1

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

includes, added in ES2016, answers "is it there?", and indexOf answers "where is it?". The last two lines show a small but real difference between them. indexOf compares with ===, which we saw earlier, and since NaN isn't equal to itself, it can never find it. includes recognizes NaN correctly. For a yes/no question, prefer includes again.

slice and splice: Twin Names, Different Personalities

These two methods are spelled almost the same, but their characters couldn't be more different. slice is a photographer: it takes a copy of part of the array and leaves the original alone. splice is a surgeon: it operates on the array itself, cutting, removing and stitching in something new.

const letters = ["a", "b", "c", "d", "e"];

const part = letters.slice(1, 3);
console.log(part);    // ["b", "c"]
console.log(letters); // ["a", "b", "c", "d", "e"]

slice follows the same rule as its sibling on strings: start included, end excluded, negative values count from the end. The original array stays as it was. Now let's watch the surgeon:

const letters = ["a", "b", "c", "d", "e"];

const removed = letters.splice(1, 2);
console.log(removed); // ["b", "c"]
console.log(letters); // ["a", "d", "e"]

letters.splice(1, 0, "x", "y");
console.log(letters); // ["a", "x", "y", "d", "e"]

The first argument to splice is the start index, the second is how many elements to delete. Anything you pass after those two is inserted at that point. If you set the delete count to 0, you slip elements in without deleting anything. The return value is always an array of the deleted elements. We'll see toSpliced from ES2023, which does the same job without changing the original, in the Intermediate series.

join, concat and Spread

join is the opposite of split, which we saw earlier in the post on strings: it glues the array's elements together with the separator you give it and makes a single string.

const words = ["JavaScript", "is", "fun"];
console.log(words.join(" "));   // "JavaScript is fun"
console.log(words.join(", "));  // "JavaScript, is, fun"
console.log(["2026", "11", "03"].join("-")); // "2026-11-03"

To combine two arrays there's concat. It doesn't change the originals; it returns a new array. In modern code, though, you'll more often see this done with the spread syntax (...), added in ES2015. The three dots mean "pour the elements of an array out, one by one, right here":

const fruits = ["apple", "pear"];
const vegetables = ["carrot", "spinach"];

console.log(fruits.concat(vegetables));    // ["apple", "pear", "carrot", "spinach"]
console.log([...fruits, ...vegetables]);   // same result
console.log(["cherry", ...fruits, "fig"]); // ["cherry", "apple", "pear", "fig"]

const copy = [...fruits];
copy.push("banana");
console.log(fruits); // ["apple", "pear"]
console.log(copy);   // ["apple", "pear", "banana"]

The last part matters: [...fruits] created a new array, so the element added to copy didn't affect fruits. We'll see why this is important in the pitfalls section. We'll see spread with objects in the post on objects, and its other tricks in the Intermediate series.

A First Look at forEach

In the post on loops we saw how to walk through an array with for...of. Arrays also have their own method for this: forEach. You give it a function, and it calls that function for every element:

const prices = [120, 45, 300];

prices.forEach((price, index) => {
  console.log(`Item ${index + 1}: ${price} TL`);
});
// Item 1: 120 TL
// Item 2: 45 TL
// Item 3: 300 TL

The (price, index) => { ... } part is an arrow function. We'll cover functions in full detail in the next post; for now, read it like this: "for each element, give me that element and its index, and I'll do what's inside the curly braces." Handing one function to another like this is called a callback.

forEach or for...of? Both get the job done. The one important difference: you can't exit a forEach halfway with break; it always visits every element. If you need to stop once a condition is met, use for...of. We'll dig into the more powerful array methods, like map, filter and reduce, in the Intermediate series.

sort: The Hidden Rule of Sorting

The answer to the opening question: [1, 10, 100, 9].

At first glance this looks like a bug, but it isn't. If you don't tell sort how to sort, it first converts the elements to strings and compares them character by character, like in a dictionary. In a dictionary, "10" comes before "9", because the first characters, "1" and "9", are compared and that settles it. Think of how names are listed in a phone book.

To sort numbers as numbers, you give sort a compare function. It takes two elements and returns a number: if it's negative, a goes first; if positive, b goes first; if zero, their order is kept. a - b does exactly that:

const numbers = [10, 9, 1, 100];

numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 9, 10, 100]

numbers.sort((a, b) => b - a);
console.log(numbers); // [100, 10, 9, 1]

Words with non-English letters hold a similar surprise. The default sort looks at the characters' Unicode order, and there letters like the Turkish "ç", "ş" and "ü" end up after "z". For proper alphabetical order in a given language, use localeCompare with that language's code:

const fruits = ["şeftali", "armut", "çilek", "üzüm", "zeytin"];

console.log([...fruits].sort());
// ["armut", "zeytin", "çilek", "üzüm", "şeftali"]

console.log([...fruits].sort((a, b) => a.localeCompare(b, "tr")));
// ["armut", "çilek", "şeftali", "üzüm", "zeytin"]

Notice that we took a copy with [...fruits] before sorting. The reason is in the next section.

Common Pitfalls

sort changes the original

const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
console.log(sorted);   // [1, 2, 3]
console.log(original); // [1, 2, 3]
console.log(sorted === original); // true

sort doesn't produce a new array; it sorts the one you have in place and returns that same array. If you want to keep the original order, copy it with spread first. There's also toSorted, added in ES2023, which leaves the original alone; we'll see it in the Intermediate series along with the other copying methods.

Assignment doesn't copy

const cart = ["pen", "notebook"];
const otherCart = cart;
otherCart.push("eraser");

console.log(cart);              // ["pen", "notebook", "eraser"]
console.log(cart === otherCart); // true
console.log([1, 2] === [1, 2]);  // false

This may be the most important part of the post. In the post on data types we took a first look at the difference between values and references; here it is in action. When you write otherCart = cart, no new array is created. You've just cut a second key to the same house. Whichever key you use, you're moving the same furniture around in the same room. The last line is the flip side: even with the same contents, two separate array literals are two separate houses, and === treats them as different. If you really want a separate array, copy it with [...cart].

Spread makes a shallow copy

Spread copies only the outermost layer. If the array contains other arrays or objects, the copy points to those same inner pieces. We'll go into objects and deep copies in upcoming posts; for now it's enough to know that spread is safe for arrays of plain values.

A Small Experiment

★☆☆ Create an empty todos array. Add three tasks with push, put an urgent task at the very start with unshift, and remove the last one with pop. Print the array to the console after each step and compare it with your predictions.

★★☆ Predict before running: given const a = [1, 2, 3, 4, 5];, what does a.slice(-2) return? Then, what does a.splice(1, 3) return, and what does a look like afterwards?

★★☆ Sort the array [5, 25, 100, 1, 40] first with the default sort, then from smallest to largest, then from largest to smallest. Check what state the original array is in each time, and fix your code so it stays untouched.

★★★ Turn the string "cat,dog,bird,cat,fish,dog" into an array with split. Using one of the loops we saw earlier and includes, remove the duplicates, sort the result alphabetically and print it on one line with join(", ").

In this post we handed a function to forEach and sort, but we haven't really talked about what a function is yet. In the next post we move on to functions: the difference between function declarations, expressions and arrow functions, parameters, return, and why we can pass a function around just like any other value.