Skip to content

Arrays

Arrays are a fundamental data structure in JavaScript that allow you to store and manage multiple values within a single object. Unlike simple variables that hold one value, arrays can hold ordered collections of any data type - numbers, strings, objects, or even other arrays.

Understanding arrays is essential because they’re everywhere in modern web development: processing API responses, managing lists of items, handling user inputs, and much more. In this chapter, you’ll learn how to create, manipulate, and transform arrays using both classic and modern JavaScript methods.

Understanding Arrays

Arrays in JavaScript are dynamic, flexible data structures with several key characteristics:

  • They are zero-indexed (first element is at index 0)
  • They can grow or shrink dynamically
  • They can store mixed data types
  • They are actually specialized objects (but with array-specific behavior)
  • They maintain insertion order
// Creating arrays
let numbers = [1, 2, 3, 4, 5];
let mixed = [42, "hello", true, null, { name: "John" }];
let empty = [];

console.log(numbers[0]);     // 1 (first element)
console.log(numbers.length); // 5
console.log(numbers[10]);    // undefined (doesn't exist)

Creating Arrays

The most common and recommended way to create arrays is using array literals with square brackets:

let fruits = ["apple", "banana", "orange"];
let numbers = [1, 2, 3, 4, 5];
let empty = [];

Array Constructor

You can also use the Array constructor, but this can be confusing:

let arr1 = new Array(5);        // Creates array with length 5 (empty slots!)
let arr2 = new Array(1, 2, 3);  // Creates [1, 2, 3]
let arr3 = new Array("5");      // Creates ["5"]

Avoid new Array() with single numeric argument

Using new Array(n) creates a sparse array with “holes” - empty slots that behave differently than undefined:

let sparse = new Array(3);     // [ <3 empty items> ]
let filled = [undefined, undefined, undefined]; // [ undefined, undefined, undefined ]

sparse.map(x => x * 2);  // Returns [ <3 empty items> ] - map skips holes!
filled.map(x => x * 2);  // Returns [ NaN, NaN, NaN ]

Best practice: Always use array literals [] for clarity and consistency.

Modern Creation Methods

// Array.from() - creates array from iterable or array-like object
let str = "hello";
let chars = Array.from(str);
console.log(chars); // ["h", "e", "l", "l", "o"]

// With mapping function
let doubled = Array.from([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]

// Array.of() - creates array from arguments (solves new Array() confusion)
let arr = Array.of(5);    // [5] - not array with length 5!
let arr2 = Array.of(1, 2, 3); // [1, 2, 3]

Iterating Over Arrays

Traditional for Loop

The classic approach gives you full control with index access:

let fruits = ["apple", "banana", "orange"];

for (let i = 0; i < fruits.length; i++) {
    console.log(i + ": " + fruits[i]);
}
// 0: apple
// 1: banana
// 2: orange

Introduced in ES2015, for...of is the cleanest way to iterate when you need the values:

let fruits = ["apple", "banana", "orange"];

for (let fruit of fruits) {
    console.log(fruit);
}
// apple
// banana
// orange

// Works with any iterable
for (let char of "hello") {
    console.log(char); // h, e, l, l, o
}

forEach Method

The forEach method executes a function for each array element. It’s more functional but you can’t break out of it:

let fruits = ["apple", "banana", "orange"];

fruits.forEach((fruit, index, array) => {
    console.log(`${index}: ${fruit}`);
});
// 0: apple
// 1: banana
// 2: orange

// Common pattern: only use value
fruits.forEach(fruit => console.log(fruit));

for…of vs forEach

  • Use for...of when you might need to break or continue
  • Use forEach for pure iteration with side effects
  • Use map/filter/reduce when transforming data

for…in Loop (Avoid for Arrays)

The for...in loop iterates over enumerable properties. Avoid using it with arrays as it can produce unexpected results:

let arr = ["a", "b", "c"];
arr.customProp = "oops"; // Don't do this, but it's possible

for (let key in arr) {
    console.log(key); // "0", "1", "2", "customProp" 😱
}

// Use for...of instead
for (let value of arr) {
    console.log(value); // "a", "b", "c" ✅
}

When to use for…in

Reserve for...in for objects, not arrays:

let person = { name: "John", age: 30 };
for (let key in person) {
    console.log(`${key}: ${person[key]}`);
}

Core Array Methods

Understanding which methods mutate the original array vs. which return new arrays is crucial.

Adding and Removing Elements

Mutating Methods (Modify Original Array)

let arr = [1, 2, 3];

// push() - add to end
arr.push(4);
console.log(arr); // [1, 2, 3, 4]

// pop() - remove from end
let last = arr.pop();
console.log(last); // 4
console.log(arr);  // [1, 2, 3]

// unshift() - add to beginning
arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]

