JavaScript kennt drei klassische Schleifen-Formen, die fast unverändert aus C übernommen sind: for mit Init/Bedingung/Update in einem Header, while mit Vor-Test und do/while mit Nach-Test. Daneben gibt es die iterator-basierten Formen for...of und for...in (eigener Artikel) sowie funktionale Methoden wie forEach, map und reduce. Dieser Artikel konzentriert sich auf die drei klassischen Formen — wo jede ihre Stärke hat, welche Fallen typisch sind und warum sie trotz moderner Alternativen weiter eine Rolle spielen.

Die for-Schleife — Zähler-Klassiker

Der for-Header hat drei Teile, getrennt durch Semikolons: Init, Bedingung, Update. Init läuft einmal vor dem ersten Durchlauf, Bedingung wird vor jedem Durchlauf geprüft, Update läuft nach jedem Durchlauf.

JavaScript for-grundform.js
for (let i = 0; i < 3; i++) {
    console.log('Index', i);
}
Output
Index 0
Index 1
Index 2

Jeder der drei Teile ist optional. for (;;) ist die kanonische Endlos-Schleife — der häufigste Anwendungsfall ist eine Schleife, die nur per break von innen verlassen wird.

JavaScript for-optional-teile.js
// Endlos-Schleife mit break als Exit
let n = 0;
for (;;) {
    n++;
    if (n >= 3) break;
}
console.log('n =', n); // 3

// Init außen, Update außen — for nur als Bedingungs-Wrapper
let i = 0;
for (; i < 3; ) {
    console.log(i);
    i++;
}
Output
n = 3
0
1
2

let im Init-Teil ist seit ES2015 das idiomatische Default — die Zählvariable ist auf den Schleifen-Block beschränkt, jeder Durchlauf bekommt eine neue Bindung. Das macht Closures innerhalb der Schleife sicher (siehe Falle weiter unten).

Mehrere Variablen im for-Header

Init und Update dürfen Komma-Listen sein — mehrere Variablen, mehrere Updates.

JavaScript for-multi.js
// Zwei Zeiger, einer von vorn, einer von hinten
const arr = [1, 2, 3, 4, 5];

for (let i = 0, j = arr.length - 1; i < j; i++, j--) {
    console.log(arr[i], '↔', arr[j]);
}
Output
1 ↔ 5
2 ↔ 4

Das ist das klassische Two-Pointer-Pattern — bei Palindrom-Checks, sortierten Arrays und ähnlichen Aufgaben. In funktionalen Pendants gibt es so etwas nicht direkt; daher bleibt for für diese Fälle die erste Wahl.

Die while-Schleife — kopfgesteuert

while (bed) { ... } prüft die Bedingung vor jedem Durchlauf. Wenn die Bedingung initial falsy ist, läuft die Schleife null Mal.

JavaScript while-grundform.js
let zeichen = '';
while (zeichen.length < 5) {
    zeichen += '*';
    console.log(zeichen);
}
Output
*
**
***
****
*****

while ist die richtige Wahl, wenn die Anzahl der Durchläufe nicht im voraus bekannt ist — etwa beim Lesen eines Streams, beim Pollen einer Bedingung oder beim Verarbeiten einer Queue.

JavaScript while-queue.js
const queue = [1, 2, 3];

while (queue.length > 0) {
    const item = queue.shift();
    console.log('Bearbeite', item);
    // ggf. weitere Items in queue.push(...) — Schleife läuft weiter
    if (item === 2) queue.push(99);
}
Output
Bearbeite 1
Bearbeite 2
Bearbeite 3
Bearbeite 99

Eine for-Schleife mit arr.length-Bedingung wäre hier irreführend, weil length sich während der Schleife ändert. while (queue.length > 0) macht das explizit.

Die do/while-Schleife — fußgesteuert

Bei do { ... } while (bed); läuft der Block mindestens einmal — die Bedingung wird erst nach dem Durchlauf geprüft.

JavaScript do-while.js
let eingabe;
let versuch = 0;

// Simulierte Eingaben — in echtem Code käme das z.B. von prompt()
const stub = ['', '', 'antwort'];

do {
    eingabe = stub[versuch];
    versuch++;
    console.log('Versuch', versuch, ':', JSON.stringify(eingabe));
} while (eingabe === '');

