The Creative Coder’s Field Guide

Everything You Need to Survive and Thrive with p5.js

Creative Coding · Week 4

Loop-Counter

Every sketch so far has meant retyping a line for every rectangle, every dot, every column: the same call, copy-pasted, with one number changed by hand. A loop replaces the copy-paste with a counter: write the line once, tell it how many times to run, and let the counter fill in the number.

JavaScript + p5.js · editor.p5js.org · Understand → Know → Do
01The idea

Code that counts itself

A loop is not a shape and it's not a color. It's a counter. It counts how many times a chunk of code should run, runs that chunk that many times, and then stops. That's the entire idea. Everything else this week is about controlling the counter: where it starts, when it should stop, and how it steps from one value to the next.

This isn't a brand-new concept, either. draw() has been a loop this whole time: p5 counts frames and calls your code again and again, roughly 60 times a second (Week 2). What's new this week is a loop you write, whose counter you control completely, that runs however many times you tell it to, right now, immediately, not spread out across frames.

That's the whole idea. Everything else this week (the three-part syntax, %, nesting one loop inside another) is just more control over the counter, and more places to send it.

02Syntax

The for loop

A for loop has a keyword, three pieces of setup packed into one set of parentheses and separated by semicolons, and a block in braces, the same block shape as if (Week 3), except this block can run more than once.

for (let i = 0; i < 5; i++) { the keyword starts the loop the start once, first the exit test a boolean the step after each print(i); } the loop body runs once per pass, top to bottom All three parts are separated by semicolons. And just like if, there's no semicolon after the closing brace.
Same block shape as if (Week 3): a head, then braces. The difference: once the body finishes, execution jumps back to the step, rechecks the exit test, and does it all again, until that boolean finally comes back false.
PartRunsIn this example
Startonce, before the first passlet i = 0
Exit testbefore every pass, must be a booleani < 5
Stepafter every passi++

Read it as a sentence: starting with i at 0, keep going while i is less than 5, and add 1 to i after every pass. i is just a variable, short for “index,” declared with let right inside the parentheses, exactly the way you'd declare any other variable (Week 2).

03The loop body

What's getting looped, and how many times

Everything inside the braces (the loop body) is the code that's getting looped. Anything outside the braces, above or below the loop, runs exactly once, same as always. Only the lines between { and } repeat.

To find out how many times, trace the counter by hand: start at the start value, and count every value that still passes the exit test.

ii < 5 ?Runs the body?
0trueyes, pass 1
1trueyes, pass 2
2trueyes, pass 3
3trueyes, pass 4
4trueyes, pass 5
5falseno, loop exits

for (let i = 0; i < 5; i++) runs 5 times, and i takes every value from 0 up to 4, never 5. The exit-test number and the number of passes are almost always different by exactly one, which is a common counting mistake. Trace it; don't guess it.

Count the values, not the boundary. for (let i = 0; i <= 5; i++) is a classic trap: swapping < for <= adds one extra pass, running 6 times (0 through 5) instead of 5. Read the exit test as a question about i, and trace it like the table above before trusting a guess.

04Patterns & formulas

Finding the formula that connects i to the canvas

A loop only helps when there's a pattern connecting the counter to what you draw: some formula that turns i into a position, a size, or a color. Spotting that formula is the real skill; the for syntax is just where you plug it in.

Say you'd hand-written four columns, 60px wide, side by side:

rect(0, 0, 60, height);
rect(60, 0, 60, height);
rect(120, 0, 60, height);
rect(180, 0, 60, height);

Look for the pattern in the numbers that change: 0, 60, 120, 180. Each one is 60 more than the last, and each is also 60 times which column this is: column 0 is 0 × 60, column 1 is 1 × 60, column 2 is 2 × 60. That's the formula: x position = counter × 60. As soon as you can see a pattern in a set of numbers like this, you can rewrite it as a loop:

for (let i = 0; i < 4; i++) {
  rect(i * 60, 0, 60, height);
}

One line in the loop body does the work of all four hardcoded calls. That line, rect(i * 60, 0, 60, height);, is the relationship: the single place where the counter i turns into something drawn on the canvas. It's the line to find first whenever you're reading someone else's loop, and the line to design first whenever you're writing your own.

The exit test scales for free. Because the formula is written in terms of i instead of hardcoded numbers, changing how many columns you want is a one-character edit: swap the 4 in the exit test for any number, and the same rect(i * 60, 0, 60, height); still produces perfectly spaced columns.

05Modulo

Know about: %, the leftover

% is the modulo operator. a % b divides a by b and gives back whatever's left over, the remainder, not the answer to the division.

7 % 3   // 7 = 2 groups of 3, with 1 left over → 1
9 % 3   // 9 = 3 groups of 3, with 0 left over → 0
2 % 3   // 3 doesn't go into 2 at all, so all of it is left over → 2

Feed it a counter that keeps climbing, and the leftover doesn't climb with it: it keeps falling back to 0 and counting up again. That's why % produces patterns: it's the operator that makes a straight line of numbers wrap into a repeating cycle.

i012345678
i % 3012012012

