Lesson 4: Functions & Modularity – Writing Clean, Reusable Code

Functions and Modularity: Writing Clean, Reusable Code

As programs grow, repeating the same lines everywhere becomes error-prone and hard to read. Functions solve this by packaging a task into a named block you can call whenever you need it. In this lesson you will write functions, pass values into them with parameters, and get results back with return values, then use them to build a dynamic LED blinker and a function-driven light sensor.

What a function is and why it matters

A function is a named block of code that performs one specific task. Instead of copying the same lines everywhere you need them, you write the task once inside a function and then call that function by name wherever you need it. This gives you three big wins: the code reads more clearly because each function name states its purpose, it is easier to debug because a fix in one place fixes every use, and it is reusable so you never copy and paste a block again. Functions are the single most important tool for keeping a growing program organized.

The simplest function: void with no parameters

The simplest function returns nothing and takes no input. You declare it with the void keyword, which means it is void of a return value, so it needs no return statement; it just runs the code between its braces. For example, a function named sayHello could hold a single line that prints Hello World. Once defined, you run it by writing its name followed by parentheses and a semicolon, sayHello(), from anywhere in your program, as many times as you like. Each call runs the whole block again.

Return values

Often you want a function to compute something and hand the answer back. That is a return value. Instead of void, you declare the type of value the function returns, such as int, double, or boolean, and you end it with a return statement of that type. A function named square that takes a number and returns its square would be declared as int square(int x) and contain return x times x. The value that comes back can be stored in a variable, so int result = square(5) stores 25 in result, ready to print or use.

Parameters: passing data in

The parentheses after a function name are where parameters go, the values you pass in for the function to work on. In int square(int x), x is a parameter; the name is only used inside the function, so it can be anything you like. Parameters make a function flexible: the same square function works for any number you give it. When you combine parameters and a return value, you get a small, general-purpose tool: give it input, get output, with the logic written once.

Where the real payoff shows up

Consider the blink code you already know: turn the LED on, wait, turn it off, wait. Wrapped in a function that takes a delayTime parameter, it becomes a single reusable command. Now blinkLED(500) blinks with a half-second delay and blinkLED(200) blinks faster, each one line instead of four. The same idea applies to reading a sensor: a readLightLevel function that returns analogRead of the sensor pin turns a hardware detail into a clean, named action you can call in your main loop.

Working through it

Build a reusable blink function. Wire an LED to pin 13 through a 220 ohm resistor. Write void blinkLED(int delayTime) that sets the pin HIGH, waits delayTime, sets it LOW, and waits delayTime again. The parameter makes the blink speed dynamic.

Call it with different speeds. In loop, call blinkLED(500) then blinkLED(200). One function, called with different arguments, produces different behavior, and each call is a single readable line instead of four repeated ones.

Wire the photoresistor for the second project. Add a photoresistor to pin A0 using a voltage divider, keeping the LED on pin 13. You can reuse the same LED circuit from the first project and just add the sensor.

Write a function that returns the light level. Declare int readLightLevel() with a single line, return analogRead(photoPin). It takes no parameters because it only needs to read the pin, and it returns an int so the caller can use the value.

Use the returned value in loop. Store the reading with int lightLevel = readLightLevel(), print it to the Serial Monitor for tuning, and use an if to turn the LED on when lightLevel is below 500 and off otherwise. Add a short delay and repeat. You have rebuilt the nightlight, but the sensor read is now a clean, named function.

A reusable blink function with a parameter