← All posts
6 min read

Loops and Repetition: Reusing Instructions Across Data and Time

#technology#computer-science#loops#programming
📑 On this page

Programs often repeat the same kind of work:

  • Display every message
  • Sum every price
  • Retry until success or timeout
  • Process rows in a file

Copying the same code many times is difficult to maintain.

A loop repeats a block of instructions while a condition holds or once for each item in a collection.

A correct loop defines where repetition begins, what changes, and why it eventually stops.

The three-part model

Many loops contain:

  1. Initialization
  2. Continuation condition
  3. Progress step

Example:

for (let index = 0; index < 5; index += 1) {
  console.log(index);
}
  • Start at zero.
  • Continue while index is below five.
  • Increase after each iteration.

Output:

0 1 2 3 4

The condition is checked before each iteration.

Off-by-one errors

Should a loop use < or <=?

For an array of length five, valid indices are 0 through 4.

index < items.length

is correct.

Using:

index <= items.length

attempts index 5, which is outside the array.

Test:

  • Empty collection
  • One item
  • Last item
  • Exact boundary

Off-by-one errors are among the most common programming bugs.

Collection iteration

When the index itself is not needed, iterate over values:

for (const message of messages) {
  renderMessage(message);
}

This communicates intent and avoids manual index mistakes.

Collections may provide higher-level operations:

const active = users.filter((user) => user.isActive);
const names = active.map((user) => user.name);

These are still forms of iteration.

While loops

A while loop repeats based on a condition:

while (queue.length > 0) {
  process(queue.shift());
}

Use it when the number of iterations is not known in advance.

The body must change something relevant to the condition or rely on an external event.

Otherwise:

while (true) { ... }

can run forever unless it contains a deliberate break or the program is a long-running event loop.

Do-while loops

A do-while loop runs the body at least once:

do {
  answer = askUser();
} while (!isValid(answer));

The condition is checked after the body.

This is useful when one attempt must occur before deciding whether to repeat.

It can also make control flow less familiar, so use it when the at-least-once behavior is genuinely clearer.

A concrete message-list example

function countUnread(messages) {
  let unread = 0;
 
  for (const message of messages) {
    if (!message.isRead) {
      unread += 1;
    }
  }
 
  return unread;
}

The loop:

  • Starts an accumulator
  • Visits each message
  • Conditionally updates state
  • Returns the result

For an empty list, it returns zero without special handling.

That empty-case behavior is worth testing explicitly.

Break and continue

break exits a loop:

for (const item of items) {
  if (item.id === wantedId) {
    found = item;
    break;
  }
}

continue skips to the next iteration:

for (const file of files) {
  if (file.hidden) continue;
  process(file);
}

Both can simplify control flow.

Too many exits can make a loop difficult to reason about. Keep the stopping behavior visible.

Nested loops

A loop inside another loop multiplies work.

for (const student of students) {
  for (const course of courses) {
    checkEnrollment(student, course);
  }
}

With 1,000 students and 1,000 courses, that makes one million checks.

Sometimes all pairs are required. Often a map or index can avoid repeated searching.

Nested loops should trigger a complexity question, not an automatic ban.

Mutation during iteration

Changing a collection while iterating can skip items or invalidate an iterator.

Example:

for (let i = 0; i < items.length; i += 1) {
  if (shouldRemove(items[i])) {
    items.splice(i, 1);
  }
}

After removal, the next item shifts into the current index, then the loop increments past it.

Safer options include:

  • Filter into a new collection
  • Iterate backward
  • Use an iterator designed for removal
  • Separate selection from mutation

Asynchronous loops

These have different behavior:

for (const url of urls) {
  await fetch(url);
}

This runs requests sequentially.

await Promise.all(urls.map((url) => fetch(url)));

This starts them concurrently.

Unlimited concurrency can overload networks or services.

A concurrency-limited worker pool often balances throughput and resource control.

Recursion versus loops

Recursion calls a function from itself.

It naturally represents:

  • Trees
  • Nested structures
  • Divide-and-conquer algorithms

Loops naturally represent sequences and repeated state updates.

Deep recursion can exceed call-stack limits unless the language optimizes tail calls or the program uses an explicit stack.

Choose the form that makes progress and termination easiest to prove.

Performance and complexity

A loop over n items is commonly O(n).

Two independent consecutive loops remain O(n):

O(n) + O(n) = O(n)

A nested all-pairs loop is O(n²).

The work inside each iteration also matters. A loop can appear simple while calling an expensive database query each time, creating an N+1 query problem.

Measure the complete operation.

Common misunderstandings

"A for loop always starts at zero"

It starts wherever initialization specifies. Zero is common for zero-indexed collections.

"Higher-level map and filter avoid loops"

They express iteration through library operations.

"Parallel requests are always faster"

They can overwhelm resources, trigger rate limits, and increase contention.

"An infinite loop is always a bug"

Servers and event loops intentionally run indefinitely, but they wait efficiently and include shutdown behavior.

Knowledge check

1. What three ideas should a loop define?

Initialization, a continuation or stopping condition, and progress toward termination.

2. Why is index < array.length usually correct?

Zero-indexed arrays end at length - 1, so equality would be out of bounds.

3. What does break do?

It exits the current loop immediately.

4. Why can mutating a collection during iteration be dangerous?

Indices and iterator state can shift, causing elements to be skipped or processed incorrectly.

The one idea to remember

Loops express controlled repetition, and correctness depends on progress, boundaries, and a clear stopping rule.

Choose iteration forms that communicate intent, handle empty and final cases, and match the required performance and concurrency.

Next, we will package operations into functions with explicit inputs, outputs, and responsibilities.