How to Get the Index in a forEach Loop in TypeScript

To get the index in a forEach loop in TypeScript, take the second parameter of the callback: items.forEach((item, index) => ...). The index is a number, it starts at 0, and you do not have to declare its type because TypeScript infers it from the array. For numbering that people read, use index + 1. This guide shows the index with plain values and with objects, the third callback parameter almost nobody uses, when map() or for...of is the better tool, and the three places where the index is not the number you expect.

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 is the real output. Reference: Array.prototype.forEach() on MDN.

Get the index in a forEach loop in TypeScript

The callback receives the element first and its index second. Name them whatever you like; the order is what matters:

const cities: string[] = ["Austin", "Denver", "Seattle"];

cities.forEach((city, index) => {
  console.log(index, city);               // the index is the second parameter
});

cities.forEach((city, index) => {
  console.log(`${index + 1}. ${city}`);   // numbering for people starts at 1
});

Output:

0 Austin
1 Denver
2 Seattle
1. Austin
2. Denver
3. Seattle
Command Prompt showing a TypeScript file compiled with tsc and run with node, printing the forEach index next to each city and then numbering them from one
The index starts at 0; index + 1 gives the numbering people expect.
What you needWrite
The indexarr.forEach((item, index) => ...)
Numbering from 1index + 1
The last elementindex === arr.length - 1
Every other elementif (index % 2 === 0)
The index and a new arrayarr.map((item, index) => ...)
The index plus break or awaitfor (const [index, item] of arr.entries())

The three callback parameters

forEach passes three things: the value, the index, and the array itself. The third one is useful for “is this the last row” checks and for writing back into the array as you go. Because forEach does not modify the array on its own, it also runs happily on a readonly array. The method itself is covered in our guide to the TypeScript forEach method:

const readings: number[] = [68, 74, 91];

// value, index, and the array forEach was called on
readings.forEach((value, index, all) => {
  const last = index === all.length - 1;
  console.log(index, value, last ? "(last reading)" : "");
});

// the array parameter also lets you write back into it
const prices: number[] = [100, 250, 75];
prices.forEach((price, index, all) => {
  all[index] = Math.round(price * 0.9 * 100) / 100;
});
console.log(prices);

// forEach never changes the array itself, so readonly arrays are fine to read
const limits: readonly number[] = [10, 20];
limits.forEach((limit, index) => console.log(index, limit));

Output:

0 68 
1 74 
2 91 (last reading)
[ 90, 225, 67.5 ]
0 10
1 20

Use the index with an array of objects

This is where the index earns its keep: numbered reports, ranked lists and row ids. TypeScript knows the type of each object, so the compiler checks every property while you format the line. See arrays of objects in TypeScript for more on the typing side:

interface Customer {
  name: string;
  city: string;
  total: number;
}

const customers: Customer[] = [
  { name: "Emma Johnson", city: "Austin", total: 249.99 },
  { name: "Michael Brown", city: "Denver", total: 89.5 },
  { name: "Olivia Davis", city: "Seattle", total: 430 },
];

customers.forEach((customer, index) => {
  const rank = String(index + 1).padStart(2, "0");
  console.log(`${rank}. ${customer.name.padEnd(15)} ${customer.city.padEnd(9)} $${customer.total.toFixed(2)}`);
});

// building a second array from the index
const labels: string[] = [];
customers.forEach((customer, index) => labels.push(`row-${index}-${customer.city.toLowerCase()}`));
console.log(labels);

Output:

01. Emma Johnson    Austin    $249.99
02. Michael Brown   Denver    $89.50
03. Olivia Davis    Seattle   $430.00
[ 'row-0-austin', 'row-1-denver', 'row-2-seattle' ]
Command Prompt output of a TypeScript forEach loop numbering customer records with the index and building row labels from it
The index numbers the rows and feeds the row-0-austin style labels.

Numbering across pages, and matching two arrays

Two jobs the index does every day in real applications. The first is numbering a paginated table, where row 1 of page 3 should read 21, not 1. The second is reading a value from a second array that lines up with the first, which is common with data that arrives as parallel columns:

const pageSize = 10;
const pageNumber = 3;                       // we are showing the third page
const rows: string[] = ["Emma Johnson", "Michael Brown", "Olivia Davis"];

rows.forEach((name, index) => {
  const position = (pageNumber - 1) * pageSize + index + 1;
  console.log(`${position}. ${name}`);      // numbering continues across pages
});

// the index also lines up two arrays that belong together
const cities: string[] = ["Austin", "Denver", "Seattle"];
const totals: number[] = [249.99, 89.5, 430];

cities.forEach((city, index) => {
  console.log(`${city.padEnd(9)} $${totals[index].toFixed(2)}`);
});

Output:

21. Emma Johnson
22. Michael Brown
23. Olivia Davis
Austin    $249.99
Denver    $89.50
Seattle   $430.00

You do not need to type the index

TypeScript infers both parameters from the array, so annotations are optional. Annotate it wrongly and the compiler stops you, which is worth seeing once:

const cities: string[] = ["Austin", "Denver"];