Once you can see that cycle, % pairs naturally with a conditional (Week 3) to alternate between two outcomes, such as a checkerboard fill, every other row a different shade:

if (i % 2 === 0) {
  fill(220);   // i is even, nothing left over when divided by 2
} else {
  fill(200);   // i is odd, 1 left over
}

Any pattern that repeats itself (alternating, cycling through a fixed set of colors, wrapping a value back into range) is a % pattern waiting to happen.

06Nested loops

Two loops vs. one loop inside another

Two separate loops, one after another, each still only touch one dimension: a loop that draws one row of cells, followed by a second, independent loop that draws one column of cells. Neither knows about the other.

A grid needs both dimensions at once: every combination of a row and a column. That takes nesting: writing one loop's entire body inside another loop's body.

for (let row = 0; row < 3; row++) {
  for (let col = 0; col < 4; col++) {
    // this line runs once for every (row, col) pair
  }
}

The outer loop's counter changes once per pass, same as any loop, but for each single value of row, the entire inner loop runs from start to finish, every value of col, before row is allowed to step again.

outer loop: row = 0, 1, 2 inner loop: col = 0, 1, 2, 3, runs fully for each row row 0, col 0 row 0, col 1 row 2, col 2 row 1, col 2
The highlighted cell is drawn on row 1's pass, when the inner loop's col reaches 2. Total passes through the innermost line = rows × columns: here, 3 × 4 = 12.

Every combination of row and col gets exactly one pass through the innermost code, which is also how you count total iterations of a nested loop: multiply the two counts. Three rows, four columns, twelve cells, twelve passes.

07Reference

Quick reference

for loop

PartQuestion it answers
Startwhere does the counter begin?
Exit testis it still true? (a boolean, checked before every pass)
Stephow does the counter change after each pass?

% modulo

a % b → the remainder left over after dividing a by b. Cycles back to 0. The operator to reach for whenever a pattern repeats.

Nested loop template

for (let row = 0; row < numRows; row++) {
  for (let col = 0; col < numCols; col++) {
    // runs once per (row, col), numRows × numCols times total
  }
}
08Practice

Be able to do

Identify the code that's getting looped

Given any for loop, find the braces first. Everything between { and } is the loop body, the code that's getting looped. Everything outside it, above or below, runs once, exactly where it sits.

let total = 0;                 // runs once
for (let i = 0; i < 10; i++) {
  total += i;                  // this line is what's looped
}
print(total);                  // runs once, after the loop is done

Calculate how many times it runs

Trace the counter (section 03) rather than guessing from the exit-test number. For for (let i = 2; i < 8; i++): i takes 2, 3, 4, 5, 6, 7 (six values), so the body runs 6 times, not 8 and not 7.

Identify the relationship line

In any loop that draws something, find the single line where the counter variable appears inside a drawing call: that's the line defining the relationship between the counter and the canvas (section 04). In the columns loop below, it's the rect() line, not the fill() line:

for (let i = 0; i < 4; i++) {
  fill(220);
  rect(i * 60, 0, 60, height);   // ← the relationship
}

Rewrite 10 columns as a loop

Ten hardcoded rect() calls draw ten equally sized, equally spaced vertical columns on a 400-wide canvas:

fill(220); rect(0,   0, 40, height);
fill(200); rect(40,  0, 40, height);
fill(220); rect(80,  0, 40, height);
fill(200); rect(120, 0, 40, height);
fill(220); rect(160, 0, 40, height);
fill(200); rect(200, 0, 40, height);
fill(220); rect(240, 0, 40, height);
fill(200); rect(280, 0, 40, height);
fill(220); rect(320, 0, 40, height);
fill(200); rect(360, 0, 40, height);

Spot the pattern (section 04): each x is the column number times 40, and the fill alternates by whether the column number is even or odd, using % (section 05). Ten calls become one loop:

let colW = width / 10;   // 10 columns, 40px each on a 400-wide canvas

function draw() {
  noStroke();
  for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) {
      fill(220);
    } else {
      fill(200);
    }
    rect(i * colW, 0, colW, height);
  }
}

Build a grid of cells you can mouse over

A nested loop (section 06) draws every cell; a comparator (Week 3) checks each cell against mouseX/mouseY to see whether the mouse is inside it, using the same boolean-variable pattern from Week 3's efficiency section, just computed fresh per cell:

let cellSize = 50;

function setup() {
  createCanvas(400, 300);   // 8 columns × 6 rows of 50px cells
}

function draw() {
  for (let row = 0; row < height / cellSize; row++) {
    for (let col = 0; col < width / cellSize; col++) {
      let x = col * cellSize;
      let y = row * cellSize;
      let hovering = mouseX > x && mouseX < x + cellSize &&
                     mouseY > y && mouseY < y + cellSize;

      if (hovering) {
        fill(255, 0, 60);
      } else {
        fill(220);
      }
      stroke(255);
      rect(x, y, cellSize, cellSize);
    }
  }
}

48 cells, 48 passes through the innermost line, every single frame, and only the one your mouse happens to be over ever comes back true.