Array.prototype.concat() verbindet das Original-Array mit beliebig vielen weiteren Arrays oder Einzelwerten und liefert ein neues Array zurück — das Original bleibt unangetastet. Damit ist concat() eine der wenigen Array-Methoden, die immutable arbeiten, ohne dass man sich darum kümmern muss. Allerdings erzeugt sie nur eine flache Kopie: verschachtelte Arrays und Objekte werden per Referenz übernommen. Der Spread-Operator [...a, ...b] deckt seit ES2015 fast alle concat-Use-Cases mit kürzerer Syntax ab — concat bleibt aber für spezielle Fälle (Iterables, Symbol.isConcatSpreadable) und in defensivem Code weiterhin sinnvoll.

Signatur & Grundform

JavaScript signatur.js
arr.concat(...werte: (T | T[])[]): T[]

Beliebig viele Argumente, jedes entweder ein einzelner Wert oder ein Array. Arrays werden flach gespreaded, Werte einfach angehängt. Rückgabe ist ein neues Array.

JavaScript grundform.js
const a = [1, 2, 3];
const b = [4, 5];

const c = a.concat(b);
console.log(c);       // [1, 2, 3, 4, 5]
console.log(a);       // [1, 2, 3] — unverändert
console.log(c === a); // false — neue Referenz

// Mehrere Argumente
const d = [].concat([1, 2], 3, [4], 'fünf');
console.log(d);       // [1, 2, 3, 4, 'fünf']
Output
[ 1, 2, 3, 4, 5 ]
[ 1, 2, 3 ]
false
[ 1, 2, 3, 4, 'fünf' ]

Flache Kopie — Referenzen werden geteilt

concat kopiert nur die oberste Ebene. Verschachtelte Objekte und Arrays bleiben dieselben Referenzen.

JavaScript flache-kopie.js
const inner = { wert: 1 };
const a = [inner];
const b = [{ wert: 2 }];

const c = a.concat(b);
console.log(c);              // [{wert: 1}, {wert: 2}]

// Mutation am inneren Objekt von a propagiert in c
inner.wert = 99;
console.log(c[0]);           // { wert: 99 }
console.log(c[0] === inner); // true — gleiche Referenz
Output
[ { wert: 1 }, { wert: 2 } ]
{ wert: 99 }
true

Für eine echte Deep-Copy: structuredClone(arr) (ES2022) oder JSON.parse(JSON.stringify(arr)) (mit den bekannten JSON-Beschränkungen).

Spread-Operator als moderne Alternative

Seit ES2015 erreicht [...a, ...b] fast jedes concat-Resultat — kürzer und ohne Method-Call.

JavaScript spread-alternative.js
const a = [1, 2];
const b = [3, 4];

// Klassisch
const klassisch = a.concat(b);

// Spread
const spread = [...a, ...b];

// Mit Werten dazwischen
const gemischt = [0, ...a, 'mitte', ...b, 'ende'];

console.log(klassisch);
console.log(spread);
console.log(gemischt);
Output
[ 1, 2, 3, 4 ]
[ 1, 2, 3, 4 ]
[ 0, 1, 2, 'mitte', 3, 4, 'ende' ]

Subtiler Unterschied: Spread nutzt das Iterator-Protokoll — also iterable Werte wie Set, Map, Strings, Generators werden entpackt. concat flat-spreaded nur echte Arrays.

JavaScript spread-vs-concat-iter.js
const set = new Set([1, 2, 3]);

// concat sieht Set als Einzelwert
console.log([0].concat(set));    // [0, Set(3) { 1, 2, 3 }]

// Spread iteriert Set
console.log([0, ...set]);        // [0, 1, 2, 3]
Output
[ 0, Set(3) { 1, 2, 3 } ]
[ 0, 1, 2, 3 ]

Für die meisten Use-Cases ist Spread die idiomatischere Wahl. concat bleibt nützlich, wenn man explizit zwischen „spreaden" und „als Einzelwert anhängen" unterscheiden will.

Symbol.isConcatSpreadable — Spread-Verhalten steuern

concat prüft beim Argument, ob es spreadable ist. Default-Regel: echte Arrays werden spreaded, andere Objekte nicht. Mit Symbol.isConcatSpreadable lässt sich das überschreiben.

JavaScript is-concat-spreadable.js
// Array NICHT spreaden — als Einzelwert anhängen
const arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;

console.log([0].concat(arr));    // [0, [1, 2, 3]] — Array als Element

// Array-like spreaden
const arrayLike = { 0: 'a', 1: 'b', length: 2, [Symbol.isConcatSpreadable]: true };
console.log([].concat(arrayLike)); // ['a', 'b']
Output
[ 0, [ 1, 2, 3 ] ]
[ [ 1, 2, 3 ] ]   ← oder ['a', 'b'] je nach Engine-Version

In Produktiv-Code praktisch nie genutzt — aber gut zu wissen, dass die Sprache diese Flexibilität bietet.

Primitive Werte direkt anfügen

concat kann Strings, Numbers, Booleans direkt als Element anfügen, ohne dass man sie in ein Array packen muss.

JavaScript primitives.js
const start = ['a', 'b'];
const erweitert = start.concat('c', 1, true, null, undefined);
console.log(erweitert);
Output
[ 'a', 'b', 'c', 1, true, null, undefined ]

Gleiches mit Spread bräuchte explizite Wert-Auflistung — kein Spread auf Primitives, weil sie nicht iterable sind (außer Strings).

Sparse Arrays — Lücken bleiben Lücken

concat erhält die sparse-Eigenschaft: Lücken im Original bleiben Lücken im Ergebnis.