// shift() - remove from beginning
let first = arr.shift();
console.log(first); // 0
console.log(arr);   // [1, 2, 3]

// splice() - add/remove elements at any position
let removed = arr.splice(1, 1, 99); // At index 1, remove 1 item, insert 99
console.log(removed); // [2]
console.log(arr);     // [1, 99, 3]

Non-mutating Methods (Return New Arrays)

let arr = [1, 2, 3, 4, 5];

// slice() - extract portion (doesn't modify original)
let portion = arr.slice(1, 4); // From index 1 to 4 (exclusive)
console.log(portion); // [2, 3, 4]
console.log(arr);     // [1, 2, 3, 4, 5] - unchanged

// concat() - combine arrays
let arr2 = [6, 7];
let combined = arr.concat(arr2);
console.log(combined); // [1, 2, 3, 4, 5, 6, 7]
console.log(arr);      // [1, 2, 3, 4, 5] - unchanged

Mutating vs Non-mutating

Mutating methods (modify original):

  • push(), pop(), shift(), unshift()
  • splice()
  • sort(), reverse()
  • fill()

Non-mutating methods (return new array):

  • slice(), concat()
  • map(), filter(), reduce()
  • toSorted(), toReversed(), toSpliced() (ES2023)

Searching and Testing

let numbers = [5, 1, 6, 7, 2, 13, 8];

// includes() - check if value exists
console.log(numbers.includes(6));    // true
console.log(numbers.includes(100));  // false

// indexOf() - find first index of value
console.log(numbers.indexOf(6));     // 2
console.log(numbers.indexOf(100));   // -1 (not found)

// lastIndexOf() - find last index of value
let arr = [1, 2, 3, 2, 1];
console.log(arr.lastIndexOf(2));     // 3

// find() - find first element matching condition
let firstLarge = numbers.find(n => n > 5);
console.log(firstLarge); // 6

// findIndex() - find index of first matching element
let index = numbers.findIndex(n => n > 5);
console.log(index); // 2

// findLast() - find last element matching condition (ES2023)
let lastLarge = numbers.findLast(n => n > 5);
console.log(lastLarge); // 8

// findLastIndex() - find index of last matching element (ES2023)
let lastIndex = numbers.findLastIndex(n => n > 5);
console.log(lastIndex); // 6

// every() - test if ALL elements pass condition
let allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true

// some() - test if AT LEAST ONE element passes condition
let hasLarge = numbers.some(n => n > 10);
console.log(hasLarge); // true

Real-world example - validating form data:

let formFields = [
    { name: "email", value: "user@example.com", valid: true },
    { name: "password", value: "123456", valid: true },
    { name: "age", value: "", valid: false }
];

// Check if all fields are valid
let canSubmit = formFields.every(field => field.valid);
console.log(canSubmit); // false

// Check if any field is invalid
let hasErrors = formFields.some(field => !field.valid);
console.log(hasErrors); // true

// Find first invalid field
let firstError = formFields.find(field => !field.valid);
console.log(firstError.name); // "age"

Transformation Methods

These methods are fundamental to functional programming in JavaScript.

map() - Transform Each Element

Creates a new array by applying a function to each element:

let numbers = [1, 2, 3, 4, 5];

// Double each number
let doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] - original unchanged

// Transform objects
let users = [
    { firstName: "John", lastName: "Doe" },
    { firstName: "Jane", lastName: "Smith" }
];

let fullNames = users.map(user => `${user.firstName} ${user.lastName}`);
console.log(fullNames); // ["John Doe", "Jane Smith"]

Real-world example - processing API data:

// API returns raw product data
let apiResponse = [
    { id: 1, name: "Laptop", price_cents: 99900, in_stock: true },
    { id: 2, name: "Mouse", price_cents: 2500, in_stock: false }
];

// Transform to display format
let products = apiResponse.map(item => ({
    id: item.id,
    name: item.name,
    price: `$${(item.price_cents / 100).toFixed(2)}`,
    available: item.in_stock ? "In Stock" : "Out of Stock"
}));

console.log(products);
// [
//   { id: 1, name: "Laptop", price: "$999.00", available: "In Stock" },
//   { id: 2, name: "Mouse", price: "$25.00", available: "Out of Stock" }
// ]

filter() - Select Elements

Creates a new array with elements that pass a test:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Get even numbers
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// Get numbers greater than 5
let large = numbers.filter(n => n > 5);
console.log(large); // [6, 7, 8, 9, 10]

Real-world example - filtering products:

let products = [
    { name: "Laptop", price: 999, category: "electronics", inStock: true },
    { name: "Shirt", price: 29, category: "clothing", inStock: false },
    { name: "Phone", price: 699, category: "electronics", inStock: true },
    { name: "Shoes", price: 89, category: "clothing", inStock: true }
];

// Get available electronics under $1000
let availableElectronics = products.filter(p => 
    p.category === "electronics" && 
    p.inStock && 
    p.price < 1000
);

console.log(availableElectronics);
// [{ name: "Phone", price: 699, category: "electronics", inStock: true }]

reduce() - Reduce to Single Value

Reduces an array to a single value by applying a function cumulatively:

// Syntax: array.reduce((accumulator, currentValue) => {...}, initialValue)

let numbers = [1, 2, 3, 4, 5];

// Sum all numbers
let sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// Find maximum
let max = numbers.reduce((acc, n) => n > acc ? n : acc, numbers[0]);
console.log(max); // 5

// Count occurrences
let items = ["apple", "banana", "apple", "orange", "banana", "apple"];
let counts = items.reduce((acc, item) => {
    acc[item] = (acc[item] || 0) + 1;
    return acc;
}, {});
console.log(counts); // { apple: 3, banana: 2, orange: 1 }

Real-world example - shopping cart total:

let cart = [
    { name: "Laptop", price: 999, quantity: 1 },
    { name: "Mouse", price: 25, quantity: 2 },
    { name: "Keyboard", price: 75, quantity: 1 }
];

// Calculate total cost
let total = cart.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
}, 0);

console.log(`Total: $${total}`); // Total: $1124

When to use reduce

reduce() is powerful but can be hard to read. Use it for:

  • Summing/accumulating values
  • Grouping or categorizing data
  • Converting arrays to objects
  • Flattening nested structures

For simple transformations, map() or filter() are usually clearer.

Sorting

sort() - In-place Sorting (Mutates Original)

The sort() method sorts array elements in place and returns the sorted array.

Default sort() converts to strings!

By default, sort() converts elements to strings and sorts by UTF-16 code units. This causes unexpected behavior with numbers:

let numbers = [1, 13, 2, 5, 6, 7, 8];
numbers.sort();
console.log(numbers); // [1, 13, 2, 5, 6, 7, 8] 😱
// Sorted as strings: "1", "13", "2", "5", "6", "7", "8"

Sorting numbers correctly:

let numbers = [1, 13, 2, 5, 6, 7, 8];

// Ascending order
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 2, 5, 6, 7, 8, 13]

// Descending order
numbers.sort((a, b) => b - a);
console.log(numbers); // [13, 8, 7, 6, 5, 2, 1]

Sorting strings:

let fruits = ["banana", "apple", "Cherry", "date"];

// Case-sensitive (uppercase first)
fruits.sort();
console.log(fruits); // ["Cherry", "apple", "banana", "date"]

// Case-insensitive
fruits.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(fruits); // ["apple", "banana", "Cherry", "date"]

Sorting objects:

let users = [
    { name: "John", age: 30 },
    { name: "Jane", age: 25 },
    { name: "Bob", age: 35 }
];

// Sort by age
users.sort((a, b) => a.age - b.age);
console.log(users);
// [{ name: "Jane", age: 25 }, { name: "John", age: 30 }, { name: "Bob", age: 35 }]

// Sort by name
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users);
// [{ name: "Bob", age: 35 }, { name: "Jane", age: 25 }, { name: "John", age: 30 }]

reverse() - Reverse Array (Mutates Original)

let arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1]

Modern Immutable Methods (ES2023)

ES2023 introduced immutable versions of common mutating methods. These return new arrays without modifying the original:

let original = [3, 1, 4, 1, 5];

// toSorted() - immutable sort
let sorted = original.toSorted((a, b) => a - b);
console.log(sorted);   // [1, 1, 3, 4, 5]
console.log(original); // [3, 1, 4, 1, 5] - unchanged! ✅

// toReversed() - immutable reverse
let reversed = original.toReversed();
console.log(reversed);  // [5, 1, 4, 1, 3]
console.log(original);  // [3, 1, 4, 1, 5] - unchanged! ✅

// toSpliced() - immutable splice
let spliced = original.toSpliced(1, 2, 99); // At index 1, remove 2, insert 99
console.log(spliced);   // [3, 99, 1, 5]
console.log(original);  // [3, 1, 4, 1, 5] - unchanged! ✅

// with() - immutable element replacement
let modified = original.with(2, 999); // Replace index 2 with 999
console.log(modified);  // [3, 1, 999, 1, 5]
console.log(original);  // [3, 1, 4, 1, 5] - unchanged! ✅

Why immutable methods matter

Immutable methods are safer in modern JavaScript because:

  • They prevent accidental mutations
  • They work better with React state and functional programming
  • They make code more predictable and easier to debug

When to use:

  • Use toSorted() instead of sort() when you need the original array
  • Use immutable methods in React components and state management
  • Use mutating methods when performance is critical and you don’t need the original

Modern Array Features

Accessing Elements with at() (ES2022)

The at() method allows negative indices to access elements from the end:

let arr = [1, 2, 3, 4, 5];

// Positive indices (like bracket notation)
console.log(arr.at(0));   // 1
console.log(arr[0]);      // 1

// Negative indices (from end) - this is new!
console.log(arr.at(-1));  // 5 (last element)
console.log(arr.at(-2));  // 4 (second to last)

// Compare to old way
console.log(arr[arr.length - 1]); // 5 (verbose)
console.log(arr.at(-1));          // 5 (clean)

Flattening Arrays (ES2019)

The flat() method creates a new array with sub-array elements concatenated:

// flat() - flatten nested arrays
let nested = [1, [2, 3], [4, [5, 6]]];

console.log(nested.flat());    // [1, 2, 3, 4, [5, 6]] (depth 1)
console.log(nested.flat(2));   // [1, 2, 3, 4, 5, 6] (depth 2)
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6] (all levels)

// flatMap() - map then flatten (depth 1)
let words = ["hello world", "foo bar"];
let letters = words.flatMap(str => str.split(" "));
console.log(letters); // ["hello", "world", "foo", "bar"]

// Equivalent to:
let letters2 = words.map(str => str.split(" ")).flat();

Real-world example - processing nested data:

let users = [
    { name: "John", hobbies: ["reading", "gaming"] },
    { name: "Jane", hobbies: ["cooking", "hiking"] }
];

// Get all unique hobbies
let allHobbies = users.flatMap(user => user.hobbies);
console.log(allHobbies); // ["reading", "gaming", "cooking", "hiking"]