console.log('Erhalten:', eingabe);
Output
Versuch 1 : ""
Versuch 2 : ""
Versuch 3 : "antwort"
Erhalten: antwort

do/while ist in der Praxis selten — der Anwendungsfall „mindestens ein Durchlauf, dann re-checken" ist nicht so häufig. Wenn er auftritt, ist do/while aber die direkteste Form. Wichtig: das Semikolon nach while(...) ist Pflicht.

Endlos-Schleifen — vermeiden oder bewusst nutzen

Eine Schleife mit konstant truthy Bedingung läuft endlos. In Node oder Browser blockiert das den Event-Loop — die Page friert ein, der Tab muss beendet werden.

JavaScript endlos-falle.js
// BUG: Schleife endet nie, weil i nie inkrementiert
let i = 0;
while (i < 10) {
    console.log(i);
    // i++ vergessen → Endlos
}

// BUG: Bedingung kann nie falsy werden
for (let n = 1; n > 0; n++) {
    // n wird positiv und immer größer — niemals <= 0
}
// außer am MAX_SAFE_INTEGER-Übergang — nach 9 Billiarden Iterationen

Bewusste Endlos-Schleifen sind in Worker- oder Daemon-Code legitim — etwa eine Event-Pump in einem Worker-Thread. Voraussetzung: ein zuverlässiger Exit-Pfad (break, return, throw).

JavaScript endlos-bewusst.js
function ersteFreiePort(start) {
    for (let port = start; ; port++) {
        if (port > 65535) throw new Error('Kein Port frei');
        if (istFrei(port)) return port;
    }
}

function istFrei(port) { return port === 8085; } // Demo-Stub
console.log(ersteFreiePort(8080)); // 8085
Output
8085

Die Closure-Falle mit var

Vor ES2015 — also vor let — war die berühmte „alle Callbacks loggen denselben Wert"-Falle Standard.

JavaScript closure-var-falle.js
// Pre-ES2015 — alle Callbacks teilen dasselbe i
const callbacks = [];
for (var i = 0; i < 3; i++) {
    callbacks.push(() => console.log(i));
}
callbacks.forEach(cb => cb());
// → 3, 3, 3 — weil i nach der Schleife 3 ist
Output
3
3
3

Mit let ist das gelöst — jeder Durchlauf bekommt eine neue Bindung:

JavaScript closure-let-fix.js
const callbacks = [];
for (let i = 0; i < 3; i++) {
    callbacks.push(() => console.log(i));
}
callbacks.forEach(cb => cb());
// → 0, 1, 2 — jeder Callback hat sein eigenes i
Output
0
1
2

Das ist einer der pragmatischsten Gründe, var heute komplett zu vermeiden — die Closure-Semantik in Schleifen ist mit let einfach „richtig".

break und continue — Kurzform

Schleifen werden mit break vorzeitig verlassen und mit continue zum nächsten Durchlauf gesprungen. Detail-Behandlung im Artikel zu Labels — hier nur die Grundform.

JavaScript break-continue.js
// break: Schleife ganz verlassen
for (let i = 0; i < 10; i++) {
    if (i === 3) break;
    console.log('A', i);
}
// → A 0, A 1, A 2

// continue: zum nächsten Durchlauf springen
for (let i = 0; i < 5; i++) {
    if (i % 2 === 0) continue;
    console.log('B', i);
}
// → B 1, B 3
Output
A 0
A 1
A 2
B 1
B 3

continue in for-Schleifen springt zum Update-Teil — nicht zur Bedingung direkt. Daher bleiben Zähler-Schleifen mit continue korrekt.

Wann welche Schleife?

Faustregeln:

  • for mit Zähler: bekannte Anzahl, Index-Zugriff nötig, Two-Pointer-Pattern, performance-kritisch.
  • for...of: iterierbare Sammlung (Array, String, Map, Set, Iterable), kein Index nötig — sieht eigener Artikel.
  • Array-Methoden (map, filter, reduce, forEach): wenn das Ergebnis ein neues Array oder ein aggregierter Wert ist.
  • while: Anzahl der Durchläufe nicht im voraus bekannt, Bedingung ändert sich durch Side-Effects.
  • do/while: mindestens ein Durchlauf garantiert nötig, danach Bedingungs-Check.
