Unit A - Foundations

06. Loops, Arrays & Functions

Remove repetition with for/while, store related pins in arrays, and package work in functions.

Estimated time 4 hours

Learning outcomes

  • Explain the init, condition and update parts of a for loop
  • Choose for or while for a counting or waiting task
  • Store related values in an array and index from zero safely
  • Write a function with parameters and void or valued return
  • Avoid off-by-one bounds errors and infinite loops

Parts and preparation

Arduino Uno, four LEDs, four 330 ohm resistors, breadboard and jumper wires.

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

Loops, Arrays & Functions instructional connection diagram

Why loops and functions matter

Copy-pasting the same digitalWrite four times works once - then a pin change means four edits and four chances to mistype. Loops walk a list. Arrays hold that list. Functions name a reusable task.

This lesson builds a four-LED chase, then a setAll helper, using those three tools together.

Flow from LED array through for loop and setAll function to outputs
Array holds pins. for walks them. setAll names a shared action.

for loop anatomy

A for header has three parts separated by semicolons: initialisation, condition and update.

Initialisation runs once. The condition is tested before each body pass - if false, the loop ends. After the body, the update runs (often i++), then the condition is tested again.

Use for when the count is known: walk every LED, repeat N times, scan an array.

for header broken into init, condition and update cards
Init once, test the condition, run the body, update, repeat until the test fails.
for (byte i = 0; i < ledCount; i++) {
  digitalWrite(leds[i], HIGH);
  delay(150);
  digitalWrite(leds[i], LOW);
}

while when you wait for a change

while (condition) { ... } repeats as long as the condition stays true. It suits waiting until a button releases, draining Serial bytes, or looping until a flag clears.

Danger: if nothing inside the body can make the condition false, the rest of loop() never runs. That is an infinite loop - fine for a deliberate hang demo, fatal in a multi-task sketch.

Side by side cards comparing for for known counts and while for waiting until something changes
for = known count. while = until something changes. Prefer the clearer shape.
// Example idea - not in the worked sketch
while (digitalRead(buttonPin) == LOW) {
  // wait until released (blocks other work)
}

An array holds several values of the same type under one name. Indexing starts at zero: the first item is leds[0], not leds[1].

For four pins, valid indices are 0, 1, 2 and 3. Declaring const byte leds[] = {2, 3, 4, 5}; keeps pin numbers in one place.

Four cells leds 0 to 3 holding pin numbers 2, 3, 4 and 5
Four items means indices 0-3. leds[4] does not exist.
ExpressionMeaning
leds[0]First pin (2)
leds[3]Last pin (5)
ledCountHow many items (4)
leds[4]Out of bounds - bug

sizeof for length

Writing const byte ledCount = 4; works, but then you must update two places when you add a pin. sizeof(leds) / sizeof(leds[0]) computes the element count from the array itself.

Use that length in every for condition so the loop and the array stay matched.

const byte leds[] = {2, 3, 4, 5};
const byte ledCount = sizeof(leds) / sizeof(leds[0]);

The bounds trap

The classic off-by-one error is writing i <= ledCount instead of i < ledCount. With four LEDs that tries index 4 - memory that is not your array.

Symptoms look random: a wrong pin toggles, the board resets, or nothing obvious happens until later. Always ask: what is the last valid index?

Safe i less than ledCount versus buggy i less than or equal to ledCount
i < ledCount stops at the last valid index. i <= ledCount walks off the end.

Functions name a task

A function packages work behind a clear name. Parts: return type, name, parameter list, body.

void means the function does not return a value - it just performs an action. Parameters are inputs: setAll(bool state) receives the level to write to every LED.

Call it with setAll(HIGH); or setAll(LOW);. One change inside setAll updates every caller.

Return type, name, parameters and body for void setAll bool state
Return type, name, parameters, body. Call the name whenever you need the task.
void setAll(bool state) {
  for (byte i = 0; i < ledCount; i++) {
    digitalWrite(leds[i], state);
  }
}

What the worked sketch practises

setup configures every pin in the array with a for loop. loop chases LEDs one by one, then calls setAll to flash them together.

delay is used for timing here so the pattern is easy to see. Later lessons replace long delays when several tasks must stay responsive.

Wiring and safe build sequence

  1. D2 -> 330 ohm -> LED anode; cathode -> GND
  2. D3 -> 330 ohm -> LED anode; cathode -> GND
  3. D4 -> 330 ohm -> LED anode; cathode -> GND
  4. D5 -> 330 ohm -> LED anode; cathode -> GND
  5. Confirm LED polarity: long leg is usually the anode
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.
const byte leds[] = {2, 3, 4, 5};
const byte ledCount = sizeof(leds) / sizeof(leds[0]);

void setAll(bool state) {
  for (byte i = 0; i < ledCount; i++) {
    digitalWrite(leds[i], state);
  }
}

void setup() {
  for (byte i = 0; i < ledCount; i++) {
    pinMode(leds[i], OUTPUT);
  }
}

void loop() {
  // Chase: one LED at a time
  for (byte i = 0; i < ledCount; i++) {
    digitalWrite(leds[i], HIGH);
    delay(150);
    digitalWrite(leds[i], LOW);
  }

  // All together via the helper function
  setAll(HIGH);
  delay(300);
  setAll(LOW);
  delay(300);
}

How the code works

  1. sizeof expression calculates the array length instead of hard-coding 4.
  2. setAll receives one Boolean (or HIGH/LOW) and writes every pin in the array.
  3. Valid indices are 0 to ledCount - 1. Never use i <= ledCount.
  4. The chase for loop and setAll both share the same leds[] list.

Test and record evidence

Expected result: Four LEDs light in order (chase), then all turn on together, then off, and the pattern repeats.

Practical evidence checklist

Common faults and checks
  • If only some LEDs work, check that LED's resistor and polarity first.
  • An i <= ledCount condition accesses one item past the array - change it to i < ledCount.
  • If the chase skips a pin, confirm the leds[] values match your wiring.
  • Dim or dead LED: try swapping that LED with a known-good one.
Extension challenge: Write chase(bool forward) that runs left-to-right when forward is true and right-to-left when false. Call both directions from loop.

Check your understanding

Q1. What are the three parts inside a for (...) header?

Show answer

Initialisation, condition and update.

Q2. What is the first array index?

Show answer

0.

Q3. Why is i <= ledCount dangerous for a 4-element array?

Show answer

It also uses index 4, which is outside the array.

Q4. What does void mean on a function?

Show answer

The function does not return a value - it only performs an action.

Q5. When is while a better fit than for?

Show answer

When you repeat until a condition changes, rather than a known count.