JavaScript sparse.js
const sparse = [1, , 3];
console.log(sparse.length);             // 3
console.log(1 in sparse);               // false

const c = sparse.concat([4, 5]);
console.log(c);                         // [1, <empty>, 3, 4, 5]
console.log(1 in c);                    // false — Loch wandert mit

// Spread füllt mit undefined
const s = [...sparse, 4, 5];
console.log(s);                         // [1, undefined, 3, 4, 5]
console.log(1 in s);                    // true — kein Loch mehr
Output
3
false
[ 1, <1 empty item>, 3, 4, 5 ]
false
[ 1, undefined, 3, 4, 5 ]
true

Das ist ein subtiler, aber realer Unterschied. Wer mit sparse Arrays arbeitet (z.B. aus new Array(n)), muss zwischen den beiden Formen bewusst wählen.

Performance — concat vs. Spread

Bei kleinen Arrays unterscheiden sich beide Formen kaum. Bei sehr großen Arrays gilt grob:

  • concat: V8 hat es lange optimiert; einmaliger Aufruf liefert das Ergebnis-Array in einem Rutsch.
  • Spread [...a, ...b]: pro Spread eine Iteration; bei N Argumenten N Iterationen.

In Benchmarks zeigt sich bei massiven Arrays (>10.000 Elemente) concat oft minimal schneller. In normalem Anwendungs-Code unmessbar — Lesbarkeit gewinnt. Für Performance-kritische Hot-Loops mit großen Arrays bleibt concat einen Hauch im Vorteil.

flat() und flatMap ersetzen Reduce-mit-concat

Vor flat/flatMap (ES2019) war reduce mit concat ein klassisches Pattern zum Flachklopfen:

JavaScript reduce-concat.js
const nested = [[1, 2], [3, 4], [5]];

// Alt: reduce + concat
const flat1 = nested.reduce((acc, arr) => acc.concat(arr), []);

// ES2019: flat()
const flat2 = nested.flat();

// ES2019: flatMap (für Map + Flatten in einem)
const verdoppelt = nested.flatMap(arr => arr.map(x => x * 2));

console.log(flat1);
console.log(flat2);
console.log(verdoppelt);
Output
[ 1, 2, 3, 4, 5 ]
[ 1, 2, 3, 4, 5 ]
[ 2, 4, 6, 8, 10 ]

In neuem Code: flat()/flatMap() direkt nutzen — deutlich schneller (kein quadratisches Re-Allocate wie bei reduce+concat) und lesbarer.

Welche Form nehmen?

SituationEmpfehlung
Zwei Arrays + ein paar EinzelwerteSpread: [...a, ...b, x, y]
Nur ein dynamisches Argument (Set, Iterable)Spread: [...iterable]
Iteratoren / Generators einbindenSpread (concat sähe sie als Einzelwert)
Sparse-Eigenschaft erhaltenconcat
Symbol.isConcatSpreadable nötigconcat
Massive Arrays, Performance-kritischconcat (minimaler Vorteil)
Verschachtelte Arrays flach klopfenflat() / flatMap()

Besonderheiten

concat ist immutable — eines der wenigen Array-Methoden

Anders als push, pop, splice, reverse, sort verändert concat das Original nicht. Es war Pre-ES2015 oft die einzige Methode, ein Array immutable zu erweitern, ohne den ganzen slice+push-Tanz. Heute teilt es sich diese Rolle mit Spread und mit den ES2023-„by-Copy"-Methoden.

Flache Kopie — verschachtelte Refs teilen sich

[[1]].concat([[2]]) liefert ein neues äußeres Array, aber die inneren Arrays sind dieselben Referenzen. Wer Deep-Copy braucht: structuredClone(arr) oder rekursive Custom-Implementierung.

concat spreaded nur ECHTE Arrays — Iterables werden als Einzelwert angehängt

[].concat(new Set([1, 2])) liefert [Set(2) {1, 2}] — das Set ist ein Element, nicht spreaded. Mit Spread [...new Set([1, 2])] bekommt man [1, 2]. Das unterscheidet die beiden Operationen fundamental.

Symbol.isConcatSpreadable steuert das Verhalten

Eine Property mit Schlüssel Symbol.isConcatSpreadable bestimmt, ob concat ein Argument spreadet. true für Array-likes, false auf echten Arrays. Selten gesehen — aber praktisch für Custom-Container, die sich „array-artig" verhalten sollen.

Sparse vs. dense — concat behält Löcher, Spread füllt mit undefined

[1, , 3].concat() behält das Loch (in-Operator zeigt false). [...[1, , 3]] füllt es mit undefined. In modernem Code irrelevant, weil sparse Arrays unüblich sind — bei Legacy-Code aber relevant.

reduce + concat war das alte flat()-Pattern — heute Anti-Pattern

arr.reduce((acc, x) => acc.concat(x), []) war Pre-2019 das Flachklopf-Idiom. Heute durch arr.flat() ersetzt — schneller (keine O(n²)-Allokation) und lesbarer. Ältere Codebases enthalten das Pattern noch reichlich.

Performance: concat marginal schneller als Spread bei großen Arrays

V8 hat concat seit Jahren als Special-Case optimiert. Bei massiven Arrays (>10.000 Elemente) zeigt sich ein leichter Vorteil gegenüber Spread, weil concat in einem Allocation-Schritt das Ergebnis baut. In normalem Code irrelevant.

concat() ohne Argumente liefert eine flache Kopie

arr.concat() ohne Argumente ist äquivalent zu arr.slice() — beide liefern eine flache Kopie. Konvention: slice() ist semantisch klarer, wenn man kopieren will. concat() wird typischerweise mit mindestens einem Argument aufgerufen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Arrays

Zur Übersicht