Episode 1 — Fundamentals / 1.21 — Arrays
1.21.b -- Accessing Elements
In one sentence: JavaScript arrays use zero-based indexing with bracket notation
arr[i], the modernat()method for negative indices, destructuring for elegant unpacking, and several search methods to find elements by value.
Navigation: <- 1.21.a -- Creating Arrays . 1.21.c -- Array Operations ->
1. Zero-based indexing explained
Arrays in JavaScript (and most programming languages) count from 0, not 1.
Index: 0 1 2 3 4
┌────────┬────────┬────────┬────────┬────────┐
Value: │ "apple"│"banana"│"cherry"│ "date" │ "fig" │
└────────┴────────┴────────┴────────┴────────┘
const fruits = ["apple", "banana", "cherry", "date", "fig"];
// First element is at index 0
console.log(fruits[0]); // "apple"
// Second element is at index 1
console.log(fruits[1]); // "banana"
// Last element is at index (length - 1)
console.log(fruits[4]); // "fig"
Why zero-based? The index represents the offset from the start of the array. The first element is 0 positions away from the beginning.
2. Accessing with bracket notation arr[index]
Bracket notation is the fundamental way to read and write array elements.
const scores = [95, 82, 74, 100, 88];
// Reading
console.log(scores[0]); // 95
console.log(scores[2]); // 74
// Writing (modifying)
scores[2] = 90;
console.log(scores[2]); // 90
console.log(scores); // [95, 82, 90, 100, 88]
// You can also use variables as indices
const i = 3;
console.log(scores[i]); // 100
// Expressions work too
console.log(scores[1 + 1]); // 90 (index 2)
Setting beyond current length extends the array:
const arr = [10, 20, 30];
arr[5] = 60;
console.log(arr); // [10, 20, 30, <2 empty items>, 60]
console.log(arr.length); // 6
3. Accessing the last element
Getting the last element is one of the most common operations. There are multiple ways:
const colors = ["red", "green", "blue", "yellow", "purple"];
// Classic: arr[arr.length - 1]
console.log(colors[colors.length - 1]); // "purple"
// Modern: arr.at(-1) (ES2022)
console.log(colors.at(-1)); // "purple"
// Destructuring with rest (less efficient for just the last item)
const last = colors[colors.length - 1];
Common pattern for second-to-last:
console.log(colors[colors.length - 2]); // "yellow"
console.log(colors.at(-2)); // "yellow"
4. Array.at() method (ES2022)
at() works like bracket notation but supports negative indices that count from the end.
const letters = ["a", "b", "c", "d", "e"];
// Positive indices -- same as bracket notation
letters.at(0); // "a"
letters.at(2); // "c"
// Negative indices -- count from the end
letters.at(-1); // "e" (last)
letters.at(-2); // "d" (second to last)
letters.at(-5); // "a" (first, same as at(0))
// Out of bounds
letters.at(10); // undefined
letters.at(-10); // undefined
Comparison with bracket notation:
| Operation | Bracket notation | at() method |
|---|---|---|
| First element | arr[0] | arr.at(0) |
| Third element | arr[2] | arr.at(2) |
| Last element | arr[arr.length - 1] | arr.at(-1) |
| Second to last | arr[arr.length - 2] | arr.at(-2) |
Why at() is useful: It makes code more readable when accessing from the end, especially in chained expressions.
// Without at() -- verbose
const lastChar = "hello".split("")[("hello".split("").length - 1)];
// With at() -- clean
const lastChar2 = "hello".split("").at(-1); // "o"
5. Out-of-bounds access (undefined, not error)
Unlike many languages that throw errors, JavaScript silently returns undefined for invalid indices.
const arr = [10, 20, 30];
console.log(arr[0]); // 10
console.log(arr[2]); // 30
console.log(arr[3]); // undefined (no error!)
console.log(arr[100]); // undefined
console.log(arr[-1]); // undefined (negative indices don't work with brackets)
// This can hide bugs!
const name = arr[5]; // undefined -- no crash, but wrong data
console.log(name.toUpperCase()); // TypeError: Cannot read properties of undefined
Defensive access patterns:
const arr = [10, 20, 30];
// Check before accessing
if (arr.length > 3) {
console.log(arr[3]);
}
// Default value with nullish coalescing
const value = arr[5] ?? "default"; // "default"
// Default value with logical OR (careful: 0 and "" are falsy)
const value2 = arr[5] || "default"; // "default"
6. Checking if an index exists
const arr = [10, undefined, 30];
// Using `in` operator -- checks if index exists as a property
0 in arr; // true
1 in arr; // true (index 1 exists, even though value is undefined)
5 in arr; // false
// Sparse array example
const sparse = [1, , 3]; // hole at index 1
0 in sparse; // true
1 in sparse; // false (hole -- index does not exist)
2 in sparse; // true
// Using hasOwnProperty
arr.hasOwnProperty(0); // true
arr.hasOwnProperty(5); // false
// The difference: undefined value vs. missing index
const a = [10, undefined, 30]; // index 1 EXISTS with value undefined
const b = [10, , 30]; // index 1 DOES NOT EXIST (hole)
1 in a; // true
1 in b; // false
a[1]; // undefined (from both!)
b[1]; // undefined (from both!)
7. Destructuring arrays
Array destructuring lets you unpack values from arrays into distinct variables.
const rgb = [255, 128, 0];
// Without destructuring
const red = rgb[0];
const green = rgb[1];
const blue = rgb[2];
// With destructuring -- cleaner
const [r, g, b] = rgb;
console.log(r); // 255
console.log(g); // 128
console.log(b); // 0
Skipping elements:
const scores = [95, 82, 74, 100, 88];
// Skip second and third
const [first, , , fourth] = scores;
console.log(first); // 95
console.log(fourth); // 100
Rest pattern ...rest:
const numbers = [1, 2, 3, 4, 5];
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
const [a, b, ...rest] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
Default values:
const [x = 0, y = 0, z = 0] = [10, 20];
console.log(x); // 10
console.log(y); // 20
console.log(z); // 0 (default, since no third element)
Nested destructuring:
const matrix = [[1, 2], [3, 4]];
const [[a, b], [c, d]] = matrix;
console.log(a, b, c, d); // 1 2 3 4
Practical example -- function returning multiple values:
function getMinMax(arr) {
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return [min, max];
}
const [minimum, maximum] = getMinMax([5, 2, 9, 1, 7]);
console.log(minimum); // 1
console.log(maximum); // 9
8. Swapping elements with destructuring
Before ES6, swapping required a temporary variable. Destructuring makes it one line.
let a = 1;
let b = 2;
// Old way
let temp = a;
a = b;
b = temp;
// Modern way -- destructuring swap
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1
Swapping array elements in place:
const arr = ["first", "second", "third"];
// Swap index 0 and index 2
[arr[0], arr[2]] = [arr[2], arr[0]];
console.log(arr); // ["third", "second", "first"]
// Swap any two indices
function swap(array, i, j) {
[array[i], array[j]] = [array[j], array[i]];
}
const nums = [10, 20, 30, 40];
swap(nums, 1, 3);
console.log(nums); // [10, 40, 30, 20]
9. Finding elements (preview of array methods)
These methods help you locate elements. They are covered in depth in 1.22 -- Array Methods, but knowing the basics here is essential.
indexOf() -- find index of a value
const fruits = ["apple", "banana", "cherry", "banana"];
fruits.indexOf("banana"); // 1 (first occurrence)
fruits.indexOf("grape"); // -1 (not found)
fruits.indexOf("banana", 2); // 3 (search from index 2)
includes() -- check if a value exists (ES2016)
const nums = [1, 2, 3, 4, 5];
nums.includes(3); // true
nums.includes(10); // false
nums.includes(3, 3); // false (search from index 3)
// includes vs indexOf
// indexOf returns index (-1 if missing); includes returns boolean
// includes handles NaN correctly
[NaN].indexOf(NaN); // -1 (broken!)
[NaN].includes(NaN); // true (correct!)
find() -- find first element matching a condition
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Carol", age: 28 }
];
const found = users.find(user => user.age > 26);
console.log(found); // { name: "Bob", age: 30 }
const notFound = users.find(user => user.age > 50);
console.log(notFound); // undefined
findIndex() -- find index of first match
const scores = [45, 82, 73, 91, 68];
const idx = scores.findIndex(score => score > 80);
console.log(idx); // 1
const noIdx = scores.findIndex(score => score > 100);
console.log(noIdx); // -1
Quick comparison:
| Method | Returns | Use when |
|---|---|---|
indexOf(val) | Index or -1 | Searching for a primitive value |
includes(val) | Boolean | Checking existence of a primitive |
find(fn) | Element or undefined | Searching with a condition / objects |
findIndex(fn) | Index or -1 | Need index of a conditional match |
10. Real example: accessing data from API responses
Arrays from APIs often need careful access patterns.
// Simulated API response
const apiResponse = {
status: "success",
data: {
users: [
{ id: 1, name: "Alice", roles: ["admin", "user"] },
{ id: 2, name: "Bob", roles: ["user"] },
{ id: 3, name: "Carol", roles: ["user", "editor"] }
]
}
};
// Accessing nested array data
const users = apiResponse.data.users;
// First user's name
console.log(users[0].name); // "Alice"
// Last user using at()
console.log(users.at(-1).name); // "Carol"
// First user's first role
console.log(users[0].roles[0]); // "admin"
// Destructuring the response
const [firstUser, ...otherUsers] = users;
console.log(firstUser.name); // "Alice"
console.log(otherUsers.length); // 2
// Safe access with optional chaining
console.log(users[10]?.name); // undefined (no error)
console.log(users[0]?.roles?.[5]); // undefined (no error)
// Find a specific user
const bob = users.find(u => u.name === "Bob");
console.log(bob?.roles); // ["user"]
Key takeaways
- Arrays are zero-indexed -- the first element is
arr[0], the last isarr[arr.length - 1]. - Bracket notation
arr[i]is the fundamental access pattern; it returnsundefinedfor missing indices (no error). at()(ES2022) supports negative indices:arr.at(-1)is the last element.- Destructuring
const [a, b, ...rest] = arrunpacks arrays into variables cleanly. - Swapping is a one-liner with destructuring:
[a, b] = [b, a]. - Use
indexOf/includesfor primitives,find/findIndexfor objects or conditions. - Always guard against out-of-bounds access with length checks or optional chaining.
Explain-It Challenge
Explain without notes:
- Why does
arr[-1]returnundefinedin bracket notation, butarr.at(-1)returns the last element? - What is the difference between a hole in a sparse array and an index whose value is
undefined? - Write destructuring that extracts the first element, skips the second, and collects the rest into a variable.
- Why does
[NaN].indexOf(NaN)return-1while[NaN].includes(NaN)returnstrue?
Navigation: <- 1.21.a -- Creating Arrays . 1.21.c -- Array Operations ->