cities.forEach((city: string, index: string) => {   // the index is a number
  console.log(index, city);
});

tsc output:

foreach_wrong_type.ts(3,16): error TS2345: Argument of type '(city: string, index: string) => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'.
  Types of parameters 'index' and 'index' are incompatible.
    Type 'number' is not assignable to type 'string'.
Command Prompt showing TypeScript error TS2345 because a forEach callback declares the index parameter as a string instead of a number
Error TS2345: the index is a number, and TypeScript will not accept string.

forEach returns nothing: use map() when you want a result

In React, this same (item, index) callback is where key={index} comes from. It works, but it is a poor key whenever the list can be reordered, filtered or added to in the middle, because the key then describes a position rather than an item. Use a stable id when you have one.

A forEach callback’s return value is thrown away, so a loop that looks like it builds something actually returns undefined. map() takes the same (value, index) callback and gives you the array back, and entries() in a for...of loop gives you the index in a loop that supports break, continue and await, as covered in looping through an array in TypeScript:

const cities: string[] = ["Austin", "Denver", "Seattle"];

// forEach returns undefined, so this does not give you an array
const nothing = cities.forEach((city, index) => `${index}: ${city}`);
console.log("forEach returned:", nothing);

// map() has the same index parameter and returns the new array
const numbered: string[] = cities.map((city, index) => `${index + 1}. ${city}`);
console.log(numbered);

// for...of with entries() gives you the index and supports break, continue and await
for (const [index, city] of cities.entries()) {
  if (city === "Denver") continue;
  console.log(index, city);
}

Output:

forEach returned: undefined
[ '1. Austin', '2. Denver', '3. Seattle' ]
0 Austin
2 Seattle

Trap 1: after filter(), the index is not the original index

Chaining filter().forEach() renumbers everything, because the callback now walks a brand new array. If you need the position in the original data, test inside a single loop instead of filtering first. Filtering arrays in TypeScript has more on the chaining side:

const orders = [
  { id: "ORD-1001", status: "shipped" },
  { id: "ORD-1002", status: "pending" },
  { id: "ORD-1003", status: "shipped" },
];

// the index here belongs to the FILTERED array, not the original one
orders.filter(o => o.status === "shipped").forEach((order, index) => {
  console.log("filtered index", index, order.id);
});

// keep the original position by finding it first
orders.forEach((order, index) => {
  if (order.status === "shipped") console.log("original index", index, order.id);
});

Output:

filtered index 0 ORD-1001
filtered index 1 ORD-1003
original index 0 ORD-1001
original index 2 ORD-1003
Command Prompt output showing that a TypeScript forEach index after filter starts again at zero while the original indexes are one and three
Same two orders, different numbers: 0, 1 after the filter, 0, 2 in the original array.

Trap 2: holes and removing items make the index jump

forEach skips empty slots entirely, so the index goes straight past them, and removing an element inside the loop shifts every later element down one place. Both are easier to see than to describe. For removals, build a new array with filter or one of the other removal methods instead:

const readings: number[] = [10, 20, 30, 40];
delete readings[1];                       // a hole, length stays 4

readings.forEach((value, index) => console.log("index", index, "value", value));   // index 1 never runs

const queue: string[] = ["a", "b", "c", "d"];
queue.forEach((item, index) => {
  console.log("visiting index", index, item);
  if (item === "b") queue.splice(index, 1);      // removing shifts everything after it
});
console.log("left:", queue);

Output:

index 0 value 10
index 2 value 30
index 3 value 40
visiting index 0 a
visiting index 1 b
visiting index 2 d
left: [ 'a', 'c', 'd' ]
Command Prompt output showing a TypeScript forEach loop skipping index one in a sparse array and splice during the loop skipping an element
Index 1 never runs, and the splice() call makes the loop miss "c".

Trap 3: you cannot break, and await does not wait

Two more limits worth knowing before you reach for forEach: break is a syntax error inside the callback, and an async callback is not awaited, so the loop finishes before any of the work does. We cover the first in breaking out of a forEach loop, and the second is demonstrated with real timings in the for…of guide. Both problems disappear with for (const [index, item] of arr.entries()).

More TypeScript array guides worth reading:

Frequently asked questions

How do I get the index in a forEach loop in TypeScript?

Use the second callback parameter: items.forEach((item, index) => console.log(index, item)). TypeScript types it as a number for you.

Does the forEach index start at 0 or 1?

At 0, like every array index in JavaScript and TypeScript. Print index + 1 when the numbering is for a reader.

What are the three forEach parameters?

The value, its index, and the array forEach was called on: (value, index, array). The third is handy for last-row checks.

Do I need to declare the type of the index?

No. TypeScript infers number from the array, and annotating it as anything else is a compile error (TS2345).

Why is my index wrong after filter()?

Because filter() returns a new array and forEach numbers that one from 0. Loop over the original array and test inside the callback.

How do I get the index and still use break or await?

Use for (const [index, item] of items.entries()). forEach supports neither break nor await.

How do I get the index in a forEach loop over an array of objects?

Exactly the same way: customers.forEach((customer, index) => ...). The object keeps its type, so property names are still checked.