Getting started General
Components
Forms
Trends
Utilities
Plugins Sass Migrate from v1
  Join us
  JavaScript Articles

6 Ways to Merge Arrays Together in JavaScript: Complete Guide

JavaScript

After reading this article, you will be able to merge arrays in JavaScript with 6 different methods. Learn concat, spread operator, push, and more with performance comparisons and best practices.

6 Ways to Merge Arrays Together in JavaScript: Complete Guide
6 Ways to Merge Arrays Together in JavaScript: Complete Guide

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)

  1. push() with spread: Fastest for large arrays (no new array creation)
  2. concat(): Good performance, consistent across browsers
  3. spread operator: Fast but can be slower with very large arrays
  4. flat(): Good for array of arrays, slower for simple merging
  5. 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

  1. For small arrays (< 1000 elements): Use spread operator for readability
  2. For large arrays (> 10000 elements): Use push() with spread for performance
  3. For mixed data types: Use concat() for safety
  4. 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.

Start Building with Axentix

Ready to create amazing websites? Get started with Axentix framework today.

Get Started

Related Posts