RizTech Academy logo
RizTech Academy
Objects and ArraysLesson 2 of 730 min

Arrays and the methods worth memorising

An object groups different things — a customer, a count, an address. An array holds many of the same thing, in order. Every list you will ever render on a page is an array.

The basics

const items = ['dal', 'rice', 'atta'];

console.log(items.length);
console.log(items[0]);
console.log(items.at(-1));
3
dal
atta

Numbered from zero, so the last index is length - 1. items[3] on a three-item array is undefined, not an error.

.at(-1) counts from the end and is far clearer than items[items.length - 1].

An array can hold anything, including objects — which is how real data arrives:

const orders = [
  { customer: 'Priya', plates: 3, paid: true },
  { customer: 'Arjun', plates: 0, paid: false },
  { customer: 'Meera', plates: 5, paid: true },
];

That shape — an array of objects — is what an API returns and what the rest of this module operates on.

The division that matters: mutating or not

Array methods split into two groups, and mixing them up is the source of most array bugs.

Some change the array in place. Some return a new one and leave the original alone. There is no way to tell from the name, so here is the table. It is worth keeping.

Method Does what Mutates?
push(x) Add to the end Yes
pop() Remove and return the last Yes
unshift(x) Add to the front Yes
shift() Remove and return the first Yes
splice(i, n) Remove/insert in the middle Yes
sort() Reorder Yes
reverse() Reverse Yes
slice(a, b) Copy a section No
concat(arr) Join arrays No
join(sep) Make a string No
includes(x) Is it there No
indexOf(x) Where is it, or -1 No
find(fn) First match, or undefined No
findIndex(fn) Index of first match, or -1 No
filter(fn) All matches No
map(fn) Transform each No
some(fn) / every(fn) Any / all No
reduce(fn, start) Boil down to one value No
toSorted() / toReversed() Sorted/reversed copy No
flat(depth) Flatten nesting No

slice copies, splice cuts. One letter apart and opposite in effect:

const items = ['dal', 'rice', 'atta'];

console.log(items.slice(1));
console.log(items);
[ 'rice', 'atta' ]
[ 'dal', 'rice', 'atta' ]
const spliced = ['dal', 'rice', 'atta'];

console.log(spliced.splice(1, 1));
console.log(spliced);
[ 'rice' ]
[ 'dal', 'atta' ]

splice returns what it removed and leaves the array shortened. Being caught by that once is normal; the table above is so you are not caught twice.

The sort bug

This one is genuinely surprising:

console.log([10, 9, 1].sort());
[ 1, 10, 9 ]

Ten before nine. sort() converts everything to strings and sorts alphabetically by default, so '10' comes before '9' — exactly the comparison from module 2, now silently reordering your data.

For numbers, supply a comparison:

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

The rule for the comparison function: return a negative number if a comes first, positive if b does, zero if it does not matter. a - b is ascending, b - a is descending.

And remember sort mutates:

const original = [3, 1, 2];
const sorted = original.sort();

console.log(original);
console.log(original === sorted);
[ 1, 2, 3 ]
true

sorted is not a copy — it is the same array. Sorting a list you were given, to display it one way, quietly reorders it for everyone else using it. Use toSorted(), or [...items].sort() if you need to support older browsers.

Searching

const orders = [
  { customer: 'Priya', plates: 3, paid: true },
  { customer: 'Arjun', plates: 0, paid: false },
  { customer: 'Meera', plates: 5, paid: true },
];

console.log(orders.find((o) => o.plates > 4));
console.log(orders.find((o) => o.plates > 99));
console.log(orders.findIndex((o) => o.plates > 99));
{ customer: 'Meera', plates: 5, paid: true }
undefined
-1

find gives undefined when nothing matches; findIndex gives -1. Two different "not found" values, and both are traps:

const missing = orders.find((o) => o.customer === 'Vikram');
console.log(missing.plates);
TypeError: Cannot read properties of undefined (reading 'plates')

