The Creative Coder’s Field Guide

Everything You Need to Survive and Thrive with p5.js

Creative Coding · Week 3

Either-Or

Every sketch so far has done exactly the same thing, every frame, no matter what. Week 3 changes that: your code can now ask a question (is the mouse over here?) and do something different depending on the answer.

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

Code that chooses

Up to now, draw() has always run top to bottom, every line, every frame. A conditional is the first tool that lets a sketch skip lines: run one block of code instead of another, depending on what's true right now.

The question a conditional asks always has exactly two possible answers: true or false. You already met that data type back in Week 2: a boolean. This week you finally get to do something with one: use it to steer which code runs.

That's the whole idea. Everything else this week (comparators, &&, chains of else if) is just more precise ways of asking the question and more places to send the answer.

02Syntax

The if statement

An if statement has three parts: the keyword, a question in parentheses, and a block of code in braces that runs only when the answer is true.

if (mouseX > 200) { the keyword run this? the condition must be true or false fill(255, 0, 60); } the block runs only if the condition is true No condition in parens, no code, and no semicolon after the closing brace.
Same shape as setup() and draw(): a head, then a block in braces. The difference is the parentheses: this head has to evaluate to true or false, and that answer decides whether the block below even runs.

With no if, that block runs simply by being there. With an if guarding it, the block becomes optional, and the condition is what decides.

03Chains

else and else if

An if by itself can only skip a block. Add else and you get exactly one of two blocks, always:

if (mouseX > 200) {
  fill(255, 0, 60);   // mouse on the right half
} else {
  fill(220);          // mouse on the left half
}

Stack more tests with else if to build a chain: JavaScript checks each condition top to bottom and runs the block belonging to the first one that's true. Every test after that is skipped entirely, even if it would also have been true.

let dotSize = 40;

if (dotSize < 20) {
  fill(0, 170, 90);     // small
} else if (dotSize < 60) {
  fill(255, 140, 0);    // medium, only checked if "small" failed
} else {
  fill(255, 0, 60);     // large, whatever's left over
}
dotSize < 20 ? test 1 dotSize < 60 ? test 2 small runs, rest skipped medium runs, rest skipped large (else) no test needed true false, check next false: nothing left to test, fall through to else
Only one branch ever runs. else costs nothing to check: by the time you reach it, every other option has already failed, so there's nothing left to ask.

Order matters. The chain stops at the first true, so put the most specific test first. If dotSize < 60 were tested before dotSize < 20, every small dot would match the “medium” branch first and never reach its own test.

04Boolean expressions

Comparators: asking precise questions

