[ ] .map() .filter() .reduce()
Frontend

JavaScript Array Methods You Must Know

Mayur Dabhi
Mayur Dabhi
Mar 9, 2026
18 min read

You get a list of orders back from an API and need the paid ones, grouped by customer, with totals. The first version is usually a for loop with three temporary variables and a counter, and a week later nobody wants to touch it. Then a sort() somewhere quietly reorders an array another component was still using, and you spend an afternoon figuring out why the UI jumped.

This post goes through the array methods I actually use day to day: what each one returns, whether it mutates the original, and the gotchas that bite in real code. There are visual examples for the trickier ones and a short section on performance at the end.

What's covered
  • Transformation methods: map(), flatMap()
  • Filtering methods: filter(), find(), findIndex()
  • Aggregation methods: reduce(), reduceRight()
  • Testing methods: some(), every(), includes()
  • Iteration methods: forEach(), entries(), keys()
  • Mutating vs non-mutating: knowing when arrays change
  • Method chaining for readable data pipelines

Understanding Array Method Categories

When I'm not sure which method to reach for, I ask what I want back: a new array, one value, one element, or a yes/no. That question maps pretty cleanly onto these groups:

Transformers

Create new arrays with modified elements

Filters

Select elements that match criteria

Reducers

Combine elements into a single value

Searchers

Find specific elements or indices

Testers

Check conditions across elements

Iterators

Execute operations on each element

Input Array [1,2,3,4] Array Method .map(x => x*2) New Array [2,4,6,8] Original unchanged Brand new array

Most array methods don't modify the original array. They return a new one.

1. The map() Method: Transform Every Element

Any time the output should have the same length as the input but each item looks different, use map(). It calls your function on every element and collects whatever you return into a new array.

Syntax
const newArray = array.map((element, index, array) => {
  // return transformed element
});
Visual Example: Doubling Numbers
1
2
3
4
→ .map(x => x * 2) →
2
4
6
8
javascript
// Basic transformation
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
// Result: [2, 4, 6, 8, 10]

// Transform objects
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

const names = users.map(user => user.name);
// Result: ['Alice', 'Bob', 'Charlie']

// Add computed properties
const enrichedUsers = users.map(user => ({
  ...user,
  isAdult: user.age >= 18,
  greeting: `Hello, ${user.name}!`
}));

// Using index parameter
const indexed = ['a', 'b', 'c'].map((letter, index) => ({
  position: index + 1,
  letter: letter.toUpperCase()
}));
// Result: [{position: 1, letter: 'A'}, {position: 2, letter: 'B'}, ...]
Common Pitfall

If you aren't using the returned array, don't use map(). For side effects, forEach() says what you mean. The other classic mistake is an arrow function with braces and no return: users.map(u => { u.name }) gives you an array full of undefined.

2. The filter() Method: Keep What Matches

filter() returns a new array with only the elements where your callback returns something truthy. The output can be shorter than the input, or empty, but never longer.

Visual Example: Filter Even Numbers
1
2
3
4
5
6
→ .filter(x => x % 2 === 0) →
2
4
6
javascript
// Filter by condition
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const evenNumbers = numbers.filter(num => num % 2 === 0);
// Result: [2, 4, 6, 8, 10]

const greaterThanFive = numbers.filter(num => num > 5);
// Result: [6, 7, 8, 9, 10]

// Filter objects
const products = [
  { name: 'Laptop', price: 999, inStock: true },
  { name: 'Phone', price: 699, inStock: false },
  { name: 'Tablet', price: 499, inStock: true },
  { name: 'Watch', price: 299, inStock: true }
];

const availableProducts = products.filter(p => p.inStock);
const affordableProducts = products.filter(p => p.price < 500);

// Multiple conditions
const affordableAvailable = products.filter(
  p => p.inStock && p.price < 700
);

// Remove falsy values
const mixed = [0, 'hello', '', null, 42, undefined, 'world'];
const truthy = mixed.filter(Boolean);
// Result: ['hello', 42, 'world']

// Remove duplicates (for primitives, [...new Set(arr)] is simpler)
const duplicates = [1, 2, 2, 3, 3, 3, 4];
const unique = duplicates.filter((val, idx, arr) => 
  arr.indexOf(val) === idx
);
// Result: [1, 2, 3, 4]

3. The reduce() Method: The Swiss Army Knife

When the result isn't an array of the same shape (a sum, a lookup object, a grouped structure), reduce() is usually the answer. It walks the array and carries an accumulator from one step to the next. You can technically rebuild map() and filter() with it, but that flexibility is also why reduce() code gets hard to read fast.

Syntax
const result = array.reduce((accumulator, current, index, array) => {
  // return updated accumulator
}, initialValue);
How reduce() Works: Summing [1, 2, 3, 4] acc: 0, curr: 1 0 + 1 = 1 acc: 1, curr: 2 1 + 2 = 3 acc: 3, curr: 3 3 + 3 = 6 acc: 6, curr: 4 6 + 4 = 10 Result: 10 acc = accumulator (running total) | curr = current element Initial Value: 0

Reduce accumulates values step by step, passing the result to the next iteration

javascript
// Sum all numbers
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// Result: 15

// Find maximum value
const max = numbers.reduce((acc, curr) => 
  curr > acc ? curr : acc, 
  numbers[0]
);
// Result: 5

// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const fruitCount = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
// Result: { apple: 3, banana: 2, orange: 1 }

// Group by property
const people = [
  { name: 'Alice', department: 'Engineering' },
  { name: 'Bob', department: 'Marketing' },
  { name: 'Charlie', department: 'Engineering' },
  { name: 'Diana', department: 'Marketing' }
];

const byDepartment = people.reduce((acc, person) => {
  const dept = person.department;
  acc[dept] = acc[dept] || [];
  acc[dept].push(person);
  return acc;
}, {});

// Flatten nested arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => [...acc, ...arr], []);
// Result: [1, 2, 3, 4, 5, 6]

// Build objects from arrays
const pairs = [['name', 'John'], ['age', 30], ['city', 'NYC']];
const obj = pairs.reduce((acc, [key, value]) => {
  acc[key] = value;
  return acc;
}, {});
// Result: { name: 'John', age: 30, city: 'NYC' }
Always Pass an Initial Value

Without one, the first element becomes the accumulator. That works until the array is empty, and then you get TypeError: Reduce of empty array with no initial value, typically in production when a filter upstream returned nothing. It also causes type mix-ups when the accumulator should be an object but starts as the first item. One more thing: the "flatten" example above spreads acc on every step, which copies the whole array each time. Fine for small inputs, slow for big ones. Use flat() instead.

4. The find() and findIndex() Methods: Get One Match

filter() returns every match. find() returns only the first one, and findIndex() returns its position. Both stop as soon as they hit a match, so they do less work when one result is all you need.

find()

Returns the first matching element, or undefined if none found

findIndex()

Returns the index of first match, or -1 if none found

javascript
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'user' },
  { id: 4, name: 'Diana', role: 'moderator' }
];

// Find first admin
const admin = users.find(user => user.role === 'admin');
// Result: { id: 1, name: 'Alice', role: 'admin' }

// Find user by ID
const user = users.find(u => u.id === 3);
// Result: { id: 3, name: 'Charlie', role: 'user' }

// Find index
const moderatorIndex = users.findIndex(u => u.role === 'moderator');
// Result: 3

// Check if exists (cleaner than findIndex !== -1)
const hasAdmin = users.some(u => u.role === 'admin');
// Result: true

// Use with nullish coalescing for defaults
const guest = users.find(u => u.role === 'guest') ?? { name: 'Anonymous' };
// Result: { name: 'Anonymous' }

// findLast() and findLastIndex() - search from end (ES2023)
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const lastThree = numbers.findLast(n => n === 3);
// Result: 3 (the second occurrence)
const lastThreeIndex = numbers.findLastIndex(n => n === 3);
// Result: 6

5. The some() and every() Methods: Test Conditions

Need a yes/no answer about the whole array? some() is the array version of OR, every() is AND. Both short-circuit. Watch out for empty arrays: [].every(fn) is true and [].some(fn) is false, so an empty cart passes an "all items in stock" check.

Visual Comparison
some() - At least ONE must pass:
❌
✓
❌
→ true
every() - ALL must pass:
✓
✓
❌
→ false
javascript
const numbers = [1, 2, 3, 4, 5];

// some() - returns true if ANY element passes
const hasEven = numbers.some(n => n % 2 === 0);
// Result: true

const hasNegative = numbers.some(n => n < 0);
// Result: false

// every() - returns true if ALL elements pass
const allPositive = numbers.every(n => n > 0);
// Result: true

const allEven = numbers.every(n => n % 2 === 0);
// Result: false

// Practical examples
const cart = [
  { name: 'Laptop', price: 999, inStock: true },
  { name: 'Mouse', price: 29, inStock: true },
  { name: 'Keyboard', price: 79, inStock: false }
];

// Can we ship the order?
const canShip = cart.every(item => item.inStock);
// Result: false (keyboard not in stock)

// Is there anything expensive?
const hasExpensiveItem = cart.some(item => item.price > 500);
// Result: true

// Form validation
const formFields = [
  { name: 'email', valid: true },
  { name: 'password', valid: true },
  { name: 'username', valid: false }
];

const isFormValid = formFields.every(field => field.valid);
// Result: false

// Check permissions
const userPermissions = ['read', 'write'];
const requiredPermissions = ['read', 'write', 'delete'];

const hasAllPermissions = requiredPermissions.every(
  perm => userPermissions.includes(perm)
);
// Result: false (missing 'delete')

6. The forEach() Method: Side Effects Only

forEach() runs a function on each element and returns nothing. It's for side effects like logging or DOM updates. Be careful with async callbacks: forEach doesn't wait for promises, so items.forEach(async item => await save(item)) fires everything at once and moves on before any of it finishes. Use for...of with await, or Promise.all(items.map(...)).

javascript
const users = ['Alice', 'Bob', 'Charlie'];

// Logging
users.forEach(user => console.log(`Hello, ${user}!`));

// DOM manipulation
users.forEach(user => {
  const li = document.createElement('li');
  li.textContent = user;
  document.querySelector('ul').appendChild(li);
});

// With index
users.forEach((user, index) => {
  console.log(`${index + 1}. ${user}`);
});

// Modifying external state (use with caution!)
let total = 0;
[10, 20, 30].forEach(n => total += n);
console.log(total); // 60
forEach() Limitations
  • Cannot use break or continue
  • Cannot return a value (returns undefined)
  • Not chainable with other array methods
  • If you need these, use for...of loop instead

7. The includes(), indexOf(), and lastIndexOf() Methods

When you're looking for an exact value instead of something matching a condition, these are simpler than find(). Keep in mind they compare by identity, so users.includes({ id: 1 }) is always false for objects.

javascript
const fruits = ['apple', 'banana', 'orange', 'banana', 'mango'];

// includes() - returns boolean
fruits.includes('banana');      // true
fruits.includes('grape');       // false
fruits.includes('banana', 3);   // true (start from index 3)

// indexOf() - returns first index or -1
fruits.indexOf('banana');       // 1
fruits.indexOf('grape');        // -1

// lastIndexOf() - returns last index or -1
fruits.lastIndexOf('banana');   // 3

// Practical: toggle item in array
function toggleItem(array, item) {
  const index = array.indexOf(item);
  if (index === -1) {
    return [...array, item];  // Add item
  } else {
    return array.filter((_, i) => i !== index);  // Remove item
  }
}

// Check membership (cleaner with includes)
const allowedRoles = ['admin', 'moderator', 'editor'];
const userRole = 'admin';

if (allowedRoles.includes(userRole)) {
  console.log('Access granted');
}

// NaN handling - includes() works, indexOf() doesn't!
const withNaN = [1, NaN, 3];
withNaN.includes(NaN);   // true ✓
withNaN.indexOf(NaN);    // -1 ✗

8. The flat() and flatMap() Methods: Handle Nested Arrays

flat() removes nesting (one level by default). flatMap() does a map() followed by a one-level flat() in a single pass, which also makes it a handy way to filter and map at the same time.

Visual: Flattening Nested Arrays
[[1,2], [3,4], [5,6]] → .flat() → [1, 2, 3, 4, 5, 6]
javascript
// flat() - flatten nested arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.flat();
// Result: [1, 2, 3, 4, 5, 6]

// Deep nesting - specify depth
const deepNested = [1, [2, [3, [4, [5]]]]];
deepNested.flat();       // [1, 2, [3, [4, [5]]]] - depth 1 (default)
deepNested.flat(2);      // [1, 2, 3, [4, [5]]]
deepNested.flat(Infinity); // [1, 2, 3, 4, 5] - flatten completely

// flatMap() - map + flat in one step
const sentences = ['Hello world', 'How are you'];
const words = sentences.flatMap(sentence => sentence.split(' '));
// Result: ['Hello', 'world', 'How', 'are', 'you']

// vs doing it separately (less efficient)
const wordsLong = sentences.map(s => s.split(' ')).flat();

// Practical: expand data
const users = [
  { name: 'Alice', emails: ['alice@work.com', 'alice@home.com'] },
  { name: 'Bob', emails: ['bob@work.com'] }
];

const allEmails = users.flatMap(user => user.emails);
// Result: ['alice@work.com', 'alice@home.com', 'bob@work.com']

// Filter while mapping
const numbers = [1, 2, 3, 4, 5];
const doubledEvens = numbers.flatMap(n => 
  n % 2 === 0 ? [n * 2] : []
);
// Result: [4, 8] - only evens, doubled

9. The sort() Method: Order Your Data

sort() sorts in place and returns the same array. That mutation is the source of a lot of bugs, especially in React, where sorting an array from props or state changes data other code still holds. Spread first, or use toSorted() (ES2023).

Default Sort Gotcha

Without a compare function, sort() converts elements to strings and sorts alphabetically. This means [10, 2, 1].sort() returns [1, 10, 2], not [1, 2, 10]!

javascript
// Default string sort (alphabetical)
const fruits = ['banana', 'apple', 'cherry'];
fruits.sort();
// Result: ['apple', 'banana', 'cherry']

// Number sort - MUST provide compare function
const numbers = [10, 2, 1, 21, 5];

// Wrong! Default converts to strings
numbers.sort();
// Result: [1, 10, 2, 21, 5] ❌

// Correct - ascending
numbers.sort((a, b) => a - b);
// Result: [1, 2, 5, 10, 21] ✓

// Descending
numbers.sort((a, b) => b - a);
// Result: [21, 10, 5, 2, 1]

// Sort objects by property
const products = [
  { name: 'Laptop', price: 999 },
  { name: 'Phone', price: 699 },
  { name: 'Tablet', price: 499 }
];

// By price (ascending)
products.sort((a, b) => a.price - b.price);

// By name (alphabetical)
products.sort((a, b) => a.name.localeCompare(b.name));

// Immutable sort (ES2023)
const original = [3, 1, 2];
const sorted = original.toSorted((a, b) => a - b);
// original: [3, 1, 2] (unchanged)
// sorted: [1, 2, 3]

// Or with spread
const sortedCopy = [...original].sort((a, b) => a - b);

// Complex multi-field sort
const users = [
  { name: 'Bob', age: 30 },
  { name: 'Alice', age: 25 },
  { name: 'Alice', age: 30 }
];

users.sort((a, b) => {
  // First by name, then by age
  const nameCompare = a.name.localeCompare(b.name);
  if (nameCompare !== 0) return nameCompare;
  return a.age - b.age;
});

10. The slice() and splice() Methods: Extract and Modify

One letter apart, very different behavior. slice() extracts a portion without modifying the original, while splice() modifies the array in place.

Feature slice() splice()
Mutates Original No ✓ Yes ⚠️
Purpose Copy portion of array Add/remove elements
Returns New array Removed elements
Parameters (start, end) (start, deleteCount, ...items)
javascript
// slice() - extract without modifying
const arr = [0, 1, 2, 3, 4, 5];

arr.slice(2);       // [2, 3, 4, 5] - from index 2 to end
arr.slice(1, 4);    // [1, 2, 3] - from index 1 to 3 (end exclusive)
arr.slice(-2);      // [4, 5] - last 2 elements
arr.slice(1, -1);   // [1, 2, 3, 4] - exclude first and last

// Original unchanged
console.log(arr);   // [0, 1, 2, 3, 4, 5]

// Clone array
const clone = arr.slice();  // or [...arr]

// splice() - modify in place
const letters = ['a', 'b', 'c', 'd', 'e'];

// Remove elements
const removed = letters.splice(2, 2);  // Start at 2, remove 2
// removed: ['c', 'd']
// letters: ['a', 'b', 'e']

// Insert elements
letters.splice(2, 0, 'x', 'y');  // Start at 2, remove 0, insert 'x','y'
// letters: ['a', 'b', 'x', 'y', 'e']

// Replace elements
letters.splice(1, 2, 'NEW');  // Start at 1, remove 2, insert 'NEW'
// letters: ['a', 'NEW', 'y', 'e']

// Immutable alternative: toSpliced() (ES2023)
const original = [1, 2, 3, 4, 5];
const modified = original.toSpliced(2, 1, 'three');
// original: [1, 2, 3, 4, 5] (unchanged)
// modified: [1, 2, 'three', 4, 5]

Method Chaining: Building Data Pipelines

Because most of these methods return arrays, you can chain them, and each step reads like one line of a spec: keep these, sort by that, reshape into this. It's the main reason I prefer them over hand-written loops for this kind of code.

Data .filter() .map() .sort() .reduce() ✓

Each step in the chain does one job and hands its result to the next

javascript
// Real-world example: E-commerce product processing
const products = [
  { id: 1, name: 'Laptop', price: 999, category: 'Electronics', rating: 4.5, inStock: true },
  { id: 2, name: 'Phone', price: 699, category: 'Electronics', rating: 4.8, inStock: true },
  { id: 3, name: 'Headphones', price: 199, category: 'Electronics', rating: 4.2, inStock: false },
  { id: 4, name: 'Desk Chair', price: 299, category: 'Furniture', rating: 4.0, inStock: true },
  { id: 5, name: 'Monitor', price: 449, category: 'Electronics', rating: 4.6, inStock: true }
];

// Get top-rated in-stock electronics, formatted for display
const featuredProducts = products
  .filter(p => p.category === 'Electronics')  // Only electronics
  .filter(p => p.inStock)                      // Must be in stock
  .filter(p => p.rating >= 4.5)                // High rated
  .sort((a, b) => b.rating - a.rating)         // Best first
  .map(p => ({                                 // Transform
    id: p.id,
    displayName: `${p.name} ⭐${p.rating}`,
    formattedPrice: `$${p.price.toLocaleString()}`
  }));

// Result:
// [
//   { id: 2, displayName: 'Phone ⭐4.8', formattedPrice: '$699' },
//   { id: 5, displayName: 'Monitor ⭐4.6', formattedPrice: '$449' },
//   { id: 1, displayName: 'Laptop ⭐4.5', formattedPrice: '$999' }
// ]

// Calculate total value of in-stock items
const inventoryValue = products
  .filter(p => p.inStock)
  .reduce((total, p) => total + p.price, 0);
// Result: 2446

// Group and summarize
const categorySummary = products
  .filter(p => p.inStock)
  .reduce((acc, product) => {
    const cat = product.category;
    if (!acc[cat]) {
      acc[cat] = { count: 0, totalValue: 0, avgRating: 0, ratings: [] };
    }
    acc[cat].count++;
    acc[cat].totalValue += product.price;
    acc[cat].ratings.push(product.rating);
    acc[cat].avgRating = acc[cat].ratings.reduce((a, b) => a + b) / acc[cat].ratings.length;
    return acc;
  }, {});

Quick Reference Cheat Sheet

Array Methods at a Glance

map(fn)Transform each element → new array
filter(fn)Keep matching elements → new array
reduce(fn, init)Accumulate to single value
find(fn)First matching element or undefined
findIndex(fn)Index of first match or -1
some(fn)Any element passes? → boolean
every(fn)All elements pass? → boolean
forEach(fn)Execute for each (no return)
includes(val)Contains value? → boolean
indexOf(val)Index of value or -1
flat(depth)Flatten nested arrays
flatMap(fn)Map + flatten in one step
sort(fn)Sort in place ⚠️ mutates
slice(start, end)Extract portion → new array
splice(i, n, ...items)Add/remove in place ⚠️ mutates

ES2023+ New Array Methods

ES2023 added non-mutating versions of the methods that used to change arrays in place, plus a couple of search helpers. They're in all current major browsers and Node 20+, but check your targets before relying on them.

javascript
// Immutable versions (ES2023) - original unchanged!
const arr = [3, 1, 2];

arr.toSorted((a, b) => a - b);    // [1, 2, 3] - sort without mutating
arr.toReversed();                  // [2, 1, 3] - reverse without mutating
arr.toSpliced(1, 1, 'new');       // [3, 'new', 2] - splice without mutating
arr.with(1, 'replaced');          // [3, 'replaced', 2] - replace at index

// findLast() and findLastIndex() - search from end
const nums = [1, 2, 3, 4, 3, 2, 1];
nums.findLast(n => n > 2);        // 3 (the second one)
nums.findLastIndex(n => n > 2);   // 4

// Array.fromAsync() - create array from async iterables
const asyncArray = await Array.fromAsync(asyncGenerator());

// Check these with your target browser support!

Performance Considerations

Performance Habits

  • Remember each chained step creates a new array
  • Use find() over filter()[0] when you need one element
  • Use some() over filter().length > 0 for existence checks
  • Consider for...of loops for very large arrays
  • Use reduce() to combine multiple operations
  • Avoid creating intermediate arrays when possible
javascript
// ❌ Inefficient: creates 2 intermediate arrays
const result = hugeArray
  .filter(x => x > 10)
  .filter(x => x < 100)
  .map(x => x * 2);

// ✓ Better: single reduce
const result = hugeArray.reduce((acc, x) => {
  if (x > 10 && x < 100) {
    acc.push(x * 2);
  }
  return acc;
}, []);

// ✓ Or combine filters
const result = hugeArray
  .filter(x => x > 10 && x < 100)
  .map(x => x * 2);

When I'd use array methods (and when I'd reach for a loop)

For most application code (API responses, form data, lists you render) I default to filter, map, and friends. The arrays are small, the intent is obvious, and not mutating anything saves real debugging time.

I switch to a plain for or for...of loop when:

And whichever you pick, know which methods mutate. sort, reverse, and splice change the original array; forgetting that is one of the most common array bugs there is.

JavaScript Arrays Methods Functional Programming ES6+
Mayur Dabhi

Mayur Dabhi

Full Stack Developer with 5+ years of experience in Laravel, React, and modern web technologies. Passionate about writing clean code and sharing knowledge.