JavaScript · Beginner
JavaScript Arrays: Creating, Indexing and Changing Lists
In short: An array is an ordered list of values, indexed from 0, that can grow and shrink. You read an element with [i], count them with .length, add and remove with push, pop, shift and unshift, copy a range with slice, and visit elements with a loop or with methods such as map, filter, forEach and reduce.
Ordered lists of values
An array holds any number of values in order. It is written as a comma-separated list in square brackets, and the values can be of any type, including other arrays and objects, though most arrays hold one kind of thing. The first element is at index 0 and the last at length - 1; reading an index that does not exist gives undefined rather than an error, which is convenient and also a common source of silent bugs.
Arrays are mutable. push adds to the end and pop removes from the end; unshift and shift do the same at the front. splice(start, count, ...items) removes count elements at start and optionally inserts new ones; it is the general-purpose editing tool. Assigning arr[i] = value replaces one element, and assigning to length truncates. The non-mutating counterparts return a new array and leave the original alone: slice(start, end) copies a range (with end excluded and negative indexes counting from the back), concat joins arrays, and map and filter build new arrays from old.
Searching is done with indexOf (the position, or -1), includes (a boolean), find (the first element satisfying a test) and findIndex. join(separator) turns the elements into one string, and at(-1) (ES2022) is a readable way to fetch the last element.
The method most people misuse is sort. With no argument it converts every element to a string and sorts those, so numbers come out in dictionary order: 100 before 5. Pass a comparison function, (a, b) => a - b for ascending numbers, and it sorts numerically. sort and reverse also change the array in place; copy first with slice() if you need the original order too. ES2023 added toSorted() and toReversed(), which return sorted copies, but sorting a slice() works everywhere.
Two facts about arrays as values matter from the start. First, a variable holds a reference to the array, not the array itself: const b = a makes two names for one array, and pushing through either shows up in both. To copy, use slice() or the spread syntax from the spread lesson. Second, === compares references, so two separately built arrays with identical contents are not equal.
The iteration methods take a function and call it for each element: forEach for side effects, map to transform, filter to select, reduce to fold everything into one value, some and every to test. They read as a pipeline and avoid index arithmetic; a plain for...of loop from the loops lesson is equally fine when you need to stop early.
Syntax
const list = [10, 20, 30]; // literal
list[0] list.length list.at(-1) // read; last element
list.push(40) list.pop() list.unshift(5) list.shift() // ends
list.splice(1, 2) // remove 2 elements at index 1 (in place)
list.slice(1, 3) // copy indexes 1 and 2 (original untouched)
list.indexOf(20) list.includes(20) list.find((x) => x > 15)
list.join(", ") // "10, 20, 30"
list.sort((a, b) => a - b) // numeric ascending, in place
list.map((x) => x * 2) list.filter((x) => x > 10) list.reduce((acc, x) => acc + x, 0)
list.forEach((x, i) => { }) // value and index, no resultMethods that end in a new array (slice, map, filter, concat) leave the original alone; push, pop, shift, unshift, splice, sort and reverse change it in place.
Mutating versus non-mutating methods
| Changes the array in place | Returns something new, array untouched |
|---|---|
| push, pop, shift, unshift | slice, concat |
| splice | map, filter, reduce |
| sort, reverse | join, indexOf, includes, find |
| arr[i] = value, arr.length = n | toSorted, toReversed (ES2023) |
A waiting queue at a clinic
People join at the back, the front is served, and one urgent case is pushed in at the front.
const queue = ["Ines", "Farid"];
queue.push("Lena");
console.log(queue);
console.log("Waiting:", queue.length);
console.log("First:", queue[0], "Last:", queue[queue.length - 1]);
console.log("Also last:", queue.at(-1));
const served = queue.shift();
console.log("Served:", served);
console.log(queue);
queue.unshift("Otto (urgent)");
console.log(queue);
console.log(queue.indexOf("Lena"), queue.includes("Farid"), queue.includes("Ines"));
console.log(queue[10]);Output
[ 'Ines', 'Farid', 'Lena' ] Waiting: 3 First: Ines Last: Lena Also last: Lena Served: Ines [ 'Farid', 'Lena' ] [ 'Otto (urgent)', 'Farid', 'Lena' ] 2 true false undefined
push and shift together make the array behave as a queue: join at the back, leave from the front. shift returns the element it removed, so the served name can be printed. After the urgent case is inserted at the front, Lena has moved to index 2, which indexOf reports. Reading index 10 of a three-element array quietly gives undefined; a program that then does arithmetic with it gets NaN and no error message, so check length before indexing.
References, copies, slice and splice
Two names share one array, a slice makes an independent copy, and splice edits in place.
const scores = [40, 75, 60, 90];
const alias = scores;
alias.push(55);
console.log(scores);
const copy = scores.slice();
copy.push(100);
console.log(scores.length, copy.length);
console.log(scores.slice(1, 3));
console.log(scores.slice(-2));
const removed = scores.splice(1, 2);
console.log("Removed:", removed);
console.log("Now:", scores);
scores.splice(1, 0, 61, 62);
console.log(scores);
console.log(scores.join(" | "));
console.log(scores === alias, scores === copy);Output
[ 40, 75, 60, 90, 55 ] 5 6 [ 75, 60 ] [ 90, 55 ] Removed: [ 75, 60 ] Now: [ 40, 90, 55 ] [ 40, 61, 62, 90, 55 ] 40 | 61 | 62 | 90 | 55 true false
alias is not a second array; it is a second name for the same one, so the push through alias appears when scores is printed. slice() with no arguments copies the whole array, and the copy grows without affecting the original. slice(1, 3) takes indexes 1 and 2 (the end is excluded) and slice(-2) takes the last two. splice(1, 2) removes two elements starting at index 1 and returns them; splice(1, 0, 61, 62) removes nothing and inserts two values. The last line confirms that === is about identity: alias is the same array, copy is a different one.
Sorting numbers and the iteration methods
Compare the two sort results, then watch map, filter, reduce, forEach, find, some and every.
const prices = [12, 5, 100, 30];
console.log(prices.slice().sort());
console.log(prices.slice().sort((a, b) => a - b));
console.log(prices);
const doubled = prices.map((p) => p * 2);
const cheap = prices.filter((p) => p < 20);
const total = prices.reduce((sum, p) => sum + p, 0);
console.log(doubled);
console.log(cheap);
console.log(total);
prices.forEach((p, i) => console.log(i + ": " + p));
console.log(prices.find((p) => p > 20), prices.some((p) => p > 50), prices.every((p) => p > 50));Output
[ 100, 12, 30, 5 ] [ 5, 12, 30, 100 ] [ 12, 5, 100, 30 ] [ 24, 10, 200, 60 ] [ 12, 5 ] 147 0: 12 1: 5 2: 100 3: 30 100 true false
The default sort compares "100", "12", "30" and "5" as text, and "1" sorts before "5", which is why 100 comes first. The comparison function (a, b) => a - b returns a negative number when a should come first, and the sort becomes numeric. Sorting a slice() leaves prices in its original order, as the third line shows. map builds a same-length array of results, filter keeps the elements whose test is true, and reduce threads an accumulator through every element starting from 0. find returns the first match (100), some asks whether any element passes and every whether all do.
Common mistakes
Sorting numbers with a bare sort()
Why it goes wrong: Without a comparison function the elements are compared as strings, so
[10, 9, 1].sort()gives[1, 10, 9].Fix: Pass a comparator:
(a, b) => a - bfor ascending,(a, b) => b - afor descending.JavaScript · fixconst n = [10, 9, 1]; n.sort((a, b) => a - b); console.log(n); // [ 1, 9, 10 ]Expecting assignment to copy an array
Why it goes wrong:
const backup = items;creates a second reference to the same array, so changes through either name affect both, and the "backup" is nothing of the kind.Fix: Copy with
items.slice()(or[...items]), remembering that objects inside the array are still shared.Using the return value of push as the array
Why it goes wrong:
pushreturns the new length, not the array, soconst list = [].push(1);leaveslistholding the number 1.Fix: Call
pushon an existing array as a statement, then use the array itself.JavaScript · fixconst list = []; list.push(1); console.log(list); // [ 1 ]Comparing two arrays with ===
Why it goes wrong:
[1, 2] === [1, 2]is false because the operator compares identity, not contents.Fix: Compare lengths and then each element in a loop, or for arrays of simple values compare
a.join(",") === b.join(",").
Where you use this
Most exercises read a list of values and then ask a question about it: the total, the largest, how many pass a test, the values in order. The natural approach is to read every line into an array first and then answer with the array methods, which keeps input handling and logic apart. Sorting a copy and reading at(0) and at(-1) gives the smallest and largest; filter(...).length counts matches; reduce totals. For a report, map formats each element and join glues the lines together for a single console.log.
const values = [];
let line;
while ((line = readline()) !== null) {
values.push(Number(line));
}
const sorted = values.slice().sort((a, b) => a - b);
console.log("min", sorted.at(0), "max", sorted.at(-1));
console.log("above 10:", values.filter((v) => v > 10).length);Key points
- Indexes start at 0 and end at
length - 1; a missing index reads asundefined. push/popwork at the end,unshift/shiftat the front,spliceanywhere, all in place.slicecopies a range and leaves the original alone;spliceedits the original.sort()compares as strings; pass(a, b) => a - bfor numbers, and sort a copy if order matters.const b = ashares one array;===compares identity, not contents.map,filter,reduce,forEach,find,some,everytake a function and apply it to each element.
Try it yourself
The starter collects every input line into an array. Print the number of items, then the items in reverse order on one line separated by a comma and a space.
const items = [];
let line;
while ((line = readline()) !== null) {
items.push(line);
}
// print the count, then the items reversed and joined with ", "
pen ↵ ink ↵ paper3
paper, ink, penconst items = [];
let line;
while ((line = readline()) !== null) {
items.push(line);
}
console.log(items.length);
console.log(items.reverse().join(", "));
Practise this
Open the JavaScript playground
Related lessons
- Loops in JavaScript: for, while, for...of and for...infor, while, do...while, for...of and for...in loops in JavaScript, with break and continue, and how to read every line of input in a loop.9 min
- JavaScript Functions: Declarations, Arrows and Return ValuesHow to define and call JavaScript functions with declarations, expressions and arrow functions; parameters, default values, return and local scope.10 min
- JavaScript Objects: Properties, Methods and NestingHow JavaScript objects group data as key-value properties: dot and bracket access, adding and deleting, methods with this, nesting and reference behaviour.10 min
- Spread and Rest in JavaScriptWhat the ... operator does in JavaScript: spreading arrays and objects into calls and literals, and collecting rest parameters and rest elements.9 min
- Destructuring in JavaScriptHow JavaScript destructuring unpacks arrays and objects into variables, with defaults, renaming, nested patterns, swaps and function parameters.9 min
Frequently asked questions
How do I copy an array in JavaScript?
original.slice() or [...original] both create a new array with the same elements; Array.from(original) does too. All three are shallow copies: if the elements are objects, the copy holds the same objects, so changing one of them is visible through both arrays. For a deep copy of plain data, structuredClone(original) (Node.js 17 and later, and current browsers) copies nested arrays and objects as well.
Why does sort() put 100 before 5?
The default comparison converts elements to strings and compares them character by character, and the character "1" comes before "5". To sort numerically, pass a comparison function that returns a negative number, zero or a positive number: arr.sort((a, b) => a - b).
What is the difference between slice and splice?
slice(start, end) returns a new array containing a copy of the elements from start up to but not including end, and does not change the original. splice(start, count, ...items) removes count elements at start from the original array, inserts any items you pass, and returns the removed elements. One reads, the other edits.
How this page was checked. Every program on it was run with Node.js 22 at build time; runs in your browser in an isolated Web Worker by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.