let uniqueHobbies = [...new Set(allHobbies)];
console.log(uniqueHobbies); // ["reading", "gaming", "cooking", "hiking"]

Advanced Patterns

Method Chaining

Array methods can be chained for powerful data transformations:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Get sum of squares of even numbers
let result = numbers
    .filter(n => n % 2 === 0)    // [2, 4, 6, 8, 10]
    .map(n => n * n)             // [4, 16, 36, 64, 100]
    .reduce((sum, n) => sum + n, 0); // 220

console.log(result); // 220

Real-world example - processing sales data:

let sales = [
    { product: "Laptop", amount: 999, category: "electronics" },
    { product: "Shirt", amount: 29, category: "clothing" },
    { product: "Phone", amount: 699, category: "electronics" },
    { product: "Shoes", amount: 89, category: "clothing" },
    { product: "Tablet", amount: 399, category: "electronics" }
];

// Get total electronics sales over $500
let totalHighValueElectronics = sales
    .filter(sale => sale.category === "electronics")
    .filter(sale => sale.amount > 500)
    .map(sale => sale.amount)
    .reduce((sum, amount) => sum + amount, 0);

console.log(totalHighValueElectronics); // 1698

Method chaining best practices

  • Each step should be clear and single-purpose
  • Break into variables if the chain gets too long (>3-4 methods)
  • Remember that each method creates a new array (except reduce)
  • For performance-critical code, consider a single loop instead

Array Destructuring

Destructuring allows extracting array elements into variables:

// Basic destructuring
let [first, second, third] = [1, 2, 3];
console.log(first);  // 1
console.log(second); // 2
console.log(third);  // 3

// Skip elements
let [a, , c] = [1, 2, 3];
console.log(a); // 1
console.log(c); // 3

// Rest operator
let [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// Default values
let [x = 10, y = 20] = [5];
console.log(x); // 5
console.log(y); // 20

// Swapping variables
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2, 1

Real-world example - parsing function returns:

function getUserData() {
    return ["John Doe", 30, "john@example.com"];
}

let [name, age, email] = getUserData();
console.log(name);  // "John Doe"
console.log(age);   // 30
console.log(email); // "john@example.com"

Spread Operator

The spread operator (...) expands arrays in places where multiple elements are expected:

// Copying arrays (shallow copy)
let original = [1, 2, 3];
let copy = [...original];
console.log(copy); // [1, 2, 3]
copy.push(4);
console.log(original); // [1, 2, 3] - unchanged
console.log(copy);     // [1, 2, 3, 4]

// Concatenating arrays
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Adding elements
let numbers = [2, 3, 4];
let expanded = [1, ...numbers, 5];
console.log(expanded); // [1, 2, 3, 4, 5]

// Passing array as function arguments
let nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3
// Equivalent to: Math.max(1, 2, 3)

// Merging objects in arrays
let defaults = { theme: "light", lang: "en" };
let userPrefs = { theme: "dark" };
let config = { ...defaults, ...userPrefs };
console.log(config); // { theme: "dark", lang: "en" }

Other Collection Types

Modern JavaScript provides specialized collection types beyond arrays.

Set - Unique Values

A Set stores unique values of any type:

// Creating sets
let set = new Set([1, 2, 3, 4, 5]);
set.add(6);
set.add(3); // Duplicate - won't be added
console.log(set.size); // 6

// Checking values
console.log(set.has(3)); // true
console.log(set.has(10)); // false

// Removing values
set.delete(3);
console.log(set.has(3)); // false

// Iterating
for (let value of set) {
    console.log(value);
}

// Converting between arrays and sets
let arr = [1, 2, 2, 3, 3, 4];
let uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4]

// Clear all
set.clear();
console.log(set.size); // 0

Real-world example - removing duplicates:

let users = [
    { id: 1, name: "John" },
    { id: 2, name: "Jane" },
    { id: 1, name: "John" }, // duplicate
    { id: 3, name: "Bob" }
];

