concat()

ES3+

Merges two or more arrays into a new array.

Syntax

array.concat(value1, value2, ...)

Parameters

values Array | any

Arrays or values to concatenate

Return Value

Array

A new array instance

Examples

JavaScript
const a = [1, 2];
const b = [3, 4];
const c = a.concat(b);
console.log(c);
Output:
// [1, 2, 3, 4]

📌 When to Use

Use concat() to merge arrays - or append loose values - into a fresh array while leaving every input untouched. Its immutability is what distinguishes it from push(...other): after a.concat(b), both a and b are exactly as they were, which makes concat() safe for combining data that other code still references, such as merging paginated API responses, appending defaults to user-supplied options, or joining results from several sources before sorting. In modern code the spread syntax [...a, ...b] covers the same ground and has become the dominant style, so the practical question is when concat() still earns its place. Three cases: it accepts a mix of arrays and single values in one call (arr.concat(x, more, y)) with each array argument flattened exactly one level; it works without knowing arity, so list.concat(...chunks) or a reduce over unknown inputs stays tidy; and it never risks the stack-overflow limits that spreading enormous arrays as individual function arguments can hit in some engines. Avoid concat() as an accumulator inside loops or reducers - each call copies everything accumulated so far, turning linear work quadratic; collect with push() and copy once at the end instead.

⚠️ Common Mistakes

Calling concat() and discarding the result out of habit from push(): arr.concat(newItems) as a standalone statement changes nothing, because concat() never mutates - the merged array vanishes unassigned. Either capture the return value or, if in-place growth is genuinely wanted, use arr.push(...newItems).

Assuming the merged array is fully independent. concat() copies references, not contents: the new array is a distinct object, but the elements inside are shared with the originals, so mutating merged[0].total also changes the object still visible through the source array. Deep independence requires cloning the elements themselves.

Accumulating with result = result.concat(chunk) inside a loop or reducer. Every iteration copies the entire accumulated array before appending, so total work grows quadratically - 1,000 chunks of 100 items means roughly 5 million element copies instead of 100,000. Push into one array inside the loop, or flatten once at the end with flat().

Expecting deep flattening. concat() spreads its ARRAY arguments exactly one level: [1].concat([2, [3]]) gives [1, 2, [3]], with the inner array preserved as-is. Nested structures need flat() with an appropriate depth, not more concat() calls.

Forgetting that a string is not an array here: arr.concat("abc") appends the single string "abc", it does not spread the characters. This differs from the spread operator, where [...arr, ..."abc"] splits the string into "a", "b", "c" because strings are iterable - a subtle behavioral gap between the two merging styles.

✅ Best Practices

Default to spread syntax [...a, ...b] in modern codebases for consistency with object spread and destructuring, but switch to a.concat(b) when merging arrays too large to spread safely (spreading passes every element as a function argument, which can overflow engine argument limits around one hundred thousand elements) or when the number of arrays is dynamic.

Exploit the mixed-argument form when assembling a list from heterogeneous pieces: base.concat(extraArray, singleItem, anotherArray) interleaves arrays and scalars in one readable call, where the spread equivalent needs careful bracket placement to avoid accidentally nesting the scalar.

Use concat() with no arguments, arr.concat(), as an alternative shallow-copy idiom equivalent to arr.slice() - handy in code that already uses concat() nearby, keeping the copying vocabulary uniform within a module.

To merge an unknown number of arrays collected in a list, prefer [].concat(...chunks) or chunks.flat() over a reduce with concat - both perform the merge in a single pass and read as one operation instead of a fold.

When merging produces duplicates you do not want, compose with Set in one expression: [...new Set(a.concat(b))] merges and deduplicates primitives in linear time, a common need when combining tag lists or id sets from multiple sources.

⚡ Performance Notes

A single concat() is O(n + m) - every element reference from every input is copied into the freshly allocated result - and V8 implements it with fast paths for packed arrays that make it competitive with, and sometimes faster than, the spread operator for large inputs, since spread must run the full iterator protocol unless the engine can prove it unnecessary. The performance cliff is repetition: concatenating k arrays pairwise in sequence copies early elements k times, so building a big list by looped concat() is O(n^2) while pushing into one array or calling flat() once stays linear. Memory behaves the same way - each intermediate result is garbage the collector must sweep. Also relevant at scale: because concat() copies only references, merging arrays of large objects is cheap per element, but merging then discarding many intermediates still churns the heap. For a hot path assembling one array from many chunks, [].concat(...chunks) or a single preallocated push loop is the efficient shape.

🌍 Real World Example

Combining Search Results from Multiple APIs

Federated search - querying several backends and presenting one combined list - is a natural concat() scenario because each source returns its own array and none of them should be mutated: the per-source arrays might still be needed for labeling results by origin or for per-source error handling. Chaining concat() twice merges all three in reading order, and the spread version below it is the character-for-character equivalent most modern teams would write. Either way the merge copies only references, so even large result objects merge cheaply. A realistic next step would be deduplicating by URL or id and sorting by relevance score - both of which operate happily on the merged copy without disturbing the originals.

const googleResults = await searchGoogle(query);
const bingResults = await searchBing(query);
const localResults = searchLocalDB(query);

// Combine all results (concat doesn't modify originals)
const allResults = googleResults
  .concat(bingResults)
  .concat(localResults);

// Modern equivalent with spread:
const allResults2 = [...googleResults, ...bingResults, ...localResults];

Related Methods