One-sentence summary
By splitting a long, crowded loop() function into small functions with meaningful names, we make our code easier to read, easier to reuse and easier to test.
Why does it matter?
As an Arduino project grows, the loop() function swells. Reading the sensor, calculating, lighting an LED, sounding a buzzer and printing to the serial port all pile up line after line. After a while, even reading your own code becomes hard. You end up staring at a line thinking, "What did this do again?"
In the Python lessons we met the idea of a function: define a job once, then call it by name. Arduino's language (C/C++) uses exactly the same idea. When you write readDistance(), you do not need to know how the job is done; the name alone tells you what it does.
You can picture this with two everyday examples:
- A recipe: A recipe split into headings like "prepare the dough", "heat the oven" and "decorate the top" is far easier to follow than the same recipe written as one long paragraph.
- A school bag: Pens go in one pocket, books in another. If everything were in one compartment, finding what you need would take longer.
Splitting code into functions opens exactly these compartments for your program.
What is a function and how do we write one?
A function is a piece of code that does a job and to which we give a name. We write it once and call it as many times as we like.
You are already using two functions in Arduino: setup() runs once, and loop() repeats forever. Now we will add our own.
The simplest function
The function below does a job but does not return a value. That is why we begin it with void, which means "empty" — there is no returned value.
void beepOnce() {
tone(4, 1000); // send a 1000 Hz tone to pin 4
delay(200);
noTone(4); // stop the tone
}
You call this function with one line inside loop(): beepOnce();. When reading the code, saying "here it beeps once" is enough; the details stay hidden inside the function.
return: sending a value back
Some functions do a job and then hand the result back. In Python we used return; C/C++ uses the same word. We write the type of the returned value at the front of the function. A function that returns a whole number starts with int; for a long whole number we write long.
int readTemperature() {
int raw = analogRead(A0); // read the sensor
int celsius = raw / 4; // a simple conversion example
return celsius; // send the result back
}
Now you can write int t = readTemperature(); to store the temperature in a variable. The return inside the function means "I am done, here is my answer."
Before: everything inside loop
Let us look at a real example. We have a distance sensor (HC-SR04), an LED and a buzzer. The goal: when an object comes closer than 10 centimetres, the LED lights up and the buzzer sounds. This is the very heart of the parking sensor in the next lesson.
On a first attempt, most people write everything inside loop():
const int trigPin = 9;
const int echoPin = 10;
const int ledPin = 3;
const int buzzerPin = 4;
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(ledPin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
}
And loop() grows this long:
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
int distance = duration / 58; // convert microseconds to cm
Serial.println(distance);
if (distance < 10) {
digitalWrite(ledPin, HIGH);
tone(buzzerPin, 1000);
} else {
digitalWrite(ledPin, LOW);
noTone(buzzerPin);
}
delay(100);
}
This code works. But loop() mixes two separate jobs: *reading* the sensor and *showing* the alert. Both sit in one heap.
After: separating the jobs into functions
Now let us split the same work in two. One function only reads the sensor and returns the distance; another only shows the alert. setup() stays the same.
First, the reading job. This function returns an int value:
int readDistance() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
return duration / 58; // return the distance in cm
}
Next, the alert job. This function takes a distance from outside (a parameter) and, because it returns nothing, it is void:
void showAlert(int distance) {
if (distance < 10) {
digitalWrite(ledPin, HIGH);
tone(buzzerPin, 1000);
} else {
digitalWrite(ledPin, LOW);
noTone(buzzerPin);
}
}
Now loop() becomes wonderfully simple. You can read what it does at a glance:
void loop() {
int distance = readDistance(); // read
Serial.println(distance);
showAlert(distance); // alert
delay(100);
}
The logic is exactly the same; no line was lost. Two jobs simply moved into two boxes. If tomorrow you want a screen instead of a buzzer, you only change the inside of showAlert(); you never touch the reading part.
Mini practice
Adapt the "after" example to your own project. This time split the alert logic into three levels and improve the showAlert() function.
Target behaviour:
- If the distance is greater than 20 cm: LED off, no sound (safe).
- If the distance is between 10 and 20 cm: LED on, no sound (caution).
- If the distance is less than 10 cm: LED on and the buzzer sounds (danger).
Do not touch readDistance() at all. Only widen the if structure inside showAlert(). To test, move your hand slowly toward the sensor and watch the number in the Serial Monitor to confirm all three states work correctly.
Hint: you can add the middle level with else if.
Common mistakes
Writing the wrong function type
If readDistance() returns a number but you begin it with void, the compiler gives an error. A function that returns a value must have a type such as int or long; a function that returns nothing is void.
Forgetting return
If you begin with int but put no return inside, the function does not hand back the value it promised. You said "I will return an answer" but you did not.
Putting pinMode outside setup
Do not scatter pinMode() calls inside your other functions. Setup happens once; its home is setup(). Otherwise loop() needlessly re-configures the pins on every pass.
Giving a function a poor name
Names like sensor2() or doIt() say nothing. Choose names that describe the job, like readDistance() and showAlert(). A good name reduces the need for comments.
Safety note
This lesson uses only low-voltage parts: power your Arduino from a USB cable or a suitable battery pack. Never work with mains (wall socket) electricity.
- Always connect an LED through a current-limiting resistor (for example 220 Ω); wired directly, the LED can be damaged.
- Small parts such as a buzzer and an LED can be driven directly from an Arduino pin. But if you add a motor in the next step, never power the motor straight from an Arduino pin: a motor draws too much current and can damage the board. A motor needs a separate power source and a motor driver board.
- Cut the board's power while changing the wiring. When you try a new connection for the first time, ask an adult for help and start at low values.
Lesson summary
- A function is a piece of code that names a job and can be called again and again.
- A function that returns nothing is
void; one that returns a value uses a type such asintorlong, andreturnhands the result back. - Splitting a long
loop()into meaningful pieces likereadDistance()andshowAlert()makes the code readable. - In split code each function is responsible for a single job; changing one does not break the other.
- This is the same idea as functions in Python; only the syntax is slightly different.
Check questions
- What is the difference between a function that begins with
voidand one that begins withint? - What does
returndo in the linereturn duration / 58;? - Why do we split a long
loop()function into small functions? Give one reason. - In
showAlert(int distance), what doesint distanceinside the parentheses mean? - Is the correct home for
pinMode()callssetup()orloop()? Why?
Answers
- A
voidfunction does a job but returns no value; anintfunction calculates a whole number and hands it back withreturn. - It marks that the function has finished and sends the calculated distance (in centimetres) back to wherever the function was called.
- The code becomes easier to read and test; because each job sits in its own box, changing one does not affect the other. (Reuse is also a valid answer.)
- It is a parameter given to the function from outside; the caller sends a distance value and the function uses it under the name
distance. setup(). Pin configuration is a setup job and needs to happen only once; putting it inloop()means needless repetition on every pass.
Source and verification note
For “Splitting Code into Functions”, verification focuses on whether the relationship between What is a function and how do we write one? and return: sending a value back remains consistent across examples. Pin, voltage and current limits can differ between Arduino-compatible boards. Compiling code does not guarantee a safe circuit; loads such as motors and servos require a suitable driver and external power where appropriate.
Next lesson
Project: Smart Parking Sensor — In this lesson we will bring together the readDistance() and showAlert() functions you wrote and build a real parking sensor that speeds up as an object gets closer.