How to Loop Through an Array in TypeScript

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
Command Prompt showing a TypeScript file compiled with tsc and run with node, looping through an array of cities with for of, an indexed for loop and forEach
Same array, three loops: for...of, an indexed for, and forEach().
You needUseCan break early?
Just the valuesfor (const item of arr)Yes
Index and valuefor (const [i, item] of arr.entries())Yes
Full control of the indexfor (let i = 0; i < arr.length; i++)Yes
A callback per elementarr.forEach((item, i) => ...)No
A new array backarr.map(...) / arr.filter(...)No
One value backarr.reduce(...)No
To stop at a matcharr.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
Command Prompt output comparing a for in loop over a TypeScript array, where the index is a string, with for of and entries which give numbers and values only
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
Command Prompt output showing forEach returning before any async work finished while a for of loop awaits each item 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' ]
Command Prompt output showing forEach skipping a hole in a sparse TypeScript array and splice during iteration skipping an element
The hole is invisible to 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));
Command Prompt output timing a for loop, for of, forEach and reduce summing one million numbers in TypeScript
One run over 1,000,000 numbers, after warm-up: the indexed 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:

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.