GDKB

Loops

Beginner15 Minutes readUpdated July 21, 20261 related concept

A loop repeats a block of code while a condition holds, or a fixed number of times, or once per element of a collection. `for`, `while`, and `foreach` are the standard forms. Loops are how "do this for every enemy in the level," "keep simulating until the physics step settles," and "run this every frame" are expressed without writing the same code once per repetition.

On this page

Loops

Summary

A loop repeats a block of code while a condition holds, or a fixed number of times, or once per element of a collection. for, while, and foreach are the standard forms. Loops are how "do this for every enemy in the level," "keep simulating until the physics step settles," and "run this every frame" are expressed without writing the same code once per repetition.

Mental Model

Where a conditional (see Conditional Statements) is a single fork checked once, a loop is a fork checked repeatedly, with the road looping back to the same sign after each pass through the block. Every iteration re-evaluates the condition; the loop keeps running as long as the answer is "true," and control only moves past the loop once it becomes "false" (or a break exits it directly).

The three loop forms differ in when the sign is checked and what drives it:

  • while checks a general boolean condition before every iteration — the most flexible form, and the one the others can all be rewritten in terms of.
  • for bundles three things a counting loop always needs — an initializer, a condition, and a per-iteration update — into one line, so a bounded, counted repetition reads as a single unit instead of three separate pieces the reader has to mentally reassemble.
  • foreach hides the counting entirely and just says "once per element of this collection," which is both simpler to read and immune to a whole class of off-by-one bugs that hand-written index management invites.

Core Explanation

  • while (condition) { ... } checks condition before each iteration, including the first. If condition is false immediately, the body never runs at all.
  • do { ... } while (condition); checks condition after each iteration, so the body always runs at least once — useful specifically when "run once, then keep going while still needed" is the actual requirement (a menu loop that must show at least once).
  • for (init; condition; update) { ... } runs init once, then repeats: check condition, run the body, run update. The canonical use is a bounded counting loop (for (int i = 0; i < count; i++)), but any of the three clauses can be more general — the classic for loop is really while with its bookkeeping made explicit and localized.
  • foreach (var item in collection) { ... } iterates once per element of a collection, in the collection's own order, without exposing an index at all. It cannot skip, reorder, or index backward the way an indexed for can — the trade for that simplicity is less control.
  • break exits the nearest enclosing loop immediately, regardless of the loop's condition. continue skips the rest of the current iteration's body and jumps straight to the next condition check (or the update clause, in a for loop) — the loop keeps going, just the rest of this pass is skipped.
  • Nested loops (a loop inside a loop) are common for anything grid-shaped (iterating rows, then columns) — break/continue inside a nested loop only affect the innermost loop unless the language provides labeled break/continue, so exiting an outer loop from inside a nested one usually needs an explicit flag or early return instead.

Infinite loops happen when the condition never becomes false — sometimes intentional (a game's main loop, exited via break or a return, not by the condition naturally failing), and sometimes a bug (forgetting to update the variable the condition depends on).

Syntax / Examples

C# (Unity)

csharp
// for: bounded counting loop
for (int i = 0; i < enemies.Count; i++)
{
    enemies[i].TakeDamage(splashDamage);
}

// foreach: no index management at all
foreach (Enemy enemy in enemies)
{
    enemy.TakeDamage(splashDamage);
}

// while: repeats until a general condition fails
while (queue.Count > 0 && !cancelled)
{
    ProcessNext(queue.Dequeue());
}

// break / continue
foreach (Enemy enemy in enemies)
{
    if (enemy.IsDead) continue;   // skip dead enemies
    if (enemy.IsBoss) break;      // stop entirely once a boss is found
    enemy.Update();
}

C++ (Unreal)

cpp
for (int32 i = 0; i < Enemies.Num(); ++i)
{
    Enemies[i]->TakeDamage(SplashDamage);
}

for (AEnemy* Enemy : Enemies) // range-based for — C++'s foreach
{
    Enemy->TakeDamage(SplashDamage);
}

while (Queue.Num() > 0 && !bCancelled)
{
    ProcessNext(Queue.Pop());
}

Unreal Blueprint

A For Loop node exposes First Index/Last Index pins and a Loop Body execution pin, mirroring the counting for. A For Each Loop node iterates an array directly, matching foreach. A While Loop node re-checks its condition pin every pass. break has no dedicated node in standard Blueprint loops — exiting early is typically done with a Branch that skips remaining logic, a documented difference from the code forms above.

Practical Example

Finding the closest enemy to the player combines a foreach loop with a running "best so far" variable — a pattern that recurs constantly:

csharp
Enemy closest = null;
float closestDistance = float.MaxValue;

foreach (Enemy enemy in enemies)
{
    float distance = Vector3.Distance(player.position, enemy.position);
    if (distance < closestDistance)
    {
        closest = enemy;
        closestDistance = distance;
    }
}

closest and closestDistance are initialized before the loop and updated inside it — a "running best" is one of the most common shapes a loop with accumulated state takes, alongside running totals and counts.

Common Mistakes

  • Off-by-one errors — i <= count instead of i < count reads one element past the end of a zero-indexed collection. foreach avoids this category of bug entirely by never exposing an index.
  • Infinite loops from a condition that never changes — a while whose condition depends on a variable the body never updates never terminates.
  • Modifying a collection while iterating it with foreach — most collection types throw at runtime if the collection changes shape (adding/removing elements) mid-iteration; removing matched elements needs either an indexed loop iterated backward, or building a separate list of items to remove and removing them after the loop ends.
  • Recomputing an expensive value every iteration instead of computing it once outside the loop when it doesn't actually change per-iteration.
  • Using break/continue inside a nested loop expecting it to affect the outer loop — it only ever affects the nearest enclosing loop.

Best Practices

  • Prefer foreach whenever the index itself isn't needed — it removes an entire category of off-by-one and index-management bugs.
  • Use for when you genuinely need the index (parallel arrays, iterating backward, iterating only every Nth element).
  • Never mutate a collection you're iterating with foreach; collect changes and apply them after the loop, or iterate an explicit index backward when removing matched elements.
  • Hoist any per-iteration-invariant computation out of the loop body.
  • Keep loop bodies focused; extract a named function (see Functions) when a loop body grows past a few lines of clearly separable logic.

Exercises

  1. Rewrite a for loop that iterates a list by index into an equivalent foreach, and explain what index-related bug class this removes.
  2. Write a loop that removes every "dead" enemy from a list without modifying the list while a foreach iterates it.
  3. Given a while (count > 0) { ... } loop, identify what must happen inside the body for the loop to ever terminate, and write a version that doesn't (as a deliberate bug), then fix it.
  4. Write a nested loop over a 2D grid that uses continue to skip already-visited cells and break to stop the inner loop once a target is found in the current row.
  • Conditional Statements — a loop's condition is the same kind of boolean expression, just re-checked every iteration instead of once.
  • Functions — extracting a loop body into a named function once it grows complex.

References

Related Concepts