// Remove duplicates by id
let uniqueIds = new Set(users.map(u => u.id));
let uniqueUsers = [...uniqueIds].map(id => 
    users.find(u => u.id === id)
);
console.log(uniqueUsers);
// [{ id: 1, name: "John" }, { id: 2, name: "Jane" }, { id: 3, name: "Bob" }]

Map - Key-Value Pairs

A Map holds key-value pairs with any type of key:

// Creating maps
let map = new Map();
map.set("name", "John");
map.set("age", 30);
map.set(1, "one");    // Number key
map.set(true, "yes"); // Boolean key

console.log(map.size); // 4

// Getting values
console.log(map.get("name")); // "John"
console.log(map.get(1));      // "one"

// Checking keys
console.log(map.has("name")); // true
console.log(map.has("email")); // false

// Deleting
map.delete("age");
console.log(map.has("age")); // false

// Iterating
for (let [key, value] of map) {
    console.log(`${key}: ${value}`);
}

// Map vs Object
let obj = { name: "John" };
map.set(obj, "some value"); // Object as key!
console.log(map.get(obj)); // "some value"

// Clear all
map.clear();

When to use Map vs Object:

  • Use Map when:

    • Keys are not strings
    • Keys are dynamically added/removed frequently
    • You need to iterate in insertion order
    • You need the size property
  • Use Object when:

    • Keys are always strings/symbols
    • Working with JSON data
    • Need simple property access (obj.key)

Typed Arrays

Typed arrays provide a mechanism for accessing raw binary data. They’re used mainly for:

  • Working with binary data (files, network protocols)
  • WebGL and graphics programming
  • Performance-critical mathematical operations
  • Audio/video processing
// Int8Array - 8-bit signed integers (-128 to 127)
let int8 = new Int8Array([1, 2, 3, 4]);

// Float32Array - 32-bit floating point
let float32 = new Float32Array([1.5, 2.7, 3.9]);

// Uint8Array - 8-bit unsigned integers (0 to 255)
let uint8 = new Uint8Array([255, 128, 0]);

console.log(int8[0]);    // 1
console.log(float32[1]); // 2.7
console.log(uint8.length); // 3

When you’ll need typed arrays

As a beginner, you likely won’t need typed arrays until you work with:

  • Canvas/WebGL for graphics
  • Audio processing (Web Audio API)
  • File manipulation (reading binary files)
  • WebAssembly interactions

For 99% of web development, regular arrays are sufficient.

Best Practices

Choosing the Right Method

// ✅ DO: Use map for transformations
let doubled = numbers.map(n => n * 2);

// ❌ DON'T: Use forEach for transformations
let doubled = [];
numbers.forEach(n => doubled.push(n * 2)); // Unnecessary

// ✅ DO: Use filter for selection
let evens = numbers.filter(n => n % 2 === 0);

// ❌ DON'T: Use reduce for simple operations
let evens = numbers.reduce((acc, n) => {
    if (n % 2 === 0) acc.push(n);
    return acc;
}, []); // Overcomplicated

// ✅ DO: Use forEach for side effects
users.forEach(user => sendEmail(user));

// ❌ DON'T: Use map for side effects
users.map(user => sendEmail(user)); // Returns unused array

Immutability

// ✅ DO: Use non-mutating methods
let sorted = [...original].sort();        // ES2015+
let sorted = original.toSorted();         // ES2023

// ❌ DON'T: Mutate unless necessary
original.sort(); // Mutates original!

// ✅ DO: Copy before mutating
let copy = [...original];
copy.push(newItem);

// ✅ DO: Use spread for shallow copies
let usersCopy = [...users];

Performance Considerations

// ⚠️ Chaining creates multiple intermediate arrays
let result = arr
    .filter(x => x > 0)    // Creates array 1
    .map(x => x * 2)       // Creates array 2
    .filter(x => x < 100); // Creates array 3

