JavaScript lesson 6 of 9
JavaScript Arrays
Learn how JavaScript arrays store ordered, changeable lists of values, and how to create, access, modify, search, sort, and transform them with push, map, and filter.
Published · Every example on this page was run before it was published.
So far you've stored one value per variable — a single number, a single string, a single boolean. Most real problems don't work that way. A gradebook holds dozens of scores. A shopping app holds a cart full of items. A weather station holds a reading for every hour of the day. You need a single variable that can hold many values at once, in a specific order, and JavaScript's answer to that need is the array.
What Is an Array?
An array is an ordered collection of values stored under one variable name. "Ordered" means every value has a fixed position, and JavaScript never rearranges that position on its own. "Collection" means the array can hold as many values as you want — zero, one, or thousands — and those values can be numbers, strings, booleans, or even other arrays.
A good mental picture is a row of numbered lockers in a school hallway. Each locker has a position — locker 0, locker 1, locker 2, and so on — and you can look inside any locker just by knowing its number. You can also swap out what's inside a locker, add a new locker to the end of the row, or clear one out entirely, without touching the others. A JavaScript array works the same way: each value sits at a numbered position (called an index), and you can read, change, add, or remove values by referring to that position.
const scores = [88, 95, 72, 100, 61];Here scores is one variable holding five values, in that exact order. Many coding problems — including
the practice problem linked at the end of this lesson — hand you input as an array and expect you to
search through it, transform it, or combine values from it.
Creating and Accessing Arrays
You create an array by writing values inside square brackets, separated by commas. An empty array is just an empty pair of brackets, and an array doesn't have to hold only one type of value:
const fruits = ["apple", "banana", "cherry"];
const empty = [];
const mixed = [1, "two", 3.0, true];JavaScript numbers array positions starting at 0, not 1, and you read a value at a given position with square brackets, the same syntax you used for reading a character out of a string:
const fruits = ["apple", "banana", "cherry"];
console.log(fruits[0]);
console.log(fruits[2]);
console.log(fruits.length);apple
cherry
3Just like strings, reading past the end of the array does not raise an error — it gives you
undefined — and square brackets do not understand negative positions. For "count from the end," use
the .at() method:
const fruits = ["apple", "banana", "cherry"];
console.log(fruits[10]);
console.log(fruits[-1]);
console.log(fruits.at(-1));
console.log(fruits.at(-2));undefined
undefined
cherry
bananaBeyond single positions, .slice(start, end) pulls out a whole range of values at once, including the
value at start and stopping just before end. Leaving out end means "through the end," and a
negative number counts back from the end:
const numbers = [10, 20, 30, 40, 50];
console.log(numbers.slice(1, 3));
console.log(numbers.slice(0, 2));
console.log(numbers.slice(2));
console.log(numbers.slice(-2));[ 20, 30 ]
[ 10, 20 ]
[ 30, 40, 50 ]
[ 40, 50 ].slice() always builds a new array, so it never disturbs the one you sliced.
Modifying Arrays
Unlike a string, an array is mutable — you can change what it holds after it's created, without
building a new array from scratch. Four methods handle most of the changes you'll make: .push() adds
a value to the end, .pop() removes and returns the last value, .unshift() adds a value to the
front, and .shift() removes and returns the first value.
const tasks = ["wash dishes", "walk dog"];
tasks.push("buy milk");
console.log(tasks);[ 'wash dishes', 'walk dog', 'buy milk' ]tasks was declared with const, and that is not a contradiction — const only stops you from
pointing tasks at a different array, and .push() is changing the contents of the same array
tasks has pointed to all along, exactly as you saw with const back in the lesson on variables.
.unshift() inserts at the front, shifting every existing value one position to the right:
const tasks = ["wash dishes", "walk dog", "buy milk"];
tasks.unshift("call mom");
console.log(tasks);[ 'call mom', 'wash dishes', 'walk dog', 'buy milk' ].pop() and .shift() are the opposite operations, and both hand back the value they removed, which is
useful when you need to both take an item out and do something with it:
const tasks = ["call mom", "wash dishes", "walk dog", "buy milk"];
const lastTask = tasks.pop();
console.log(lastTask);
console.log(tasks);
const firstTask = tasks.shift();
console.log(firstTask);
console.log(tasks);buy milk
[ 'call mom', 'wash dishes', 'walk dog' ]
call mom
[ 'wash dishes', 'walk dog' ]A fifth method, .splice(), can insert or remove at any position, not just the two ends — worth
knowing the name exists, even though .push(), .pop(), .unshift(), and .shift() cover most
everyday cases.
Mutation vs. Reassignment
Because arrays are mutable, two variables can end up pointing at the same array in memory. When that happens, changing the array through one variable name also changes what the other variable sees — there is only ever one array, just two labels for it:
const originalIds = [101, 102, 103];
const backup = originalIds;
backup.push(104);
console.log(originalIds);
console.log(originalIds === backup);[ 101, 102, 103, 104 ]
trueWriting const backup = originalIds did not create a second array — it made backup a second name for
the exact same array originalIds already pointed to. Calling backup.push(104) mutated that one
shared array, so the change shows up no matter which name you use to look at it. === between two
arrays checks whether they are literally the same object, and here it's true.
If you want an independent copy — one where changes to the copy don't touch the original — use the
spread operator, ..., inside a new array literal, or call .slice() with no arguments:
const originalIds2 = [101, 102, 103];
const copy = [...originalIds2];
copy.push(104);
console.log(originalIds2);
console.log(originalIds2 === copy);[ 101, 102, 103 ]
false[...originalIds2] unpacks every value out of originalIds2 and collects them into a brand-new array,
so copy starts out equal in content but is never the same object.
Searching, Checking, and Sorting
.includes() and .indexOf() cover most searching. .includes() answers a yes-or-no question;
.indexOf() tells you the position, or -1 if the value is not present:
const nums = [5, 3, 1, 4, 2];
console.log(nums.includes(4));
console.log(nums.includes(9));
console.log(nums.indexOf(1));true
false
2.sort() deserves real caution. Called with no arguments, it does not sort numbers numerically —
it converts every value to a string first and sorts those strings alphabetically:
const scores = [40, 100, 5, 25];
console.log(scores.sort());[ 100, 25, 40, 5 ]That is not a mistake in the example — it is genuinely what .sort() does by default, because "100"
sorts before "25" alphabetically ("1" comes before "2"), and "5" sorts last ("5" comes after
"4"). To sort numbers correctly, pass a comparator function: a function that takes two values and
returns a negative number if the first should come first, a positive number if the second should, and
zero if they're equal. Subtraction is the standard trick for ascending numeric order:
const scores2 = [40, 100, 5, 25];
scores2.sort((a, b) => a - b);
console.log(scores2);[ 5, 25, 40, 100 ].sort() also mutates the array it is called on and returns that same array, rather than building a
new one — worth remembering, because .map() and .filter(), coming up next, behave the opposite way.
Transforming Arrays: map and filter
.map() and .filter() are the two array methods you will reach for constantly once you start
processing real data. Both take a function, call it once for every value in the array, and return a
brand-new array — neither one changes the original.
.map() transforms every value and keeps the array the same length:
const prices = [10, 20, 30];
const withShipping = prices.map(price => price + 5);
console.log(withShipping);
console.log(prices);[ 15, 25, 35 ]
[ 10, 20, 30 ].filter() keeps only the values that pass a test, so the result can be shorter than the original — or
even empty:
const numbers2 = [3, 8, 1, 9, 4, 7];
const bigNumbers = numbers2.filter(n => n > 5);
console.log(bigNumbers);
console.log(numbers2);[ 8, 9, 7 ]
[ 3, 8, 1, 9, 4, 7 ]In both cases, the short arrow function you pass in is called once per value and its return value
decides the outcome: for .map(), whatever it returns becomes the new value at that position; for
.filter(), a truthy return keeps the value and a falsy one drops it. Because they return new arrays
rather than mutating, it is easy to chain them — filter first, then map the survivors — without ever
worrying about the original data changing underneath you.
A Worked Example
Here is a small program that builds up a list of temperature readings with .push(), then uses
.filter() and .map() together to report which ones were above freezing, in both Celsius and
Fahrenheit.
const rawReadings = [];
rawReadings.push(15, -5, 10, 25, -10, 20);
const aboveFreezing = rawReadings.filter(temp => temp > 0);
const inFahrenheit = aboveFreezing.map(celsius => (celsius * 9) / 5 + 32);
console.log("All readings:", rawReadings);
console.log("Above freezing (C):", aboveFreezing);
console.log("Above freezing (F):", inFahrenheit);
console.log("Coldest reading:", Math.min(...rawReadings));All readings: [ 15, -5, 10, 25, -10, 20 ]
Above freezing (C): [ 15, 10, 25, 20 ]
Above freezing (F): [ 59, 50, 77, 68 ]
Coldest reading: -10Walking through it: rawReadings starts as an empty array, and .push() accepts as many arguments as
you give it, appending each one in order — a single call built the entire starting array.
.filter(temp => temp > 0) keeps only the positive readings, dropping -5 and -10 and producing a
brand-new array without touching rawReadings. .map() then runs the Celsius-to-Fahrenheit formula
over that filtered array, again producing a new array.
The last line uses the spread operator for a different job than copying: Math.min() does not accept
an array directly, only individual arguments, so ...rawReadings unpacks the array into separate
numbers on the spot — the call becomes Math.min(15, -5, 10, 25, -10, 20) by the time JavaScript
actually runs it, and -10 comes back as the smallest.
Common Mistakes
Forgetting that map and filter return a new array instead of changing the original
const values = [1, 2, 3, 4, 5];
values.filter(v => v % 2 === 0);
console.log(values);[ 1, 2, 3, 4, 5 ]The filtered array really was built — it was just thrown away immediately, because the result of
.filter() was never assigned to anything. values is completely unchanged. Capture the return value
in a variable to keep it:
const values = [1, 2, 3, 4, 5];
const evens = values.filter(v => v % 2 === 0);
console.log(evens);[ 2, 4 ]Removing items from an array while looping over it
const numbers3 = [2, 2, 4];
for (let i = 0; i < numbers3.length; i++) {
if (numbers3[i] === 2) {
numbers3.splice(i, 1);
}
}
console.log(numbers3);[ 2, 4 ]Both 2s were supposed to disappear, and one survives. Removing the value at index 0 shifts
everything after it one position to the left, so the second 2 slides into index 0 — the exact
position the loop just finished checking. The loop's counter moves on to index 1 regardless, so the
survivor is skipped entirely. The safest fix is to avoid mutating an array you're stepping through at
all, and use .filter() instead:
const numbers4 = [2, 2, 4];
const withoutTwos = numbers4.filter(n => n !== 2);
console.log(withoutTwos);[ 4 ]Expecting push to return the array
const letters = ["a", "b"];
const result = letters.push("c");
console.log(result);
console.log(letters);3
[ 'a', 'b', 'c' ].push() does update letters in place, but the value it returns is the array's new length, 3,
not the array itself and not the value you just added. Storing that return value as if it were the
array — or trying to chain another array method directly off a .push() call — is a common source of
confusion. If you need the array afterward, refer to the original variable, not the result of .push().
Next Steps
Arrays are the foundation for a huge share of coding problems, because so many problems boil down to "do something with a sequence of values." The Two Number Sum practice problem is a direct next step from here — it hands you an array of integers and asks you to find the pair that adds up to a target, which means indexing, looping, and membership checks all come into play. Once you've read through this lesson, head to the JavaScript playground to experiment with the examples above on your own arrays before attempting the problem.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.