A boolean expression is anything that evaluates to true or false (the thing that goes inside an if's parentheses). A comparator is how you build one out of two values.

ComparatorAsksExample
>greater thanmouseX > 200
<less thanmouseX < 200
>=greater than or equal tox >= width
<=less than or equal tox <= 0
===equal, value and type must matchkey === "a"
!==not equal (strict, same rule)key !== "a"
==equal, but converts types first (loose)"5" == 5true
!=not equal, loose"5" != 5false

Default to === and !==. The loose versions, == and !=, quietly convert types to try to make things match: "5" == 5 comes back true even though one side is text and the other is a number. That's rarely what you meant. The strict versions refuse to guess: different types are never equal, full stop. Reach for ==/!= only if you can say exactly why you want the conversion.

05Boolean variables

Saving the answer

A comparator produces a boolean value, and like any value, you can store it in a variable instead of using it right away:

let isHovering = mouseX > 200;   // isHovering now holds true or false

Once you have a boolean variable, use it directly as a condition. Don't compare it to true again, because it already is the true-or-false answer, and asking the same question twice is a comparison you didn't need to make:

// unnecessary: isHovering already holds the answer
if (isHovering === true) { … }

// the variable is the boolean; just ask it
if (isHovering) { … }

This isn't new territory, either. Back in Week 2's reference table, mouseIsPressed and keyIsPressed were already boolean variables: p5 fills them in for you, every frame. You could have written if (mouseIsPressed) the moment you met them; you just didn't have if yet.

06Combining

Logical operators: &&, ||, !

Three operators combine boolean expressions into bigger ones.

&&: AND

True only if both sides are true.

leftrightleft && right
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

||: OR

True if at least one side is true.

leftrightleft || right
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

!: NOT

Flips a single boolean to its opposite: !true is false; !false is true. It takes one value, not two.

// mouse is somewhere inside the canvas
if (mouseX > 0 && mouseX < width) { … }

// mouse is near either edge
if (mouseX < 20 || mouseX > width - 20) { … }

JavaScript reads left to right and stops as soon as it knows the answer. For &&, one false is enough to know the whole thing is false, so it never even looks at the right side. For ||, one true is enough to know the whole thing is true. This is called short-circuit evaluation, and it's not just a shortcut for reading: it's the machine doing less work, which comes back in section 07.

07Efficiency

Boolean logic and efficiency

A sketch's draw() runs about 60 times a second. Every comparison inside it runs 60 times a second too, so a redundant one isn't free, it's just small and repeated. Efficient conditionals are about not repeating a question you've already answered.

  • Use else if, not separate ifs, when only one outcome should happen. Separate ifs are all checked, every time, even after one has already matched. A chain stops at the first match (section 03).
  • Order && and || so the cheapest or most likely test comes first. Short-circuiting (section 06) means the second side is sometimes never evaluated at all: put the test that fails often on the left of an &&, or the one that succeeds often on the left of an ||.
  • Save a repeated comparison in a boolean variable instead of writing the same expression more than once in a frame (section 05).
  • Don't re-ask what the chain already told you. Inside an else if branch, you already know every earlier test failed: there's no need to test the lower bound again, only whatever's still undecided.

“Fewest calculations” means fewest comparisons the computer actually has to make, not fewest lines of code. A chain that checks two things and then falls through to else is doing less work than three independent ifs that each check two things, even if the second version reads a little more simply.

08Flipping

Two ways to become your opposite: a = !a and a *= -1

“Flip to the opposite” is common enough in creative coding to deserve its own pattern, and it looks different depending on what you're flipping.

PatternWorks onBefore → afterTypical use
a = !abooleanstruefalsetoggle a state on/off
a *= -1numbers3-3reverse a direction

Toggling a boolean. mousePressed() is another function p5 calls for you (the same deal as setup() and draw()), you're just filling in what happens, this time whenever a mouse button goes down:

let isRed = false;

function draw() {
  if (isRed) {
    background(255, 0, 60);
  } else {
    background(220);
  }
}

function mousePressed() {
  isRed = !isRed;   // whatever it was, it's the opposite now
}

Reversing a direction. The same “become your opposite” idea, but applied to a number instead of a boolean, this time a speed that reverses when it hits a wall:

let x = 0;
let speed = 3;

function draw() {
  background(220);
  x += speed;
  if (x > width || x < 0) {
    speed *= -1;   // hit a wall, reverse direction
  }
  ellipse(x, height / 2, 30, 30);
}

Same idea, different data type. !a asks a boolean to become whichever value it currently isn't. a *= -1 asks a number to become its mirror image across zero. They're not interchangeable: reach for the one that matches what you're actually flipping.

09Reference

Quick reference

Comparators

SymbolMeaning
>   <   >=   <=greater than / less than / or-equal-to versions
===   !==strict equal / not equal, default to these
==   !=loose equal / not equal, converts types first

Logical operators

SymbolMeaningShort-circuits on
&&both sides must be truefirst false
||at least one side truefirst true
!flips one booleann/a

Debug tip

print() works on boolean expressions exactly like anything else, and it's often the fastest way to check a condition before wiring any drawing code to it:

print(mouseX < 150);   // true or false, right in the console
10Practice

Be able to do: three columns, fewest calculations

Divide the canvas into three equal vertical columns. Color the column red when the mouse hovers over it, in the most efficient way possible: the fewest number of calculations.

Set the stage

Pick a canvas width that divides evenly by three, because it keeps every boundary a clean number instead of a repeating decimal. Compute the column width once, in setup(), rather than re-deriving it every frame:

let colW;

function setup() {
  createCanvas(450, 300);
  colW = width / 3;   // 150, divides evenly
}

function draw() {
  noStroke();
  fill(220);
  rect(0, 0, colW, height);
  rect(colW, 0, colW, height);
  rect(colW * 2, 0, colW, height);
}

Run it first with no conditionals at all: confirm the three grey columns line up before adding any logic.

Ask one question at a time

Write the boolean expression for “is the mouse in the first column?” and check it with print() (section 09) before wiring it to any color:

print(mouseX < colW);

Sweep the mouse across the canvas and watch it flip between true and false in the console.

The tempting way (and its cost)

The obvious first draft tests every column as its own range, with its own if:

if (mouseX >= 0 && mouseX < colW) { /* column 1 */ }
if (mouseX >= colW && mouseX < colW * 2) { /* column 2 */ }
if (mouseX >= colW * 2 && mouseX < width) { /* column 3 */ }

It works, but every frame runs all three independent ifs, up to six comparisons total, and most of them re-prove a lower bound you already knew from the column before.

The efficient way

An else if chain (section 03) needs only two comparisons, ever, because three columns have only two boundaries between them, and the third column is just “whatever's left,” free:

function draw() {
  noStroke();

  if (mouseX < colW) {
    fill(255, 0, 60);   rect(0, 0, colW, height);
    fill(220);          rect(colW, 0, colW, height);
                        rect(colW * 2, 0, colW, height);
  } else if (mouseX < colW * 2) {
    fill(220);          rect(0, 0, colW, height);
    fill(255, 0, 60);   rect(colW, 0, colW, height);
    fill(220);          rect(colW * 2, 0, colW, height);
  } else {
    fill(220);          rect(0, 0, colW, height);
                        rect(colW, 0, colW, height);
    fill(255, 0, 60);   rect(colW * 2, 0, colW, height);
  }
}

Notice there's no background() here at all: the three rectangles already cover the canvas edge to edge every frame, so clearing first would just be one more unnecessary calculation.

Why this is the fewest calculations

Three columns, two boundaries, at most two comparisons a frame: the naive version above ran up to six. Scale it up: n columns always need only n−1 boundary checks with an else if chain, no matter how many columns there are, because the last one never needs to be tested at all. That's section 07's whole point, made concrete: the fastest question is the one you never ask, and turning that n-column count into a loop is exactly where Week 4 picks up.