// ✅ For large arrays (10,000+ items), consider single loop
let result = [];
for (let x of arr) {
    if (x > 0) {
        let doubled = x * 2;
        if (doubled < 100) {
            result.push(doubled);
        }
    }
}

// But for most cases, readability > micro-optimization!

Readability

// ❌ DON'T: Complex one-liners
let x = users.filter(u => u.age > 18).map(u => ({...u, adult: true})).reduce((a, u) => a + u.age, 0);

// ✅ DO: Break into steps with meaningful names
let adults = users.filter(u => u.age > 18);
let markedAdults = adults.map(u => ({...u, adult: true}));
let totalAge = markedAdults.reduce((sum, u) => sum + u.age, 0);

// Or:
let totalAdultAge = users
    .filter(u => u.age > 18)
    .map(u => ({...u, adult: true}))
    .reduce((sum, u) => sum + u.age, 0);

Destructuring

Destructuring is a convenient way to extract values from arrays or properties from objects into distinct variables. Introduced in ES6, it makes code cleaner and more readable.

Array Destructuring

Extract values from arrays:

// Traditional way
const numbers = [1, 2, 3];
const first = numbers[0];
const second = numbers[1];

// Destructuring way
const [one, two, three] = numbers;
console.log(one);    // 1
console.log(two);    // 2
console.log(three);  // 3

Skipping Elements:

const numbers = [1, 2, 3, 4, 5];
const [first, , third] = numbers;  // Skip second element
console.log(first);   // 1
console.log(third);   // 3

Rest Pattern:

const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first);   // 1
console.log(second);  // 2
console.log(rest);    // [3, 4, 5]

Default Values:

const numbers = [1];
const [first, second = 0] = numbers;
console.log(first);   // 1
console.log(second);  // 0 (default value)

Swapping Variables:

let a = 1;
let b = 2;

[a, b] = [b, a];  // Swap values
console.log(a);  // 2
console.log(b);  // 1

Summary

Arrays are the foundation of data manipulation in JavaScript. Understanding how to effectively create, iterate, transform, and manage arrays is essential for modern web development.

Key Takeaways

Creating Arrays:

  • Always use array literals [] instead of new Array() to avoid confusion
  • Use Array.from() to convert iterables to arrays
  • Use Array.of() when you need to create arrays from arguments

Iteration:

  • Use for...of for simple value iteration (modern and clean)
  • Use forEach for side effects when you don’t need to break
  • Use traditional for when you need index manipulation or need to break
  • Avoid for...in with arrays - use it only for objects

Core Concepts:

  • Mutating methods modify the original array: push, pop, shift, unshift, splice, sort, reverse
  • Non-mutating methods return new arrays: map, filter, reduce, slice, concat, toSorted, toReversed
  • Always know whether a method mutates or not to avoid bugs!

Transformation Methods:

  • map(): Transform each element into something else
  • filter(): Select elements that match criteria
  • reduce(): Accumulate array into single value
  • These are the workhorses of functional JavaScript!

Modern Features (ES2019-ES2023):

  • at(): Access elements with negative indices (arr.at(-1) for last element)
  • flat() / flatMap(): Flatten nested arrays
  • findLast() / findLastIndex(): Search from the end
  • toSorted() / toReversed() / toSpliced() / with(): Immutable alternatives to mutating methods

Best Practices:

  • Prefer immutable methods when you don’t need the original array
  • Use method chaining for readable data transformations
  • Choose the simplest method for the task (don’t use reduce when map will do)
  • Use Set for unique values, Map for key-value pairs with non-string keys
  • Prioritize readability over clever one-liners
  • Copy arrays with spread [...arr] to avoid mutation

When to Use What:

  • Simple transformation? → map()
  • Selection? → filter()
  • Accumulation? → reduce()
  • Finding one item? → find() or findIndex()
  • Testing conditions? → every() or some()
  • Need to preserve original? → Use non-mutating methods or copy first

Arrays are everywhere in JavaScript - from handling API responses to managing UI state to processing user input. Mastering these patterns will make you a significantly more effective JavaScript developer.


Assignment(s)