Loops
TypeScript inherits JavaScript’s iteration surface: the C-style for, the while, the do-while, and the iterator-based for...of and for...in. The principal contemporary forms are for...of (over iterables) and the array methods (forEach, map, filter, reduce). The combination — explicit for for index-based loops, for...of for iterables, array methods for transformations — covers the routine iteration surface; the conventional discipline favours array methods for substantial transformations and explicit loops for control-heavy iteration.
The C-style for
for (let i = 0; i < 10; i++) {
console.log(i);
}
The form: for (init; cond; update) { body }. Each part is optional but the semicolons are required.
// Forever:
for (;;) {
if (done) break;
}
// Reverse:
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]);
}
// Step by 2:
for (let i = 0; i < 100; i += 2) {
console.log(i);
}
// Multiple variables:
for (let i = 0, j = arr.length - 1; i < j; i++, j--) {
[arr[i], arr[j]] = [arr[j], arr[i]]; // swap
}
The conventional discipline uses the C-style form when explicit indexed access is needed; otherwise for...of or array methods are conventional.
for...of
The conventional iteration over iterables:
const arr = [10, 20, 30];
for (const x of arr) {
console.log(x); // 10, 20, 30
}
// Strings:
for (const c of "hello") {
console.log(c); // "h", "e", "l", "l", "o"
}
// Maps and Sets:
const set = new Set([1, 2, 3]);
for (const x of set) {
console.log(x);
}
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
console.log(key, value);
}
The form admits iterating any iterable — a value with a [Symbol.iterator]() method.
For index and value, entries:
for (const [i, x] of arr.entries()) {
console.log(`[${i}] = ${x}`);
}
for...in
Iterates over the enumerable property keys of an object — including inherited keys:
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key, obj[key]);
}
A common pitfall: for...in on arrays iterates over indexes as strings, including any added properties:
const arr = [10, 20, 30];
arr.foo = "bar"; // (in JS) admits adding properties
for (const i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
The conventional discipline avoids for...in for arrays; for...of is the conventional choice. For object keys, Object.keys(obj) plus for...of is conventionally clearer than for...in:
for (const key of Object.keys(obj)) {
console.log(key, obj[key]);
}
while and do...while
let n = 10;
while (n > 0) {
console.log(n);
n--;
}
let m = 0;
do {
console.log(m);
m++;
} while (m < 3);
The do...while runs the body at least once, then checks the condition. Conventional uses are polling and read-then-test patterns; the while-with-break form is conventional in TypeScript.
break and continue
for (let i = 0; i < 100; i++) {
if (i === 50) break; // exit
if (i % 2 === 0) continue; // skip
console.log(i);
}
The break exits the innermost loop; continue proceeds to the next iteration. For nested loops, labels admit targeting:
outer: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i * j > 50) break outer;
}
}
The label form is rarely used in idiomatic code; restructuring into a function with return is conventionally clearer.
Array methods
The conventional contemporary discipline favours array methods for transformations:
forEach
arr.forEach(x => console.log(x));
arr.forEach((x, i) => console.log(`[${i}] = ${x}`));
The forEach is not a transformation — it returns undefined. For genuine iteration with side effects, conventional; for value-producing transformations, the other methods are conventional.
A subtle limitation: forEach does not support early termination via break or return. The conventional alternatives are for...of or some / every:
// Doesn't break out of forEach:
arr.forEach(x => {
if (x > 100) return; // only "returns" from this callback
});
// Conventional alternative:
for (const x of arr) {
if (x > 100) break; // truly breaks
}
map
const doubled = arr.map(x => x * 2);
const lengths = strings.map(s => s.length);
const indexed = arr.map((x, i) => ({ index: i, value: x }));
The map produces a new array of the same length with each element transformed.
filter
const evens = arr.filter(x => x % 2 === 0);
const positives = nums.filter(x => x > 0);
The filter produces a new array containing only elements for which the predicate returns truthy.
reduce
const sum = arr.reduce((acc, x) => acc + x, 0);
const max = arr.reduce((m, x) => x > m ? x : m, -Infinity);
// Build an object:
const counts = words.reduce<Record<string, number>>((acc, w) => {
acc[w] = (acc[w] ?? 0) + 1;
return acc;
}, {});
The reduce accumulates over the array; the second argument is the initial value.
For reduceRight, the iteration is from end to start.
find, findIndex, findLast
const first = arr.find(x => x > 10); // first matching element (or undefined)
const idx = arr.findIndex(x => x > 10); // index (or -1)
const last = arr.findLast(x => x < 10); // last matching (ES2023)
some and every
const hasEven = arr.some(x => x % 2 === 0);
const allPositive = arr.every(x => x > 0);
The some short-circuits on the first true; every short-circuits on the first false.
flat and flatMap
const flat = [[1, 2], [3, 4]].flat(); // [1, 2, 3, 4]
const deep = [[[1]], [[2]]].flat(2); // [1, 2]
const result = arr.flatMap(x => [x, x * 2]);
// Equivalent to: arr.map(x => [x, x * 2]).flat()
includes, indexOf
arr.includes(10); // true / false
arr.indexOf(10); // index or -1
arr.lastIndexOf(10);
Sorting
const sorted = [...arr].sort(); // mutates a copy
arr.sort(); // mutates in place
arr.sort((a, b) => a - b); // ascending
arr.sort((a, b) => b - a); // descending
// Strings:
arr.sort((a, b) => a.localeCompare(b));
// By property:
people.sort((a, b) => a.age - b.age);
// Stable in modern engines (since ES2019):
people.sort((a, b) => a.age - b.age);
Method chaining
The conventional functional-style transformation:
const result = items
.filter(x => x.active)
.map(x => ({ id: x.id, name: x.name }))
.sort((a, b) => a.name.localeCompare(b.name));
The form is conventional for substantial pipelines.
Iteration over Map, Set, and other iterables
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) { /* ... */ }
for (const key of map.keys()) { /* ... */ }
for (const value of map.values()) { /* ... */ }
for (const entry of map.entries()) { /* ... */ }
const set = new Set([1, 2, 3]);
for (const x of set) { /* ... */ }
For Map, the iteration order is insertion order — predictable and stable.
for await...of
For async iterables:
async function readLines(stream: AsyncIterable<string>) {
for await (const line of stream) {
console.log(line);
}
}
Treated in Async and concurrency.
Common patterns
Iterate with index
for (const [i, x] of arr.entries()) {
console.log(`[${i}] = ${x}`);
}
// Or:
arr.forEach((x, i) => console.log(`[${i}] = ${x}`));
Range iteration
JavaScript has no built-in range; common patterns:
// Standard for loop:
for (let i = 0; i < 10; i++) { /* ... */ }
// Array.from with mapper:
const range = Array.from({ length: 10 }, (_, i) => i); // [0, 1, ..., 9]
for (const i of range) { /* ... */ }
// Array spread:
const range2 = [...Array(10).keys()];
// Generator (more elaborate):
function* range(n: number) { for (let i = 0; i < n; i++) yield i; }
for (const i of range(10)) { /* ... */ }
Filter and map combined
const result = items
.filter(x => x.active)
.map(x => x.name);
Counting
const counts = words.reduce<Record<string, number>>((acc, w) => {
acc[w] = (acc[w] ?? 0) + 1;
return acc;
}, {});
// Or with Map:
const counts = new Map<string, number>();
for (const w of words) {
counts.set(w, (counts.get(w) ?? 0) + 1);
}
Group by
const byCategory = items.reduce<Record<string, Item[]>>((acc, item) => {
(acc[item.category] ??= []).push(item);
return acc;
}, {});
// ES2024:
// const byCategory = Object.groupBy(items, item => item.category);
The Object.groupBy and Map.groupBy methods (ES2024) admit substantial conciseness.
Finding
const first = items.find(x => x.id === target);
const idx = items.findIndex(x => x.id === target);
const all = items.filter(x => x.tag === "important");
Sum, max, min
const sum = nums.reduce((a, b) => a + b, 0);
const max = nums.reduce((a, b) => (a > b ? a : b), -Infinity);
const min = Math.min(...nums); // works for short arrays
const max2 = Math.max(...nums);
For substantial arrays, the spread of Math.max may exceed argument limits; the reduce form is the conventional defence.
Iterating object entries
const obj = { a: 1, b: 2, c: 3 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} = ${value}`);
}
// Or with Object.keys + lookup:
for (const key of Object.keys(obj)) {
console.log(`${key} = ${obj[key as keyof typeof obj]}`);
}
Generators
The function* syntax produces a generator — an iterator-producing function:
function* fibonacci(): Generator<number> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const gen = fibonacci();
for (const f of gen) {
if (f > 100) break;
console.log(f); // 0, 1, 1, 2, 3, 5, 8, ...
}
// Take a finite slice:
const fibs = [];
const it = fibonacci();
for (let i = 0; i < 10; i++) {
fibs.push(it.next().value);
}
Generators admit substantial flexibility for lazy iteration and infinite sequences.
Custom iterables
class Range {
constructor(public start: number, public end: number) {}
*[Symbol.iterator](): Generator<number> {
for (let i = this.start; i < this.end; i++) {
yield i;
}
}
}
for (const x of new Range(0, 5)) {
console.log(x); // 0, 1, 2, 3, 4
}
const arr = [...new Range(0, 5)]; // [0, 1, 2, 3, 4]
The [Symbol.iterator] admits making any class iterable.
Iteration with break-equivalent
const result = items.find(x => x.matches); // returns first match or undefined
if (result) { /* ... */ }
// Or:
const result = items.some(x => {
if (x.matches) {
process(x);
return true; // breaks
}
return false;
});
Iterating Map entries
const map = new Map<string, number>();
// ... populate ...
for (const [key, value] of map.entries()) {
console.log(`${key} = ${value}`);
}
// Iterate sorted by key:
const sortedKeys = [...map.keys()].sort();
for (const key of sortedKeys) {
console.log(`${key} = ${map.get(key)}`);
}
A note on the conventional discipline
The contemporary TypeScript loops advice:
- Use
for...offor iterating arrays, Maps, Sets, strings, and custom iterables. - Use array methods (
map,filter,reduce,forEach) for transformations. - Use
for(C-style) for index-based iteration. - Avoid
for...infor arrays; usefor...oforObject.keys. - Avoid
varin loop initialisers; uselet. - Use generators for lazy iteration.
- Use the
entries(),keys(),values()methods for granular iteration. - Use
for await...offor async iterables. - Chain array methods for substantial transformations.
The combination — multiple loop forms (for, while, do, for...of, for...in), array methods for functional transformations, generators for lazy iteration, async iterables — is the substance of TypeScript’s iteration surface. The discipline produces clear, expressive iteration code; the array-method style admits substantial conciseness for routine transformations.