Scriptdrill

JavaScript for Sheets Updated November 2026

forEach vs for loops

rows.forEach((row, i) => ...) runs a function per row and gives the index, but cannot break early and return only skips the current row; for (const row of rows) supports break and continue; the classic for (let i = 0; ...) gives full control, including looping backward for deletions. Never use for...in on arrays.

Syntax

rows.forEach((row, i) => {...}) | for (const row of rows) {...} | for (let i = rows.length - 1; i >= 0; i--) {...}

Examples

const rows = [["Pens", 12], ["Paper", 5], ["Clips", 300]];
rows.forEach((r, i) => Logger.log((i + 2) + ": " + r[0]));

Result: 2: Pens, 3: Paper, 4: Clips (index plus header offset gives the sheet row)

let firstLow = null;
for (const [name, qty] of rows) {
  if (qty < 10) { firstLow = name; break; }
}
Logger.log(firstLow);

Result: Paper

break stops at the first match; forEach could not do this.

const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getValues();
for (let i = data.length - 1; i >= 0; i--) {
  if (data[i][1] === 0) sheet.deleteRow(i + 2);
}

Result: Rows with quantity 0 removed, bottom up so indexes stay valid

for (const i in rows) Logger.log(typeof i);

Result: string, string, string

for...in gives string keys, not numbers, which is why it is wrong for arrays.

Common mistakes

  • return inside forEach does not exit the function: it only ends that iteration; use a for loop with return or break, or array.some / find.
  • for...in on an array iterates string indexes and inherited keys: use for...of or forEach.
  • The loop variable is shared across iterations with var: use let or const so each iteration has its own binding.
  • Deleting rows in a forward loop skips rows: loop backward when removing rows or elements.

Practice forEach vs for loops

These drills check your answer on live data, free, no account needed.

  • Has every tagReturn `true` when every string in `required` appears in `tags`, otherwise `false`. Matching is case-sensitive, and an empty `required` list is always satisfied.
  • Range of numbersReturn the numbers from `start` up to, but not including, `end`, counting by `step`. A negative `step` counts down (then `end` is below `start`). If the step never reaches `end`, or `step` is 0, return an empty array.
  • Running totalsReturn a new array where each position holds the sum of all numbers up to and including that position. Do not change `nums`.

Questions

Is forEach slower than for?

Not meaningfully in Apps Script. The expensive part is calling the spreadsheet inside the loop, so batch those calls regardless of loop style.

How do I get the index with for...of?

for (const [i, row] of rows.entries()) gives both the index and the row.

Related

  • Loop through rowsLoop through rows in Google Sheets with Apps Script: read the data once with getValues, loop the 2D array with for, for...of or forEach, then write back.
  • map, filter and reduceUse map, filter and reduce on Google Sheets data in Apps Script: transform rows, keep rows that match a condition, sum a column, chain them, write back.
  • Delete rowsDelete rows in Google Sheets with Apps Script: deleteRow and deleteRows by position, delete rows that match a condition by looping from the bottom, blanks.
  • Objects: keys, values and entriesWork with objects in Google Apps Script: turn sheet rows into objects by header, Object.keys, values and entries, Object.fromEntries, count or group rows.

Previous: map, filter and reduceNext: Objects: keys, values and entries