p5.js Beginner Article 2

Loops & Patterns

Use for and while loops to generate grids, rings, spirals, and repeating motifs.

⏱ 20 min read loops patterns for while generative

The for loop

A for loop runs a block of code a set number of times:

for (let i = 0; i < 10; i++) {
  // runs 10 times — i goes 0, 1, 2, ... 9
}

Draw 10 evenly-spaced circles across the canvas:

function setup() {
  createCanvas(600, 200);
  noLoop();
}

function draw() {
  background(20);
  noStroke();
  fill(100, 200, 255);

  for (let i = 0; i < 10; i++) {
    let x = map(i, 0, 9, 40, width - 40);
    circle(x, height / 2, 30);
  }
}

Nested loops — grids

Two loops, one inside the other, generate a 2D grid:

function draw() {
  background(20);
  noStroke();

  let cols = 12;
  let rows = 8;
  let cellW = width / cols;
  let cellH = height / rows;

  for (let col = 0; col < cols; col++) {
    for (let row = 0; row < rows; row++) {
      let x = col * cellW + cellW / 2;
      let y = row * cellH + cellH / 2;

      // Size based on distance from cursor
      let d    = dist(mouseX, mouseY, x, y);
      let size = map(d, 0, 300, cellW * 0.9, 4);

      fill(
        map(col, 0, cols - 1, 50, 255),
        map(row, 0, rows - 1, 50, 255),
        200
      );
      circle(x, y, size);
    }
  }
}

Loop with step size

You’re not limited to i++. Change the increment:

// Lines every 20 pixels
for (let x = 0; x <= width; x += 20) {
  line(x, 0, x, height);
}

// Count down
for (let i = 100; i > 0; i -= 10) {
  circle(width / 2, height / 2, i * 2);
}

Concentric rings — a classic:

function draw() {
  background(20);
  noFill();
  stroke(100, 200, 255, 80);
  strokeWeight(1);

  for (let r = 10; r < 300; r += 15) {
    circle(width / 2, height / 2, r * 2);
  }
}

while loop

Use while when you don’t know in advance how many iterations you need:

// Pack random circles without overlapping (basic attempt)
let circles = [];

while (circles.length < 50) {
  let candidate = {
    x: random(width),
    y: random(height),
    r: random(10, 40)
  };

  let valid = true;
  for (let c of circles) {
    if (dist(candidate.x, candidate.y, c.x, c.y) < candidate.r + c.r + 4) {
      valid = false;
      break;
    }
  }

  if (valid) circles.push(candidate);
}

Be careful with while loops — if the condition never becomes false, you’ll freeze the browser. Always have a safe exit condition or a maximum iteration count.

Polar coordinates — rings and spirals

Instead of a grid (x, y), think in terms of angle and radius:

function setup() {
  createCanvas(600, 600);
  noLoop();
  background(20);
}

function draw() {
  translate(width / 2, height / 2);
  noStroke();

  let numDots = 120;
  for (let i = 0; i < numDots; i++) {
    let angle  = TWO_PI / numDots * i;
    let radius = map(i, 0, numDots, 40, 240);
    let x = cos(angle) * radius;
    let y = sin(angle) * radius;

    colorMode(HSB, 360, 100, 100);
    fill(map(i, 0, numDots, 0, 360), 80, 90);
    circle(x, y, map(i, 0, numDots, 4, 14));
  }
}

Change radius to a constant for a circle of dots. Use it as a growing value for a spiral.

Recursive patterns

A function can call itself to create fractal-like structures:

function setup() {
  createCanvas(600, 600);
  noLoop();
  background(20);
  stroke(100, 200, 255, 120);
  noFill();
  branches(width / 2, height - 20, -HALF_PI, 120);
}

function branches(x, y, angle, len) {
  if (len < 5) return;  // stop condition — essential!

  let endX = x + cos(angle) * len;
  let endY = y + sin(angle) * len;

  strokeWeight(map(len, 5, 120, 0.5, 3));
  line(x, y, endX, endY);

  branches(endX, endY, angle - 0.4, len * 0.72);
  branches(endX, endY, angle + 0.4, len * 0.72);
}

Loop-based animation

Use frameCount with a loop for animated patterns:

function draw() {
  background(20);
  translate(width / 2, height / 2);
  noFill();
  stroke(150, 100, 255, 80);

  let t = frameCount * 0.008;

  for (let i = 0; i < 60; i++) {
    let angle = TWO_PI / 60 * i + t;
    let r1    = 80 + sin(t * 2 + i * 0.2) * 40;
    let r2    = 140 + cos(t + i * 0.15) * 50;
    strokeWeight(0.8);
    line(
      cos(angle) * r1, sin(angle) * r1,
      cos(angle + 0.2) * r2, sin(angle + 0.2) * r2
    );
  }
}

Key takeaways

  • for (let i = 0; i < n; i++) is the standard counting loop
  • Nested for loops generate 2D grids
  • Change the step size (i += 20) for spaced repetition
  • Polar coordinates (cos(angle) * r, sin(angle) * r) produce circular/spiral arrangements
  • Recursive functions can generate tree and fractal structures — always include a stop condition
  • Combine loops with frameCount for animated patterns