JavaScript Array Methods: The Ultimate Reference Guide
Master JavaScript array methods like map, filter, reduce, and more. Complete guide with examples and performance considerations.
JavaScript arrays are one of the most versatile data structures in the language, and mastering array methods is essential for writing clean, efficient, and functional code. Modern JavaScript provides a rich set of array methods that can help you manipulate data with ease and elegance.
In this comprehensive guide, we'll explore all the essential array methods, their use cases, performance considerations, and real-world examples that will make you an array manipulation expert.
Array Basics Refresher
Before diving into methods, let's quickly review array fundamentals:
// Creating arrays
const numbers = [1, 2, 3, 4, 5]
const mixed = [1, 'hello', true, null, { name: 'John' }]
const fromConstructor = new Array(5) // Creates array with 5 empty slots
const fromArrayOf = Array.of(1, 2, 3) // [1, 2, 3]
const fromArrayFrom = Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
// Array properties
console.log(numbers.length) // 5
console.log(Array.isArray(numbers)) // true
Iteration Methods
forEach() - Execute Function for Each Element
const fruits = ['apple', 'banana', 'orange']
// Basic usage
fruits.forEach((fruit, index, array) => {
console.log(`${index}: ${fruit}`)
})
// Practical example: Update DOM elements
const listItems = document.querySelectorAll('.item')
listItems.forEach((item, index) => {
item.textContent = `Item ${index + 1}`
item.addEventListener('click', () => handleItemClick(index))
})
// Note: forEach doesn't return anything and can't be broken out of
map() - Transform Each Element
const numbers = [1, 2, 3, 4, 5]
// Basic transformation
const doubled = numbers.map((num) => num * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
// Working with objects
const users = [
{ id: 1, name: 'John', age: 25 },
{ id: 2, name: 'Jane', age: 30 },
{ id: 3, name: 'Bob', age: 35 },
]
const userNames = users.map((user) => user.name)
console.log(userNames) // ['John', 'Jane', 'Bob']
// Creating new objects
const usersWithStatus = users.map((user) => ({
...user,
status: user.age >= 30 ? 'senior' : 'junior',
}))
// Chaining with other methods
const processedData = users
.map((user) => ({ ...user, age: user.age + 1 }))
.map((user) => ({ ...user, canVote: user.age >= 18 }))
filter() - Select Elements Based on Condition
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Basic filtering
const evenNumbers = numbers.filter((num) => num % 2 === 0)
console.log(evenNumbers) // [2, 4, 6, 8, 10]
// Complex filtering with objects
const products = [
{ name: 'Laptop', price: 999, category: 'Electronics', inStock: true },
{ name: 'Book', price: 25, category: 'Education', inStock: false },
{ name: 'Phone', price: 699, category: 'Electronics', inStock: true },
{ name: 'Desk', price: 200, category: 'Furniture', inStock: true },
]
// Multiple conditions
const availableElectronics = products.filter(
(product) =>
product.category === 'Electronics' &&
product.inStock &&
product.price < 800,
)
// Using filter with index
const firstHalfItems = products.filter(
(product, index) => index < products.length / 2,
)
// Removing duplicates (with primitive values)
const numbersWithDuplicates = [1, 2, 2, 3, 3, 3, 4]
const uniqueNumbers = numbersWithDuplicates.filter(
(num, index, array) => array.indexOf(num) === index,
)
reduce() - Accumulate Values
const numbers = [1, 2, 3, 4, 5]
// Basic sum
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0)
console.log(sum) // 15
// Finding maximum
const max = numbers.reduce((max, current) => (current > max ? current : max))
// More complex reductions
const transactions = [
{ type: 'deposit', amount: 100 },
{ type: 'withdrawal', amount: 30 },
{ type: 'deposit', amount: 50 },
{ type: 'withdrawal', amount: 20 },
]
// Calculate balance
const balance = transactions.reduce((total, transaction) => {
return transaction.type === 'deposit'
? total + transaction.amount
: total - transaction.amount
}, 0)
// Group by property
const groupedTransactions = transactions.reduce((groups, transaction) => {
const { type } = transaction
if (!groups[type]) {
groups[type] = []
}
groups[type].push(transaction)
return groups
}, {})
// Count occurrences
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const wordCount = words.reduce((count, word) => {
count[word] = (count[word] || 0) + 1
return count
}, {})
// Flattening arrays
const nestedArrays = [
[1, 2],
[3, 4],
[5, 6],
]
const flattened = nestedArrays.reduce(
(flat, current) => flat.concat(current),
[],
)
// Or use flat(): nestedArrays.flat()
Search and Test Methods
find() and findIndex()
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' },
{ id: 3, name: 'Bob', email: 'bob@example.com' },
]
// Find first matching element
const user = users.find((user) => user.id === 2)
console.log(user) // { id: 2, name: 'Jane', email: 'jane@example.com' }
// Find index of first matching element
const userIndex = users.findIndex((user) => user.name === 'Bob')
console.log(userIndex) // 2
// Return undefined if not found
const nonExistent = users.find((user) => user.id === 999)
console.log(nonExistent) // undefined
// Practical example: Form validation
function validateForm(fields, rules) {
const invalidField = fields.find((field) => {
const rule = rules[field.name]
return rule && !rule.validator(field.value)
})
return invalidField ? invalidField.name : null
}
includes(), some(), and every()
const numbers = [1, 2, 3, 4, 5]
const users = [
{ name: 'John', age: 25, active: true },
{ name: 'Jane', age: 30, active: true },
{ name: 'Bob', age: 35, active: false },
]
// Check if array includes a value
console.log(numbers.includes(3)) // true
console.log(numbers.includes(6)) // false
// Check if some elements match condition
const hasAdult = users.some((user) => user.age >= 30)
console.log(hasAdult) // true
const hasInactiveUser = users.some((user) => !user.active)
console.log(hasInactiveUser) // true
// Check if all elements match condition
const allActive = users.every((user) => user.active)
console.log(allActive) // false
const allAdults = users.every((user) => user.age >= 18)
console.log(allAdults) // true
// Practical example: Permissions check
function hasRequiredPermissions(userPermissions, requiredPermissions) {
return requiredPermissions.every((permission) =>
userPermissions.includes(permission),
)
}
function hasAnyPermission(userPermissions, permissions) {
return permissions.some((permission) => userPermissions.includes(permission))
}
Modification Methods
push(), pop(), shift(), unshift()
const fruits = ['apple', 'banana']
// Add to end
fruits.push('orange')
console.log(fruits) // ['apple', 'banana', 'orange']
// Add multiple to end
fruits.push('grape', 'kiwi')
console.log(fruits) // ['apple', 'banana', 'orange', 'grape', 'kiwi']
// Remove from end
const lastFruit = fruits.pop()
console.log(lastFruit) // 'kiwi'
console.log(fruits) // ['apple', 'banana', 'orange', 'grape']
// Add to beginning
fruits.unshift('mango')
console.log(fruits) // ['mango', 'apple', 'banana', 'orange', 'grape']
// Remove from beginning
const firstFruit = fruits.shift()
console.log(firstFruit) // 'mango'
console.log(fruits) // ['apple', 'banana', 'orange', 'grape']
// Performance note: push/pop are faster than unshift/shift
splice() - Swiss Army Knife Method
const numbers = [1, 2, 3, 4, 5, 6, 7, 8]
// Remove elements (start, deleteCount)
const removed = numbers.splice(2, 3) // Remove 3 elements starting at index 2
console.log(removed) // [3, 4, 5]
console.log(numbers) // [1, 2, 6, 7, 8]
// Insert elements (start, deleteCount, ...items)
numbers.splice(2, 0, 'a', 'b') // Insert at index 2, delete 0
console.log(numbers) // [1, 2, 'a', 'b', 6, 7, 8]
// Replace elements
numbers.splice(2, 2, 3, 4, 5) // Replace 2 elements starting at index 2
console.log(numbers) // [1, 2, 3, 4, 5, 6, 7, 8]
// Practical examples
function removeById(array, id) {
const index = array.findIndex((item) => item.id === id)
if (index !== -1) {
return array.splice(index, 1)[0]
}
return null
}
function insertAtPosition(array, position, item) {
array.splice(position, 0, item)
return array
}
slice() - Non-Mutating Extraction
const numbers = [1, 2, 3, 4, 5, 6, 7, 8]
// Extract portion (start, end) - end not included
const portion = numbers.slice(2, 5)
console.log(portion) // [3, 4, 5]
console.log(numbers) // Original unchanged: [1, 2, 3, 4, 5, 6, 7, 8]
// From start index to end
const fromIndex = numbers.slice(3)
console.log(fromIndex) // [4, 5, 6, 7, 8]
// Negative indices (from end)
const lastThree = numbers.slice(-3)
console.log(lastThree) // [6, 7, 8]
// Copy array (shallow copy)
const copy = numbers.slice()
// Practical examples
function paginate(array, page, size) {
const start = (page - 1) * size
const end = start + size
return array.slice(start, end)
}
function getLastN(array, n) {
return array.slice(-n)
}
ES6+ Array Methods
flat() and flatMap()
// Flatten nested arrays
const nested = [1, [2, 3], [4, [5, 6]]]
console.log(nested.flat()) // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)) // [1, 2, 3, 4, 5, 6] (depth 2)
console.log(nested.flat(Infinity)) // Flatten all levels
// flatMap combines map and flat(1)
const sentences = ['Hello world', 'How are you']
const words = sentences.flatMap((sentence) => sentence.split(' '))
console.log(words) // ['Hello', 'world', 'How', 'are', 'you']
// Practical example: Processing nested data
const users = [
{ name: 'John', hobbies: ['reading', 'gaming'] },
{ name: 'Jane', hobbies: ['painting', 'cooking', 'hiking'] },
]
const allHobbies = users.flatMap((user) => user.hobbies)
console.log(allHobbies) // ['reading', 'gaming', 'painting', 'cooking', 'hiking']
ES2022+ Array Methods
// at() - Access elements with negative indices
const fruits = ['apple', 'banana', 'orange', 'grape']
// Traditional way
console.log(fruits[fruits.length - 1]) // 'grape'
// Using at() method
console.log(fruits.at(-1)) // 'grape'
console.log(fruits.at(-2)) // 'orange'
console.log(fruits.at(0)) // 'apple'
// findLast() and findLastIndex()
const numbers = [1, 2, 3, 4, 3, 2, 1]
const lastEven = numbers.findLast((num) => num % 2 === 0)
console.log(lastEven) // 2
const lastEvenIndex = numbers.findLastIndex((num) => num % 2 === 0)
console.log(lastEvenIndex) // 5
// Immutable array methods (ES2023) - don't modify original array
const originalArray = [3, 1, 4, 1, 5]
const sorted = originalArray.toSorted() // [1, 1, 3, 4, 5]
const reversed = originalArray.toReversed() // [5, 1, 4, 1, 3]
const spliced = originalArray.toSpliced(1, 2, 'new') // [3, 'new', 1, 5]
const withReplacement = originalArray.with(0, 'first') // ['first', 1, 4, 1, 5]
console.log(originalArray) // [3, 1, 4, 1, 5] (unchanged!)
Array.from() and Array.of()
// Array.from() - Create array from iterable or array-like object
const string = 'hello'
const chars = Array.from(string)
console.log(chars) // ['h', 'e', 'l', 'l', 'o']
// With mapping function
const numbers = Array.from({ length: 5 }, (_, index) => index + 1)
console.log(numbers) // [1, 2, 3, 4, 5]
// From Set or Map
const uniqueNumbers = Array.from(new Set([1, 2, 2, 3, 3, 4]))
console.log(uniqueNumbers) // [1, 2, 3, 4]
// Array.of() - Create array from arguments
const singleElement = Array.of(5) // [5] (not empty array of length 5)
const multipleElements = Array.of(1, 2, 3) // [1, 2, 3]
// Practical examples
function createRange(start, end, step = 1) {
const length = Math.ceil((end - start) / step)
return Array.from({ length }, (_, i) => start + i * step)
}
function convertNodeListToArray(nodeList) {
return Array.from(nodeList)
}
Advanced Patterns and Techniques
Method Chaining
const sales = [
{ product: 'Laptop', amount: 1200, quarter: 'Q1' },
{ product: 'Phone', amount: 800, quarter: 'Q1' },
{ product: 'Tablet', amount: 600, quarter: 'Q2' },
{ product: 'Laptop', amount: 1100, quarter: 'Q2' },
{ product: 'Phone', amount: 750, quarter: 'Q3' },
]
// Complex data processing chain
const laptopSalesQ1Q2 = sales
.filter((sale) => sale.product === 'Laptop')
.filter((sale) => ['Q1', 'Q2'].includes(sale.quarter))
.map((sale) => ({ ...sale, discounted: sale.amount * 0.9 }))
.reduce((total, sale) => total + sale.discounted, 0)
console.log(laptopSalesQ1Q2) // 2070
// Building a data processing pipeline
class DataProcessor {
constructor(data) {
this.data = data
}
filter(predicate) {
this.data = this.data.filter(predicate)
return this
}
map(transform) {
this.data = this.data.map(transform)
return this
}
sort(compareFn) {
this.data = this.data.sort(compareFn)
return this
}
take(count) {
this.data = this.data.slice(0, count)
return this
}
result() {
return this.data
}
}
// Usage
const topExpensiveElectronics = new DataProcessor(products)
.filter((p) => p.category === 'Electronics')
.filter((p) => p.inStock)
.sort((a, b) => b.price - a.price)
.take(3)
.result()
Working with Async Operations
// Sequential processing
async function processArraySequentially(items, asyncProcessor) {
const results = []
for (const item of items) {
const result = await asyncProcessor(item)
results.push(result)
}
return results
}
// Parallel processing
async function processArrayInParallel(items, asyncProcessor) {
const promises = items.map((item) => asyncProcessor(item))
return Promise.all(promises)
}
// Controlled concurrency
async function processWithConcurrency(items, asyncProcessor, concurrency = 3) {
const results = []
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency)
const batchResults = await Promise.all(
batch.map((item) => asyncProcessor(item)),
)
results.push(...batchResults)
}
return results
}
// Filter async
async function filterAsync(array, asyncPredicate) {
const results = await Promise.all(
array.map(async (item) => ({
item,
keep: await asyncPredicate(item),
})),
)
return results.filter(({ keep }) => keep).map(({ item }) => item)
}
// Example usage
const urls = ['url1', 'url2', 'url3']
const validUrls = await filterAsync(urls, async (url) => {
try {
const response = await fetch(url)
return response.ok
} catch {
return false
}
})
Performance Considerations
// Large array performance tips
// 1. Use appropriate method for the task
const largeArray = new Array(1000000).fill(0).map((_, i) => i)
// Bad: Using find when you need to check existence
console.time('find')
const found = largeArray.find((x) => x === 999999)
console.timeEnd('find')
// Good: Using includes for simple value check
console.time('includes')
const exists = largeArray.includes(999999)
console.timeEnd('includes')
// 2. Break early when possible
function findFirstEven(numbers) {
for (const num of numbers) {
if (num % 2 === 0) {
return num // Break early
}
}
return null
}
// 3. Avoid creating intermediate arrays when not needed
// Bad: Multiple passes
const result1 = largeArray
.map((x) => x * 2)
.filter((x) => x > 100)
.reduce((sum, x) => sum + x, 0)
// Better: Single pass with reduce
const result2 = largeArray.reduce((sum, x) => {
const doubled = x * 2
return doubled > 100 ? sum + doubled : sum
}, 0)
// 4. Use for...of for better performance in some cases
function sumArray(numbers) {
let sum = 0
for (const num of numbers) {
sum += num
}
return sum
}
Utility Functions
// Array utilities
const ArrayUtils = {
// Remove duplicates
unique(array) {
return [...new Set(array)]
},
// Remove duplicates by property
uniqueBy(array, key) {
const seen = new Set()
return array.filter((item) => {
const value = typeof key === 'function' ? key(item) : item[key]
if (seen.has(value)) {
return false
}
seen.add(value)
return true
})
},
// Group by property
groupBy(array, key) {
return array.reduce((groups, item) => {
const value = typeof key === 'function' ? key(item) : item[key]
;(groups[value] = groups[value] || []).push(item)
return groups
}, {})
},
// Chunk array into smaller arrays
chunk(array, size) {
const chunks = []
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size))
}
return chunks
},
// Shuffle array
shuffle(array) {
const shuffled = [...array]
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
}
return shuffled
},
// Partition array based on predicate
partition(array, predicate) {
const truthy = []
const falsy = []
array.forEach((item) => {
;(predicate(item) ? truthy : falsy).push(item)
})
return [truthy, falsy]
},
}
// Usage examples
const numbers = [1, 2, 2, 3, 3, 4, 5]
console.log(ArrayUtils.unique(numbers)) // [1, 2, 3, 4, 5]
const users = [
{ name: 'John', department: 'IT' },
{ name: 'Jane', department: 'HR' },
{ name: 'Bob', department: 'IT' },
]
const byDepartment = ArrayUtils.groupBy(users, 'department')
const [itUsers, otherUsers] = ArrayUtils.partition(
users,
(user) => user.department === 'IT',
)
Conclusion
JavaScript array methods are incredibly powerful tools for data manipulation and functional programming. By mastering these methods, you can write more expressive, readable, and efficient code.
Key takeaways:
- Choose the right method for your specific use case
- Understand the difference between mutating and non-mutating methods
- Leverage method chaining for complex data transformations
- Consider performance implications with large datasets
- Use ES6+ methods like
flat()
,flatMap()
,at()
, and immutable methods for cleaner code - Build reusable utility functions for common operations
Practice these methods with real-world data, and you'll find that complex data manipulations become much more intuitive and enjoyable. Arrays are fundamental to JavaScript development, and these methods are your toolkit for working with them effectively.
Happy coding! 🚀