Merging arrays is one of the most common operations in JavaScript development. Whether you're combining data from
multiple sources, building dynamic lists, or managing state in your applications, understanding the different ways
to merge arrays is crucial. This comprehensive guide explores 6 different methods to merge arrays in JavaScript,
from the traditional concat()
method to modern
approaches like the spread operator, along with performance considerations and best practices for each method.
Understanding Array Merging
Array merging in JavaScript involves combining two or more arrays into a single array. The key distinction is between methods that create a new array (immutable) versus those that modify the existing array (mutable). Understanding this difference is crucial for writing predictable and maintainable code.
Before diving into the methods, let's establish some common scenarios where array merging is essential:
- Combining data from multiple API responses
- Building dynamic lists in React or Vue applications
- Managing state updates in Redux or similar state management libraries
- Processing and aggregating data from different sources
- Creating comprehensive search results from multiple datasets
Method 1: Array.concat() - The Traditional Approach
The concat()
method is
the most traditional and widely supported way to merge arrays. It creates a new array containing the elements from
the original array followed by the elements from the additional arrays or values.
Basic Usage
const fruits = ['🍎', '🍌'];
const vegetables = ['🥕', '🥬'];
const combined = fruits.concat(vegetables);
console.log(combined); // ['🍎', '🍌', '🥕', '🥬']
console.log(fruits); // ['🍎', '🍌'] - original array unchanged
console.log(vegetables); // ['🥕', '🥬'] - original array unchanged
Merging Multiple Arrays
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const array3 = [7, 8, 9];
// Method 1: Chaining concat calls
const result1 = array1.concat(array2).concat(array3);
// Method 2: Passing multiple arguments
const result2 = array1.concat(array2, array3);
// Method 3: Using empty array as base
const result3 = [].concat(array1, array2, array3);
console.log(result1); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(result2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(result3); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Handling Non-Array Values
One of the key advantages of concat()
is its ability to
handle non-array values gracefully:
const numbers = [1, 2, 3];
const string = 'hello';
const number = 42;
const result = numbers.concat(string, number);
console.log(result); // [1, 2, 3, 'hello', 42]
Advantages:
- Excellent browser support (works in all browsers)
- Handles non-array values gracefully
- Creates a new array (immutable)
- Clear and readable syntax
Disadvantages:
- Can be verbose when merging many arrays
- Slightly slower than some modern alternatives
Method 2: Spread Operator (...) - The Modern Approach
The spread operator, introduced in ES6, provides a concise and elegant way to merge arrays. It expands an iterable (like an array) into individual elements.
Basic Usage
const fruits = ['🍎', '🍌'];
const vegetables = ['🥕', '🥬'];
const combined = [...fruits, ...vegetables];
console.log(combined); // ['🍎', '🍌', '🥕', '🥬']
console.log(fruits); // ['🍎', '🍌'] - original array unchanged
console.log(vegetables); // ['🥕', '🥬'] - original array unchanged
Merging Multiple Arrays
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const array3 = [7, 8, 9];
const combined = [...array1, ...array2, ...array3];
console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Adding Individual Elements
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5, 6];
console.log(moreNumbers); // [1, 2, 3, 4, 5, 6]
// Adding elements at the beginning
const evenMoreNumbers = [0, ...numbers, 4];
console.log(evenMoreNumbers); // [0, 1, 2, 3, 4]
Important Limitation: Non-Array Values
Unlike concat()
, the
spread operator requires all values to be iterable:
const numbers = [1, 2, 3];
const string = 'hello';
// This works - string is iterable
const result1 = [...numbers, ...string];
console.log(result1); // [1, 2, 3, 'h', 'e', 'l', 'l', 'o']
// This will throw an error
const number = 42;
// const result2 = [...numbers, ...number]; // TypeError: number is not iterable
Advantages:
- Concise and readable syntax
- Excellent performance
- Creates a new array (immutable)
- Flexible for adding individual elements
Disadvantages:
- Requires ES6+ support
- All values must be iterable
- Can be confusing with non-array values
Method 3: Array.push() with Spread - The Mutable Approach
The push()
method
modifies the original array by adding elements to the end. When combined with the spread operator, it can merge
arrays efficiently.
Basic Usage
const fruits = ['🍎', '🍌'];
const vegetables = ['🥕', '🥬'];
fruits.push(...vegetables);
console.log(fruits); // ['🍎', '🍌', '🥕', '🥬'] - original array modified
console.log(vegetables); // ['🥕', '🥬'] - unchanged
Merging Multiple Arrays
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const array3 = [7, 8, 9];
array1.push(...array2, ...array3);
console.log(array1); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Performance Considerations
push()
with spread can
be very efficient for large arrays because it doesn't create a new array:
// Efficient for large arrays
const largeArray = new Array(1000000).fill(0);
const additionalData = [1, 2, 3, 4, 5];
// This modifies the original array without creating a new one
largeArray.push(...additionalData);
Advantages:
- Very efficient for large arrays
- No new array creation
- Simple syntax
- Good performance
Disadvantages:
- Modifies the original array (mutable)
- Can lead to unexpected side effects
- Not suitable for functional programming patterns
Method 4: Array.from() with concat() - The Functional Approach
Array.from()
combined
with concat()
provides
a functional programming approach to array merging.
Basic Usage
const arrays = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const merged = Array.from(arrays).reduce((acc, curr) => acc.concat(curr), []);
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Using Array.from() with Spread
const arrays = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const merged = Array.from(arrays).reduce((acc, curr) => [...acc, ...curr], []);
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Advantages:
- Functional programming approach
- Works well with array of arrays
- Creates a new array (immutable)
Disadvantages:
- More complex syntax
- Can be slower for simple cases
- Less readable than other methods
Method 5: Array.flat() - The Flattening Approach
Array.flat()
is perfect
when you have an array of arrays and want to flatten it into a single array.
Basic Usage
const arrays = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const flattened = arrays.flat();
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Handling Nested Arrays
const nestedArrays = [
[1, 2, [3, 4]],
[5, 6, [7, 8]],
[9, 10]
];
// Flatten one level
const oneLevel = nestedArrays.flat();
console.log(oneLevel); // [1, 2, [3, 4], 5, 6, [7, 8], 9, 10]
// Flatten completely
const completelyFlat = nestedArrays.flat(Infinity);
console.log(completelyFlat); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Advantages:
- Perfect for array of arrays
- Handles nested structures
- Simple and readable
- Creates a new array (immutable)
Disadvantages:
- Requires ES2019+ support
- Only works with array of arrays
- Can flatten too much if not careful
Method 6: Custom Utility Function - The Flexible Approach
Sometimes you need a custom solution that handles specific requirements or edge cases. Creating a utility function gives you complete control over the merging process.
Basic Utility Function
function mergeArrays(...arrays) {
return arrays.reduce((acc, curr) => {
if (Array.isArray(curr)) {
return acc.concat(curr);
} else {
return acc.concat([curr]);
}
}, []);
}
const result = mergeArrays([1, 2], [3, 4], 5, [6, 7]);
console.log(result); // [1, 2, 3, 4, 5, 6, 7]
Advanced Utility with Options
function mergeArrays(options = {}) {
const {
arrays = [],
unique = false,
filter = null,
sort = false
} = options;
let result = arrays.reduce((acc, curr) => {
if (Array.isArray(curr)) {
return acc.concat(curr);
} else {
return acc.concat([curr]);
}
}, []);
if (filter) {
result = result.filter(filter);
}
if (unique) {
result = [...new Set(result)];
}
if (sort) {
result = result.sort();
}
return result;
}
// Usage examples
const arrays = [[1, 2, 3], [2, 3, 4], [3, 4, 5]];
// Basic merge
const basic = mergeArrays({ arrays });
console.log(basic); // [1, 2, 3, 2, 3, 4, 3, 4, 5]
// Merge with unique values
const unique = mergeArrays({ arrays, unique: true });
console.log(unique); // [1, 2, 3, 4, 5]
// Merge with filtering
const filtered = mergeArrays({
arrays,
filter: x => x > 2
});
console.log(filtered); // [3, 3, 4, 3, 4, 5]
Advantages:
- Complete control over the process
- Can handle complex requirements
- Reusable across projects
- Can include additional logic
Disadvantages:
- Requires more code
- Need to maintain the function
- Can be overkill for simple cases
Performance Comparison
Understanding the performance characteristics of each method is crucial for choosing the right approach, especially when dealing with large datasets.
Performance Test Results
// Performance test function
function performanceTest(method, arrays, iterations = 1000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
method(arrays);
}
const end = performance.now();
return end - start;
}
// Test data
const largeArray1 = new Array(10000).fill(0).map((_, i) => i);
const largeArray2 = new Array(10000).fill(0).map((_, i) => i + 10000);
// Test methods
const methods = {
concat: (arrays) => arrays[0].concat(arrays[1]),
spread: (arrays) => [...arrays[0], ...arrays[1]],
push: (arrays) => {
const result = [...arrays[0]];
result.push(...arrays[1]);
return result;
},
flat: (arrays) => [arrays[0], arrays[1]].flat()
};
// Run tests
const testArrays = [largeArray1, largeArray2];
const results = {};
for (const [name, method] of Object.entries(methods)) {
results[name] = performanceTest(method, testArrays);
}
console.log('Performance Results (ms):', results);
Performance Rankings (Typical Results)
- push() with spread: Fastest for large arrays (no new array creation)
- concat(): Good performance, consistent across browsers
- spread operator: Fast but can be slower with very large arrays
- flat(): Good for array of arrays, slower for simple merging
- Custom functions: Performance depends on implementation
Common Issues and Troubleshooting
Issue 1: Spread Operator with Non-Iterable Values
Problem: Trying to spread non-iterable values causes errors.
const numbers = [1, 2, 3];
const notAnArray = 42;
// This will throw an error
// const result = [...numbers, ...notAnArray]; // TypeError: 42 is not iterable
Solution: Check if the value is iterable or use concat() instead.
const numbers = [1, 2, 3];
const notAnArray = 42;
// Safe approach with concat
const result = numbers.concat(notAnArray);
console.log(result); // [1, 2, 3, 42]
// Or check if iterable first
const safeResult = [...numbers, ...(Array.isArray(notAnArray) ? notAnArray : [notAnArray])];
console.log(safeResult); // [1, 2, 3, 42]
Issue 2: Unexpected Mutation with push()
Problem: Using push() modifies the original array, causing unexpected side effects.
const originalArray = [1, 2, 3];
const additionalData = [4, 5, 6];
originalArray.push(...additionalData);
console.log(originalArray); // [1, 2, 3, 4, 5, 6] - modified!
// This can cause bugs if you expected originalArray to remain unchanged
Solution: Create a copy first or use immutable methods.
const originalArray = [1, 2, 3];
const additionalData = [4, 5, 6];
// Solution 1: Create a copy first
const result1 = [...originalArray];
result1.push(...additionalData);
// Solution 2: Use immutable methods
const result2 = [...originalArray, ...additionalData];
console.log(originalArray); // [1, 2, 3] - unchanged
console.log(result1); // [1, 2, 3, 4, 5, 6]
console.log(result2); // [1, 2, 3, 4, 5, 6]
Issue 3: Memory Issues with Large Arrays
Problem: Creating new arrays with large datasets can cause memory issues.
// This can cause memory issues with very large arrays
const hugeArray1 = new Array(1000000).fill(0);
const hugeArray2 = new Array(1000000).fill(1);
// Creates a new array with 2 million elements
const merged = [...hugeArray1, ...hugeArray2];
Solution: Use push() for large arrays or process in chunks.
const hugeArray1 = new Array(1000000).fill(0);
const hugeArray2 = new Array(1000000).fill(1);
// More memory efficient
hugeArray1.push(...hugeArray2);
// Or process in chunks for very large datasets
function mergeInChunks(array1, array2, chunkSize = 10000) {
for (let i = 0; i < array2.length; i += chunkSize) {
const chunk = array2.slice(i, i + chunkSize);
array1.push(...chunk);
}
return array1;
}
Real-World Examples
Example 1: Merging API Responses
// Simulating API responses
async function fetchUserData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
// Merge all data into a single array
const allData = [...users, ...posts, ...comments];
return allData;
}
Example 2: React State Management
// React component with state merging
function TodoList() {
const [todos, setTodos] = useState([]);
const [newTodos, setNewTodos] = useState([]);
const addNewTodos = () => {
// Immutable update using spread operator
setTodos(prevTodos => [...prevTodos, ...newTodos]);
setNewTodos([]);
};
const addSingleTodo = (todo) => {
// Adding single item
setTodos(prevTodos => [...prevTodos, todo]);
};
return (
// Component JSX
);
}
Example 3: Data Processing Pipeline
function processData(rawData) {
// Step 1: Merge data from different sources
const mergedData = rawData.reduce((acc, source) => {
return acc.concat(source.data);
}, []);
// Step 2: Filter and transform
const processedData = mergedData
.filter(item => item.active)
.map(item => ({
...item,
processedAt: new Date()
}));
// Step 3: Remove duplicates
const uniqueData = [...new Set(processedData.map(item => item.id))]
.map(id => processedData.find(item => item.id === id));
return uniqueData;
}
Best Practices and Recommendations
When to Use Each Method
- Use concat(): When you need maximum browser compatibility or are dealing with mixed data types
- Use spread operator: For modern codebases with ES6+ support and clean, readable syntax
- Use push() with spread: When performance is critical and you can safely mutate the original array
- Use flat(): When you have an array of arrays and want to flatten them
- Use custom functions: When you need complex logic or specific requirements
Performance Guidelines
- For small arrays (< 1000 elements): Use spread operator for readability
- For large arrays (> 10000 elements): Use push() with spread for performance
- For mixed data types: Use concat() for safety
- For array of arrays: Use flat() for simplicity
Code Quality Tips
- Always consider whether you need a new array or can modify the existing one
- Use consistent methods across your codebase
- Add comments for complex merging logic
- Test with edge cases (empty arrays, null values, etc.)
- Consider using TypeScript for better type safety
Browser Compatibility
Understanding browser support is crucial for choosing the right method for your project:
Support Matrix
- concat(): All browsers (IE5.5+)
- spread operator: Modern browsers (Chrome 46+, Firefox 16+, Safari 8+)
- push(): All browsers (IE5.5+)
- flat(): Modern browsers (Chrome 69+, Firefox 62+, Safari 12+)
- Array.from(): Modern browsers (Chrome 45+, Firefox 32+, Safari 9+)
Polyfills and Fallbacks
// Polyfill for flat() if needed
if (!Array.prototype.flat) {
Array.prototype.flat = function(depth = 1) {
return this.reduce((acc, val) => {
if (Array.isArray(val) && depth > 0) {
acc.push(...val.flat(depth - 1));
} else {
acc.push(val);
}
return acc;
}, []);
};
}
// Safe spread operator usage
function safeSpread(...arrays) {
return arrays.reduce((acc, curr) => {
if (Array.isArray(curr)) {
return [...acc, ...curr];
} else {
return [...acc, curr];
}
}, []);
}
Conclusion
Merging arrays in JavaScript is a fundamental operation that every developer should master. Each of the 6 methods
we've explored has its own strengths and use cases. The concat()
method remains the
most reliable and widely supported approach, while the spread operator offers modern syntax and excellent
performance for most use cases.
The key to choosing the right method lies in understanding your specific requirements: Do you need to preserve the original array? Are you working with large datasets? Do you need to handle mixed data types? By considering these factors and following the best practices outlined in this guide, you can write efficient, maintainable code that handles array merging effectively.
Remember that performance considerations become more important as your data grows, and always test your chosen method with realistic data sizes. Whether you're building a simple web application or a complex data processing system, the right array merging technique will help you write cleaner, more efficient JavaScript code.
As JavaScript continues to evolve, new methods and optimizations will emerge, but the fundamental principles of array manipulation remain constant. By mastering these 6 methods, you'll be well-equipped to handle any array merging challenge that comes your way.