Processing Beginner Article 1

Functions in Processing

Write reusable void and return-type functions to organise your sketches and unlock recursion.

⏱ 20 min functions parameters return scope recursion

You’ve already been using functions

Every time you call ellipse(), fill(), or background(), you are calling a function that Processing provides. Functions are named, reusable blocks of code. You pass data in (arguments), they do something, and some functions hand a value back. Now it is time to write your own.

Void functions

A void function performs a task but does not return a value. The keyword void is the return type, meaning “nothing comes back.”

void drawStar(float x, float y, float r) {
  // a simple 5-pointed star using triangles
  pushMatrix();
  translate(x, y);
  noStroke();
  fill(255, 220, 50);
  for (int i = 0; i < 5; i++) {
    float angle = TWO_PI / 5 * i - HALF_PI;
    float ox = cos(angle) * r;
    float oy = sin(angle) * r;
    float ix = cos(angle + TWO_PI / 10) * r * 0.4;
    float iy = sin(angle + TWO_PI / 10) * r * 0.4;
    triangle(0, 0, ox, oy, ix, iy);
  }
  popMatrix();
}

void setup() {
  size(600, 400);
}

void draw() {
  background(20, 20, 40);
  drawStar(150, 200, 60);
  drawStar(300, 150, 40);
  drawStar(480, 250, 80);
}

drawStar takes three parameters: x, y, and r. When you call drawStar(150, 200, 60), the values 150, 200, and 60 are the arguments — they are copied into those parameters for that one call.

Return-type functions

When you need a function to compute a value and hand it back, replace void with the type of that value.

float distance(float x1, float y1, float x2, float y2) {
  float dx = x2 - x1;
  float dy = y2 - y1;
  return sqrt(dx * dx + dy * dy);
}

// In draw():
float d = distance(mouseX, mouseY, width / 2, height / 2);
float radius = map(d, 0, 400, 10, 150);
ellipse(width / 2, height / 2, radius, radius);

The return statement immediately exits the function and sends the value back to the caller. Processing already has dist() built in, but writing your own version shows exactly how it works.

Other useful return-type patterns:

// Clamp a value between lo and hi
float clamp(float val, float lo, float hi) {
  return max(lo, min(hi, val));
}

// Map a 0–1 noise value to a colour
color noiseColor(float n) {
  return color(n * 255, 100, 200 - n * 150);
}

Scope: local vs global variables

Variables declared inside a function are local — they only exist for the duration of that call and are invisible to other functions. Variables declared at the top of your sketch (outside any function) are global and can be read or changed anywhere.

float circleX = 300;  // global — visible everywhere
float circleY = 200;  // global

void setup() {
  size(600, 400);
}

void draw() {
  background(30);
  moveCircle();
  drawCircle();
}

void moveCircle() {
  float speed = 2;          // local — only exists here
  circleX += speed;
  if (circleX > width + 30) circleX = -30;
}

void drawCircle() {
  fill(80, 180, 255);
  noStroke();
  ellipse(circleX, circleY, 60, 60);
}

As a general rule, prefer local variables and only promote to global when multiple functions genuinely need to share the value.

Refactoring: breaking a long sketch into functions

Before refactoring, draw() might look like this:

void draw() {
  background(15);
  // draw background stars
  fill(255);
  noStroke();
  for (int i = 0; i < 80; i++) {
    float sx = noise(i * 0.3) * width;
    float sy = noise(i * 0.3 + 100) * height;
    ellipse(sx, sy, 2, 2);
  }
  // draw planet
  fill(60, 120, 200);
  ellipse(width / 2, height / 2, 120, 120);
  fill(255, 255, 255, 80);
  ellipse(width / 2 - 20, height / 2 - 20, 40, 40);
}

After refactoring:

void draw() {
  background(15);
  drawStars();
  drawPlanet(width / 2, height / 2, 120);
}

void drawStars() {
  fill(255);
  noStroke();
  for (int i = 0; i < 80; i++) {
    float sx = noise(i * 0.3) * width;
    float sy = noise(i * 0.3 + 100) * height;
    ellipse(sx, sy, 2, 2);
  }
}

void drawPlanet(float x, float y, float diameter) {
  fill(60, 120, 200);
  noStroke();
  ellipse(x, y, diameter, diameter);
  fill(255, 255, 255, 80);
  ellipse(x - diameter * 0.17, y - diameter * 0.17,
          diameter * 0.33, diameter * 0.33);
}

Each function has a single responsibility, a name that describes it, and is easy to test or change in isolation.

Recursion: drawing a fractal tree

Recursion is when a function calls itself. You need a base case — a condition that stops the recursion — otherwise the sketch crashes with a stack overflow.

void setup() {
  size(600, 600);
  background(15, 15, 25);
  stroke(180, 220, 160);
  strokeWeight(1.5);
  // Draw tree from the bottom centre
  branch(width / 2, height - 80, -HALF_PI, 120, 0);
}

// angle is in radians; len is branch length; depth tracks recursion level
void branch(float x, float y, float angle, float len, int depth) {
  if (len < 6 || depth > 10) return;  // base case

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

  // Thicker near the trunk, thinner at tips
  strokeWeight(map(len, 6, 120, 1, 8));
  line(x, y, endX, endY);

  // Recurse into two child branches
  branch(endX, endY, angle - 0.4, len * 0.68, depth + 1);
  branch(endX, endY, angle + 0.4, len * 0.68, depth + 1);
}

Each call to branch() draws one line segment, then calls branch() twice more for the two child branches. Because len shrinks by 32 % on each level, the recursion terminates naturally when len < 6.

Key takeaways

  • Declare a void function to package up actions you want to repeat or name.
  • Declare a typed return function (float, int, color, etc.) when you need to compute and reuse a value.
  • Parameters are local copies — changing them inside the function does not affect the caller.
  • Keep global variables to a minimum; prefer passing values as arguments.
  • Recursion requires a base case — always confirm yours before running.