Always check a find result before using it. And because -1 is truthy, if (orders.findIndex(...)) is true when the thing is missing — use !== -1, or use includes when you only want a yes or no.

Asking about the whole array

console.log(orders.some((o) => !o.paid));
console.log(orders.every((o) => o.paid));
true
false

some is "is there at least one"; every is "are they all". Both stop as soon as they know the answer.

A detail worth knowing: every on an empty array is true, and some is false. That is the mathematically correct answer and occasionally a surprise — "every unpaid order has been chased" is true when there are no unpaid orders.

Building and joining

const items = ['dal', 'rice'];

items.push('atta');
console.log(items);
console.log(items.join(', '));
console.log(items.includes('rice'));
[ 'dal', 'rice', 'atta' ]
dal, rice, atta
true

join is how a list becomes a sentence. It is also the thing to reach for instead of building a string with += in a loop.

Combining arrays without mutating:

const more = [...items, 'ghee'];
console.log(more.length, items.length);
4 3

Copying, and the same trap as objects

Arrays are objects, so everything from the last lesson applies:

const a = [1, 2];
const b = a;
b.push(3);
console.log(a);
[ 1, 2, 3 ]

[...a] or a.slice() makes a shallow copy. If the array holds objects, those objects are still shared — copying the array does not copy what is in it.

Check your work

items.at(-1) is the last element. items[items.length - 1] does the same thing less clearly, and items[3] on a three-item array is undefined.

slice returns a copy and leaves the original; splice removes in place and returns what it removed. From ['dal','rice','atta'], slice(1) gives ['rice','atta'] with the original intact; splice(1, 1) returns ['rice'] and leaves ['dal','atta'].

[10, 9, 1].sort() gives [1, 10, 9]. Default sort compares as strings. sort((a, b) => a - b) gives [1, 9, 10].

sort mutates and returns the same array, so original === sorted is true. Use toSorted() or [...items].sort() to leave the original alone.

find with no match is undefined; findIndex is -1. Using the result of a failed find gives Cannot read properties of undefined. -1 is truthy, so compare findIndex(...) !== -1 rather than testing it directly.

some is true if any match; every is true if all do. On an empty array, every is true and some is false.

b = a; b.push(3) changes a. Arrays are references, exactly like objects.

Non-mutating add and remove:

const addItem = (list, item) => [...list, item];
const removeItem = (list, item) => list.filter((x) => x !== item);

Both build a new array and leave the argument untouched — check by printing the original afterwards. The mutating equivalents, push and splice, change the caller's array, which is what you do not want when somebody else is also holding it. Note that removeItem drops every match rather than the first; if you need only the first, find its index and slice around it.

Practice

  1. Build an array of five items. Print the first, the last with at(-1), and the length. Then read an index past the end.
  2. Build the array of order objects from this lesson and print each customer with for...of.
  3. Prove slice and splice differ. Run both on identical arrays and print the return value and the array afterwards. Say which one you would want by default.
  4. Run the sort bug. Sort [10, 9, 1] with no comparison function. Then fix it ascending and descending.
  5. Sort an array and confirm the original changed. Then do it again with toSorted() and confirm it did not.
  6. Sort the orders by plate count, highest first. Then sort them by customer name — and use localeCompare from module 2 so capitals do not sort first.
  7. find an order that does not exist, then use the result, and read the error. Then write the version that checks first.
  8. Use findIndex for something missing and write the if wrongly (if (index)), so that a missing item reports as found. Then fix it.
  9. Use some and every to answer "is anything unpaid" and "is everything paid". Then run both on an empty array and explain the answers.
  10. Harder. Write addItem(list, item) that returns a new list with the item added, leaving the original untouched, and removeItem(list, item) that does the same for removal. Prove with a test that neither mutates its argument. This is how every framework expects you to update a list, and module 7 depends on the habit.

Next: map, filter and reduce — three methods that will replace most of the loops you currently write.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship