Array Methods
Iterate and modify arrays efficiently using built-in methods like map, filter, reduce, find, some, every, and slice.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is the difference between map(), filter(), and forEach()?
| Method | Returns | Purpose | Mutates original? |
|---|---|---|---|
| map() | New array, same length | Transform each element | No |
| filter() | New array, possibly shorter | Keep elements matching a condition | No |
| forEach() | undefined | Run side effects per element (logging, DOM updates) | No (but callback can mutate) |
const numbers = [1, 2, 3, 4, 5];
// map - transforms every element, same array length
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - keeps elements passing a test, length can shrink
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// forEach - just iterates, no new array is created
const result = numbers.forEach(n => console.log(n * 2));
console.log(result); // undefined - forEach always returns undefined
// Common mistake: using forEach when you need a new array
const wrong = numbers.forEach(n => n * 2); // WRONG - returns undefined, not the doubled array
console.log(wrong); // undefined
// Correct: use map instead
const correct = numbers.map(n => n * 2);
console.log(correct); // [2, 4, 6, 8, 10]
// Chaining map and filter
const result2 = numbers
.filter(n => n % 2 === 0)
.map(n => n * 10);
console.log(result2); // [20, 40]
Q2. How does the reduce() method work and what can it be used for?
reduce() executes a reducer function on each element, accumulating a single result. It's the most powerful and flexible array method - map, filter, and even forEach can all be implemented using it.
// Basic syntax: array.reduce((accumulator, currentValue) => {...}, initialValue)
const numbers = [1, 2, 3, 4, 5];
// Sum all numbers
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15
// Find the maximum value
const max = numbers.reduce((acc, curr) => (curr > acc ? curr : acc), numbers[0]);
console.log(max); // 5
// Count occurrences - building an object
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, orange: 1 }
// Grouping objects by a property
const people = [
{ name: 'John', dept: 'IT' },
{ name: 'Jane', dept: 'HR' },
{ name: 'Bob', dept: 'IT' }
];
const grouped = people.reduce((acc, person) => {
(acc[person.dept] = acc[person.dept] || []).push(person);
return acc;
}, {});
console.log(grouped); // { IT: [John, Bob], HR: [Jane] }
// Flattening a nested array
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5]
// Implementing map using reduce
const mapped = numbers.reduce((acc, n) => {
acc.push(n * 2);
return acc;
}, []);
console.log(mapped); // [2, 4, 6, 8, 10]
// Without an initial value - first element becomes the initial accumulator
const sumNoInit = numbers.reduce((acc, curr) => acc + curr);
console.log(sumNoInit); // 15 - works, but throws on empty arrays without initial value
Q3. What is the difference between find(), findIndex(), some(), and every()?
| Method | Returns | Stops early? |
|---|---|---|
| find() | The first matching element, or undefined | Yes, on first match |
| findIndex() | Index of first match, or -1 | Yes, on first match |
| some() | true if ANY element matches | Yes, on first true |
| every() | true only if ALL elements match | Yes, on first false |
const users = [
{ id: 1, name: 'John', active: true },
{ id: 2, name: 'Jane', active: false },
{ id: 3, name: 'Bob', active: true }
];
// find - returns the actual element
const inactiveUser = users.find(u => !u.active);
console.log(inactiveUser); // { id: 2, name: 'Jane', active: false }
const notFound = users.find(u => u.id === 99);
console.log(notFound); // undefined
// findIndex - returns the position
const index = users.findIndex(u => u.name === 'Bob');
console.log(index); // 2
console.log(users.findIndex(u => u.name === 'Nobody')); // -1
// some - true if AT LEAST ONE matches (like logical OR across the array)
const hasInactive = users.some(u => !u.active);
console.log(hasInactive); // true
// every - true only if ALL match (like logical AND across the array)
const allActive = users.every(u => u.active);
console.log(allActive); // false - Jane is inactive
// Practical use: validating a form's fields
const fields = [{ valid: true }, { valid: true }, { valid: false }];
const formIsValid = fields.every(f => f.valid);
console.log(formIsValid); // false
// Practical use: checking permission - does user have ANY admin role?
const roles = ['editor', 'viewer'];
const isAdmin = roles.some(role => role === 'admin');
console.log(isAdmin); // false
// Performance note: all four short-circuit, stopping as soon as the answer is known
// - useful for large arrays where you don't need to process every element
Q4. What is the difference between slice() and splice()?
| Method | Mutates original? | Returns | Purpose |
|---|---|---|---|
| slice(start, end) | No | New array (shallow copy of a portion) | Extract a section without changing original |
| splice(start, count, ...items) | Yes | Array of removed elements | Remove/insert/replace elements in place |
const arr = ['a', 'b', 'c', 'd', 'e'];
// SLICE - non-mutating, extracts a portion
const sliced = arr.slice(1, 3); // start index 1, up to (not including) 3
console.log(sliced); // ['b', 'c']
console.log(arr); // ['a', 'b', 'c', 'd', 'e'] - unchanged
// Negative indices count from the end
console.log(arr.slice(-2)); // ['d', 'e']
// Common use: copying an array
const copy = arr.slice();
console.log(copy === arr); // false - different array, same values
// SPLICE - mutates the original array
const arr2 = ['a', 'b', 'c', 'd', 'e'];
// Removing elements: splice(startIndex, deleteCount)
const removed = arr2.splice(1, 2); // remove 2 elements starting at index 1
console.log(removed); // ['b', 'c'] - the removed elements
console.log(arr2); // ['a', 'd', 'e'] - original is mutated!
// Inserting elements: splice(startIndex, 0, ...newItems)
const arr3 = ['a', 'b', 'e'];
arr3.splice(2, 0, 'c', 'd'); // insert without removing anything
console.log(arr3); // ['a', 'b', 'c', 'd', 'e']
// Replacing elements: splice(startIndex, deleteCount, ...newItems)
const arr4 = ['a', 'b', 'X', 'd'];
arr4.splice(2, 1, 'c'); // remove 1 element at index 2, insert 'c'
console.log(arr4); // ['a', 'b', 'c', 'd']
// Memory tip: sLice does NOT mutate (like 'slice a cake, keep the rest'),
// sPlice DOES mutate (splices/cuts the original wire)
Q5. How do you check if an array includes a value, and what's the difference between includes() and indexOf()?
| Method | Returns | Detects NaN? |
|---|---|---|
| includes() | Boolean (true/false) | Yes |
| indexOf() | Index number, or -1 | No - cannot find NaN |
const arr = [1, 2, 3, NaN, 5];
// includes - simple boolean check, more readable
console.log(arr.includes(3)); // true
console.log(arr.includes(99)); // false
// indexOf - returns position, -1 if not found
console.log(arr.indexOf(3)); // 2
console.log(arr.indexOf(99)); // -1
// Old pattern before includes() existed - checking with indexOf
if (arr.indexOf(3) !== -1) {
console.log('Found using indexOf');
}
// Modern, more readable equivalent
if (arr.includes(3)) {
console.log('Found using includes');
}
// KEY DIFFERENCE: NaN handling
console.log(arr.indexOf(NaN)); // -1 - indexOf uses === which NaN fails against itself
console.log(arr.includes(NaN)); // true - includes uses SameValueZero algorithm
// includes with a starting search position (2nd argument)
console.log([1, 2, 3, 2].includes(2, 2)); // true - searches from index 2 onward
// String includes() works similarly (unrelated to array, but same concept)
console.log('Hello World'.includes('World')); // true
Q6. How do sort() and reverse() work, and why should you be careful with sort()?
Both sort() and reverse() mutate the original array. sort() converts elements to strings by default, which produces incorrect results for numbers unless a compare function is provided.
// DEFAULT sort() - converts to strings, sorts lexicographically
const numbers = [10, 1, 21, 2];
console.log(numbers.sort());
// [1, 10, 2, 21] - WRONG for numeric sorting! '10' < '2' as strings
// CORRECT numeric sort - provide a compare function
const ascending = [10, 1, 21, 2].sort((a, b) => a - b);
console.log(ascending); // [1, 2, 10, 21]
const descending = [10, 1, 21, 2].sort((a, b) => b - a);
console.log(descending); // [21, 10, 2, 1]
// sort() MUTATES the original array
const original = [3, 1, 2];
const sorted = original.sort();
console.log(original === sorted); // true - same array reference!
console.log(original); // [1, 2, 3] - original changed
// Non-mutating sort using spread to copy first
const safeCopy = [...original].sort((a, b) => a - b);
// Sorting objects by a property
const users = [{ name: 'Charlie', age: 35 }, { name: 'Alice', age: 25 }];
users.sort((a, b) => a.age - b.age);
console.log(users); // Alice first (25), then Charlie (35)
// Sorting strings alphabetically (works correctly by default for strings)
const names = ['Charlie', 'Alice', 'Bob'];
names.sort();
console.log(names); // ['Alice', 'Bob', 'Charlie']
// reverse() also MUTATES the original
const arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1] - original mutated
// Non-mutating reverse
const reversedCopy = [...arr].reverse();
Q7. What are mutating vs non-mutating array methods, and why does the distinction matter?
Some array methods change the original array in place (mutating), while others return a new array leaving the original untouched (non-mutating). This matters greatly for predictable code and frameworks relying on immutability.
| Mutating (changes original) | Non-mutating (returns new) |
|---|---|
| push(), pop() | concat() |
| shift(), unshift() | slice() |
| splice() | map(), filter(), reduce() |
| sort(), reverse() | [...spread] |
| fill(), copyWithin() | toSorted(), toReversed() (ES2023) |
const original = [3, 1, 2];
// MUTATING example - push changes the original
function addItemMutating(arr, item) {
arr.push(item);
return arr;
}
const result1 = addItemMutating(original, 4);
console.log(original === result1); // true - same array!
console.log(original); // [3, 1, 2, 4] - original changed
// NON-MUTATING example - spread creates a new array
function addItemImmutable(arr, item) {
return [...arr, item];
}
const arr2 = [1, 2, 3];
const result2 = addItemImmutable(arr2, 4);
console.log(arr2 === result2); // false - different arrays
console.log(arr2); // [1, 2, 3] - unchanged
console.log(result2); // [1, 2, 3, 4]
// Why it matters: unexpected mutations cause hard-to-track bugs
function processOrders(orders) {
orders.sort((a, b) => a.total - b.total); // mutates the caller's array!
return orders.slice(0, 5);
}
// Caller's original 'orders' array order is now unexpectedly changed
// Safer version
function processOrdersSafe(orders) {
return [...orders].sort((a, b) => a.total - b.total).slice(0, 5);
}
// ES2023 added non-mutating alternatives to classic mutating methods
const arr3 = [3, 1, 2];
const sortedCopy = arr3.toSorted(); // new in ES2023, does not mutate arr3
console.log(arr3); // [3, 1, 2] - unchanged
console.log(sortedCopy); // [1, 2, 3]
Q8. How do you flatten nested arrays using flat() and flatMap()?
flat() reduces nesting depth into a single-level array, and flatMap() combines map() with a single-level flatten in one efficient pass.
// flat() - default depth of 1
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());
// [1, 2, 3, 4, [5, 6]] - only flattens ONE level deep
// flat(depth) - specify how deep to flatten
console.log(nested.flat(2));
// [1, 2, 3, 4, 5, 6] - flattens 2 levels
// flat(Infinity) - flatten ALL levels regardless of depth
const deeplyNested = [1, [2, [3, [4, [5]]]]];
console.log(deeplyNested.flat(Infinity));
// [1, 2, 3, 4, 5]
// flat() also removes empty slots in sparse arrays
const sparse = [1, , 3]; // hole at index 1
console.log(sparse.flat()); // [1, 3] - hole removed
// flatMap() - map + flatten(1) combined, more efficient than doing both separately
const sentences = ['Hello world', 'How are you'];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words); // ['Hello', 'world', 'How', 'are', 'you']
// Without flatMap - requires map then flat separately
const wordsOld = sentences.map(s => s.split(' ')).flat();
console.log(wordsOld); // same result, but two passes over the data
// Practical use: filtering AND transforming, returning multiple or zero items
const numbers = [1, 2, 3, 4, 5];
const doubledEvens = numbers.flatMap(n =>
n % 2 === 0 ? [n * 2] : [] // returning [] effectively filters out odd numbers
);
console.log(doubledEvens); // [4, 8]
Q9. How do you convert between arrays and other data structures (Array.from, Array.of, spread)?
JavaScript provides several utilities for creating arrays from array-like or iterable objects, which is common when working with DOM collections, strings, Sets, or Maps.
// Array.from - converts array-like or iterable objects into real arrays
const nodeList = document.querySelectorAll('div'); // NodeList, not a real array
const divsArray = Array.from(nodeList);
divsArray.map(div => div.textContent); // now array methods work
// Array.from with a string
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']
// Array.from with a Set (removing duplicates then converting)
const uniqueSet = new Set([1, 2, 2, 3]);
console.log(Array.from(uniqueSet)); // [1, 2, 3]
// Array.from with a mapping function (2nd argument)
const doubled = Array.from([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]
// Array.from to create a sequence of numbers
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range); // [0, 1, 2, 3, 4]
// Array.of - creates an array from arguments (avoids Array() constructor quirk)
console.log(Array.of(7)); // [7]
console.log(new Array(7)); // [ <7 empty items> ] - creates array of LENGTH 7, not [7]!
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
// Spread operator - similar to Array.from for iterables
const spreadFromSet = [...uniqueSet];
console.log(spreadFromSet); // [1, 2, 3]
const spreadFromString = [...'abc'];
console.log(spreadFromString); // ['a', 'b', 'c']
// Converting a Map to an array of entries
const map = new Map([['a', 1], ['b', 2]]);
console.log(Array.from(map)); // [['a', 1], ['b', 2]]
console.log([...map]); // same result
Q10. How do you chain multiple array methods together, and when should you avoid over-chaining?
Array methods that return arrays (map, filter) can be chained fluently, but each method in a chain iterates the array separately - long chains can hurt readability and performance on large datasets.
const products = [
{ name: 'Laptop', price: 1200, category: 'electronics', inStock: true },
{ name: 'Phone', price: 800, category: 'electronics', inStock: false },
{ name: 'Desk', price: 300, category: 'furniture', inStock: true },
{ name: 'Chair', price: 150, category: 'furniture', inStock: true }
];
// Chaining filter -> map -> sort for a readable pipeline
const result = products
.filter(p => p.inStock) // keep in-stock items
.map(p => ({ ...p, priceWithTax: p.price * 1.1 })) // add computed field
.sort((a, b) => a.priceWithTax - b.priceWithTax); // sort ascending
console.log(result);
// Each chained method creates a NEW array and iterates fully
// For a 10,000 item array chained 5 times, that's potentially 50,000 iterations
// PERFORMANCE CONCERN with long chains on large data
const largeArray = new Array(100000).fill(0).map((_, i) => i);
const chained = largeArray
.filter(n => n % 2 === 0) // pass 1: 100,000 iterations
.map(n => n * 2) // pass 2: 50,000 iterations
.filter(n => n > 1000); // pass 3: 50,000 iterations
// ALTERNATIVE - combine logic into a single reduce pass for large datasets
const singlePass = largeArray.reduce((acc, n) => {
if (n % 2 === 0) {
const doubled = n * 2;
if (doubled > 1000) acc.push(doubled);
}
return acc;
}, []);
// One iteration instead of three - better for performance-critical large datasets
// Readability guideline: chaining 2-3 methods is usually clearer than one giant reduce;
// beyond that, consider breaking into named intermediate variables for clarity
const inStockProducts = products.filter(p => p.inStock);
const withTax = inStockProducts.map(p => ({ ...p, priceWithTax: p.price * 1.1 }));
const sorted = withTax.sort((a, b) => a.priceWithTax - b.priceWithTax);
Array Methods
Iterate and modify arrays efficiently using built-in methods like map, filter, reduce, find, some, every, and slice.
What is the difference between map(), filter(), and forEach()?
MethodReturnsPurposeMutates original?map()New array, same lengthTransform each elementNofilter()New array, pos...
How does the reduce() method work and what can it be used for?
reduce() executes a reducer function on each element, accumulating a single result. It's the most powerful and...
What is the difference between find(), findIndex(), some(), and every()?
MethodReturnsStops early?find()The first matching element, or undefinedYes, on first matchfindIndex()Index of...
What is the difference between slice() and splice()?
MethodMutates original?ReturnsPurposeslice(start, end)NoNew array (shallow copy of a portion)Extract a section...
How do you check if an array includes a value, and what's the difference between includes() and indexOf()?
MethodReturnsDetects NaN?includes()Boolean (true/false)YesindexOf()Index number, or -1No - cannot find NaNcons...
How do sort() and reverse() work, and why should you be careful with sort()?
Both sort() and reverse() mutate the original array. sort() converts elements to strings by default, which pro...
What are mutating vs non-mutating array methods, and why does the distinction matter?
Some array methods change the original array in place (mutating), while others return a new array leaving the...
How do you flatten nested arrays using flat() and flatMap()?
flat() reduces nesting depth into a single-level array, and flatMap() combines map() with a single-level flatt...
How do you convert between arrays and other data structures (Array.from, Array.of, spread)?
JavaScript provides several utilities for creating arrays from array-like or iterable objects, which is common...
How do you chain multiple array methods together, and when should you avoid over-chaining?
Array methods that return arrays (map, filter) can be chained fluently, but each method in a chain iterates th...