JavaScript wahl-beispiele.js
// Klassisch for: Index nötig
const arr = ['a', 'b', 'c'];
for (let i = 0; i < arr.length; i++) {
    console.log(`[${i}] ${arr[i]}`);
}

// Modern for...of: Wert genügt
for (const c of arr) {
    console.log(c);
}

// Array-Methode: neues Array
const groß = arr.map(c => c.toUpperCase());
console.log(groß);
Output
[0] a
[1] b
[2] c
a
b
c
[ 'A', 'B', 'C' ]

Performance — meistens egal

Die klassische for-Schleife mit lokalem Index ist üblicherweise die schnellste Schleifen-Form, weil die Engine den Code aggressiv optimieren kann. forEach ist marginal langsamer (Function-Call-Overhead pro Element). for...of liegt dazwischen.

Praktisch ist der Unterschied bei modernen Engines im einstelligen Prozent-Bereich und nur bei Hunderttausenden Iterationen messbar. Wer also Lesbarkeit zugunsten von 3 % Performance opfert, optimiert am falschen Ende.

Echte Performance-Tipps liegen woanders: Allokationen in der Schleife reduzieren (Array nicht neu erzeugen), DOM-Zugriffe bündeln, Funktion-Inlining vermeiden bei monomorphen Hot-Paths. Schleifen-Form ist sekundär.

Interessantes

let in for-Init: jeder Durchlauf eine neue Bindung

Das ist eine Sonder-Regel der Sprache, nicht eine Konsequenz allgemeiner Scope-Regeln. for (let i = 0; ...; i++) erzeugt pro Durchlauf eine neue Closure-Bindung von i — daher funktionieren Callbacks innerhalb der Schleife wie erwartet. var i erzeugt nur eine einzige Bindung im umgebenden Function-Scope.

for-Header darf leer sein: for (;;) ist gültig

Alle drei Teile sind optional, die Semikolons aber nicht. for (;;) ist die kanonische Endlos-Schleife — kürzer als while (true) und in vielen Style-Guides bevorzugt, weil sie die Absicht klarer signalisiert.

Komma-Operator im for-Header — mehrere Variablen, ein Header

for (let i = 0, j = 10; ...; i++, j--) nutzt den Komma-Operator. Das ist einer der wenigen Stellen, an denen er sinnvoll ist. Wichtig: die Init-Variablen müssen denselben let- bzw. const-Modus teilen — gemischte Deklarationen sind nicht möglich.

do/while braucht Semikolon nach der Bedingung

do { ... } while (bed); — das abschließende Semikolon ist Teil der Syntax, nicht eine ASI-Konvention. Ohne Semikolon ist es zwar oft noch parsbar, aber manche Tools (insbesondere Minifier) hatten historisch Schwierigkeiten.

while (true) vs. for (;;) — keine Performance-Differenz

Beide werden von modernen Engines identisch optimiert. Die Wahl ist rein stilistisch. Manche Style-Guides bevorzugen for (;;) als „bewusste Endlos-Schleife", andere while (true) als „lesbarer".

continue in for springt zum Update, nicht zur Bedingung

Wichtiges Detail: for (let i = 0; i < 10; i++) { if (...) continue; ... } führt nach continue das i++ aus, dann die Bedingung. In while springt continue direkt zur Bedingung — daher muss man den Counter dort manuell vorher inkrementieren.

Schleifen-Init mit const: für const-Iteration nicht möglich

for (const i = 0; i < 3; i++) wirft TypeError: Assignment to constant variable beim ersten i++. const im for-Init ist nur sinnvoll, wenn der Update-Teil leer bleibt und die Variable im Body nicht reassigned wird — selten der Fall in Zähler-Schleifen. const funktioniert dagegen einwandfrei in for...of und for...in.

Endlos-Schleifen blockieren den Event-Loop in Browser und Node

JavaScript ist single-threaded — eine Endlos-Schleife im Main-Thread blockiert alles: UI, Timer, Promise-Callbacks. Browser zeigen nach einigen Sekunden „Skript reagiert nicht" und bieten Tab-Schließen an. Worker-Threads können dagegen sicher in Endlos-Loops sitzen, weil sie isoliert laufen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Kontrollstrukturen

Zur Übersicht