To iterate over an array in TypeScript, use for...of when you only need the values, a classic for loop or entries() when you need the index, and forEach() when a callback reads better. All three keep full type safety: the loop variable gets the array’s element type, so string[] gives you a string. This guide runs every way to loop through an array, shows which one to reach for, and covers the traps that bite in real code: for...in on an array, break inside forEach(), and await that does not wait.
Every example was compiled and run with TypeScript 5.2.2 and Node.js v22.22.2 in the Windows Command Prompt, with tsc --target es2022 --lib es2023,dom --strict file.ts and then node file.js. The output shown is the real output. Reference: for…of on MDN.
Iterate over an array in TypeScript
Here are the three loops you will use ninety percent of the time, over the same array:
const cities: string[] = ["Austin", "Denver", "Seattle"];
for (const city of cities) { // the value, one at a time
console.log("for...of:", city);
}
for (let i = 0; i < cities.length; i++) { // the index as well
console.log(`index ${i}:`, cities[i]);
}
cities.forEach((city, i) => { // a callback per element
console.log(`forEach ${i}:`, city);
});
Output:
for...of: Austin
for...of: Denver
for...of: Seattle
index 0: Austin
index 1: Denver
index 2: Seattle
forEach 0: Austin
forEach 1: Denver
forEach 2: Seattle
for...of, an indexed for, and forEach().| You need | Use | Can break early? |
|---|---|---|
| Just the values | for (const item of arr) | Yes |
| Index and value | for (const [i, item] of arr.entries()) | Yes |
| Full control of the index | for (let i = 0; i < arr.length; i++) | Yes |
| A callback per element | arr.forEach((item, i) => ...) | No |
| A new array back | arr.map(...) / arr.filter(...) | No |
| One value back | arr.reduce(...) | No |
| To stop at a match | arr.find(...) / arr.some(...) | Stops itself |
for…of: the default loop for arrays
for...of reads the values in order and supports break and continue, which the callback methods do not. It also works on a readonly array, because reading never breaks the promise not to change anything. There is a fuller walkthrough in our guide to for…of loops in TypeScript:
const temperatures: number[] = [68, 74, 91, 55, 102];
for (const temp of temperatures) {
if (temp < 60) continue; // skip this one, keep going
if (temp > 100) {
console.log("too hot, stopping at", temp);
break; // leave the loop completely
}
console.log("reading:", temp);
}
const readonlyDays: readonly string[] = ["Mon", "Tue", "Wed"];
for (const day of readonlyDays) console.log(day); // readonly arrays iterate fine
Output:
reading: 68
reading: 74
reading: 91
too hot, stopping at 102
Mon
Tue
Wed
Loop through an array with the index
entries() hands you a [number, T] pair, so you can destructure the index and the value in one line. The classic for loop is still the right tool when you need to walk backwards, skip every second element, or change i yourself; see the for loop in TypeScript for those patterns:
const products: string[] = ["Keyboard", "Monitor", "Webcam"];
for (const [index, name] of products.entries()) { // index and value together
console.log(`${index + 1}. ${name}`);
}
products.forEach((name, index, all) => { // forEach gets a third argument
console.log(index, name, "of", all.length);
});
for (let i = products.length - 1; i >= 0; i--) { // backwards
console.log("reverse:", products[i]);
}
Output:
1. Keyboard
2. Monitor
3. Webcam
0 Keyboard of 3
1 Monitor of 3
2 Webcam of 3
reverse: Webcam
reverse: Monitor
reverse: Keyboard
forEach() passes the index as its second argument and the whole array as its third. If that is what brought you here, we have a dedicated guide to the forEach loop with an index.
for…in on an array is almost always a mistake
for...in loops over keys, and array keys are strings. That is why i + 1 gives you "01" instead of 1, and why for...in also walks any extra property someone hung on the array:
const scores: number[] = [90, 72, 88];
for (const i in scores) {
console.log(typeof i, "| index + 1 =", i + 1); // a string, so "+" concatenates
}
for (const [i] of scores.entries()) {
console.log(typeof i, "| index + 1 =", i + 1); // a real number
}
const withExtra: number[] = [90, 72];
(withExtra as unknown as Record<string, unknown>).lastUpdated = "2025-03-14";
for (const key in withExtra) console.log("for...in sees:", key); // the extra property too
for (const value of withExtra) console.log("for...of sees:", value); // only the elements
Output:
string | index + 1 = 01
string | index + 1 = 11
string | index + 1 = 21
number | index + 1 = 1
number | index + 1 = 2
number | index + 1 = 3
for...in sees: 0
for...in sees: 1
for...in sees: lastUpdated
for...of sees: 90
for...of sees: 72
for...in hands you string keys and any extra properties; for...of does not.Ask for a number and the compiler confirms it:
const scores: number[] = [90, 72, 88];
for (const i in scores) {
const next: number = i; // the key is a string, not a number
console.log(next);
}
tsc output:
for_in_typed.ts(4,9): error TS2322: Type 'string' is not assignable to type 'number'.
Keep for...in for plain objects and use for...of or entries() for arrays.
Iterate over an array of objects
Nothing changes when the elements are objects: the loop variable gets the interface type, so the compiler knows every property and narrows the union in status. This is the pattern behind most real reports, and it pairs well with arrays of objects in TypeScript:
interface Order {
id: string;
customer: string;
total: number;
status: "pending" | "shipped";
}
const orders: Order[] = [
{ id: "ORD-1001", customer: "Emma", total: 249.99, status: "shipped" },
{ id: "ORD-1002", customer: "Michael", total: 89.5, status: "pending" },
{ id: "ORD-1003", customer: "Olivia", total: 430, status: "shipped" },
];
let revenue = 0;
for (const order of orders) {
if (order.status === "pending") continue;
revenue += order.total;
console.log(`${order.id} ${order.customer.padEnd(8)} $${order.total.toFixed(2)}`);
}
console.log("shipped revenue: $" + revenue.toFixed(2));
Output:
ORD-1001 Emma $249.99
ORD-1003 Olivia $430.00
shipped revenue: $679.99
You cannot break out of forEach()
break inside a forEach() callback is a syntax error, and return only skips the current element, so the loop keeps running. When you want to stop early, use find(), some() or a plain for...of. We cover the workarounds in breaking out of a forEach loop, and the method itself in the TypeScript forEach method:
const temperatures: number[] = [68, 74, 91, 55, 102];
temperatures.forEach(temp => {
if (temp > 90) return; // return only skips THIS element
console.log("forEach saw", temp);
});
const firstHot = temperatures.find(temp => temp > 90); // stops at the first match
console.log("first over 90:", firstHot);
console.log("any over 100?", temperatures.some(temp => temp > 100));
console.log("all above freezing?", temperatures.every(temp => temp > 32));
Output:
forEach saw 68
forEach saw 74
forEach saw 55
first over 90: 91
any over 100? true
all above freezing? true
Loop with async/await: forEach does not wait
This one costs people hours. forEach() ignores the promise your async callback returns, so the loop finishes immediately and the results land later, in whatever order they finish. for...of awaits each turn properly:
const ids: number[] = [3, 1, 2];
const wait = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));
async function load(id: number): Promise<string> {
await wait(id * 100); // pretend this is an API call
return `order-${id} loaded`;
}
async function main(): Promise<void> {
console.log("forEach:");
ids.forEach(async id => console.log(" ", await load(id)));
console.log(" forEach returned before anything loaded");
await wait(500);
console.log("for...of:");
for (const id of ids) console.log(" ", await load(id));
console.log(" for...of finished, in order");
}
main();
Output:
forEach:
forEach returned before anything loaded
order-1 loaded
order-2 loaded
order-3 loaded
for...of:
order-3 loaded
order-1 loaded
order-2 loaded
for...of finished, in order
forEach() returned first and the results arrived out of order; for...of kept them in order.If the calls are independent and you want them in parallel, do not loop at all: await Promise.all(ids.map(load)) starts them together and still gives you the results in array order.
map, filter and reduce: loops that give something back
When the point of the loop is to build a value, these read better than pushing into an array by hand. Each one keeps its types: map over a Product[] returning names gives a string[]. See filtering arrays in TypeScript for the narrowing tricks:
interface Product {
name: string;
price: number;
}
const products: Product[] = [
{ name: "Keyboard", price: 49.99 },
{ name: "Monitor", price: 229 },
{ name: "Webcam", price: 39.5 },
];
const names: string[] = products.map(p => p.name); // same length, new values
const cheap: Product[] = products.filter(p => p.price < 100); // fewer items
const total: number = products.reduce((sum, p) => sum + p.price, 0); // one value
console.log(names);
console.log(cheap.map(p => p.name));
console.log("total: $" + total.toFixed(2));
Output:
[ 'Keyboard', 'Monitor', 'Webcam' ]
[ 'Keyboard', 'Webcam' ]
total: $318.49
Two traps: holes and changing the array while you loop
forEach() skips holes but for...of visits them as undefined, and removing an element inside a loop shifts everything after it, so the next element gets skipped. If you need to remove items, build a new array with filter() instead, or loop backwards, or collect the ones you want into a new array:
const sparse: number[] = [10, 20, 30];
delete sparse[1]; // leaves a hole, length stays 3
sparse.forEach(value => console.log("forEach:", value)); // the hole is skipped
for (const value of sparse) console.log("for...of:", value); // the hole is undefined
const queue: string[] = ["a", "b", "c", "d"];
queue.forEach((item, i) => {
console.log("visiting", item);
if (item === "b") queue.splice(i, 1); // removing while looping skips "c"
});
console.log("left:", queue);
Output:
forEach: 10
forEach: 30
for...of: 10
for...of: undefined
for...of: 30
visiting a
visiting b
visiting d
left: [ 'a', 'c', 'd' ]
forEach(), and splice() mid-loop skips "c".Change the elements while you loop
forEach() gives you the array itself as a third argument, so you can write back into it as you go. If nothing else should see the change, use map() and keep the original. Note that the loop variable in for...of is a copy of the value, so assigning to it changes nothing:
const prices: number[] = [100, 200, 300, 400, 500];
// forEach hands you the array itself as the third argument
prices.forEach((price, i, all) => {
all[i] = Math.round(price * 0.9 * 100) / 100; // 10% off, in place
});
console.log(prices);
const original: number[] = [100, 200, 300];
const discounted: number[] = original.map(price => price * 0.9); // new array instead
console.log(discounted, original);
for (const price of original) {
// changing "price" here would do nothing: it is a copy of the value
console.log("value copy:", price);
}
Output:
[ 90, 180, 270, 360, 450 ]
[ 90, 180, 270 ] [ 100, 200, 300 ]
value copy: 100
value copy: 200
value copy: 300
Which loop is fastest?
Summing a million numbers, each loop is timed after a warm-up run so the JIT has finished optimising:
function time(label: string, fn: () => number): void {
fn(); // warm-up run, so the JIT is done optimising
const start = performance.now();
const result = fn();
console.log(label.padEnd(16), (performance.now() - start).toFixed(1).padStart(7), "ms", "sum", result);
}
const numbers: number[] = Array.from({ length: 1_000_000 }, (_, i) => i);
time("for", () => { let s = 0; for (let i = 0; i < numbers.length; i++) s += numbers[i]; return s; });
time("for, cached len", () => { let s = 0; const len = numbers.length; for (let i = 0; i < len; i++) s += numbers[i]; return s; });
time("for...of", () => { let s = 0; for (const n of numbers) s += n; return s; });
time("forEach", () => { let s = 0; numbers.forEach(n => { s += n; }); return s; });
time("reduce", () => numbers.reduce((s, n) => s + n, 0));
for is several times quicker than the rest.The indexed for loop does the least work per step: for...of pays for an iterator object on every turn, and forEach() and reduce() call a function a million times. Caching the length in a variable, advice you still see everywhere, made no difference at all: V8 hoists arr.length out of the loop by itself. Milliseconds over a million elements are not what slows a page down, so write the loop that reads best and keep the indexed for for genuinely hot code.
More TypeScript array guides you may find useful:
- Add or append elements to an array
- Sort an array of objects by property
- Find an object in a TypeScript array
- Check if an array contains a value
- Initialize an array in TypeScript
Frequently asked questions
How do I loop through an array in TypeScript?
Use for (const item of items) for the values, or items.forEach((item, i) => ...) if you prefer a callback. Both keep the element type.
How do I get the index while looping?
Destructure entries(): for (const [i, item] of items.entries()). A classic for loop and forEach()‘s second argument also give you the index.
What is the difference between for…of and forEach()?
for...of supports break, continue and await. forEach() supports none of them, but it is compact and cannot forget the loop bounds.
Can I use break inside forEach() in TypeScript?
No, break there is a syntax error and return only skips one element. Use for...of, or find() / some() to stop at a match.
Why should I not use for…in with an array?
for...in gives string keys, so arr[i] raises error TS7015, and it also visits any extra properties on the array. Use for...of.
How do I use await inside a loop over an array?
Use for...of with await for one at a time, or await Promise.all(items.map(fn)) to run them in parallel. forEach() will not wait.
Which loop is fastest in TypeScript?
An indexed for loop is usually a little faster than for...of, forEach() and reduce(), but over a million items the gap is milliseconds. Readability matters more.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn