Functions

Beginner40 min

Break a problem into named, reusable pieces with clear inputs and outputs, and see how calls stack up while a program runs.

What you will learn
  • Define and call a function with parameters and a return value
  • Distinguish parameters from arguments, and returning a value from printing it
  • Explain side effects and recognise a pure function
  • Describe the call stack and what happens when functions call each other
On this page

Suppose you convert a temperature in one place, and a week later you need the same conversion somewhere else. Copying the formula works until you find a mistake in it and have to fix it in six places. A function is a named block of code you write once and use as often as you like. It is the main tool for breaking a large problem into small pieces that you can understand, test and reuse one at a time.

Anatomy of a function

A function has a name, a list of parameters (the inputs it expects), a body, and usually a return value (its output).

  • A parameter is the placeholder name in the definition: celsius in to_fahrenheit(celsius).
  • An argument is the actual value you supply in a call: the 100 in to_fahrenheit(100).

When you call a function, the arguments are matched to the parameters, the body runs, and the call expression is replaced by the returned value. That last point is why you can write print(to_fahrenheit(100)) or to_fahrenheit(c) + 1: a call is an expression.

call: to_fahrenheit(100)
|
v argument 100 is matched to parameter celsius
body: return celsius * 9 / 5 + 32 -> 212.0
|
v the call is replaced by the returned value
use: print(212.0)

Python

functions_basic.py
def to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
for c in [100, 37, -40]:
print(f"{c} C = {to_fahrenheit(c):.1f} F")

JavaScript

functions_basic.js
function toFahrenheit(celsius) {
return celsius * 9 / 5 + 32;
}
for (const c of [100, 37, -40]) {
console.log(`${c} C = ${toFahrenheit(c).toFixed(1)} F`);
}

Java

FunctionsBasic.java
public class FunctionsBasic {
static double toFahrenheit(double celsius) {
return celsius * 9 / 5 + 32;
}
public static void main(String[] args) {
int[] temps = {100, 37, -40};
for (int c : temps) {
System.out.printf("%d C = %.1f F%n", c, toFahrenheit(c));
}
}
}

C

functions_basic.c
#include <stdio.h>
double to_fahrenheit(double celsius) {
return celsius * 9 / 5 + 32;
}
int main(void) {
int temps[] = {100, 37, -40};
for (int i = 0; i < 3; i++) {
printf("%d C = %.1f F\n", temps[i], to_fahrenheit(temps[i]));
}
return 0;
}

All four print:

100 C = 212.0 F
37 C = 98.6 F
-40 C = -40.0 F

Python and JavaScript need no type declarations. Java and C name the type of every parameter and of the return value (double here), and C requires a function to be defined (or declared) before the code that calls it. In Java and C, the main function is where the program starts. See values and types for why those declarations exist.

Return versus print

New programmers often mix these up. Printing sends text to the screen for a human to read, and the program cannot use it afterwards. return hands a value back to the calling code, which can store it, compute with it or print it. A function that computes should normally return, and the caller decides what to do with the result. That makes the function reusable: the same to_fahrenheit works in a program that prints, one that draws a chart and one that writes a file.

A function with no return statement gives back a special empty value, and the languages handle that differently.

Python

shout.py
def shout(text):
print(text.upper())
result = shout("hi")
print(result)
Output
HI
None

A function with no return gives back None.

JavaScript

shout.js
function shout(text) {
console.log(text.toUpperCase());
}
const result = shout("hi");
console.log(result);
Output
HI
undefined

A function with no return gives back undefined.

Java

Shout.java
public class Shout {
static void shout(String text) {
System.out.println(text.toUpperCase());
}
public static void main(String[] args) {
shout("hi");
// String result = shout("hi"); // compile error: a void method has no value
}
}
Output
HI

The return type void says “no value”. Trying to store the result is a compile error, so the mistake cannot happen at run time.

C

shout.c
#include <ctype.h>
#include <stdio.h>
void shout(const char *text) {
for (; *text; text++) {
putchar(toupper((unsigned char)*text));
}
putchar('\n');
}
int main(void) {
shout("hi");
/* int result = shout("hi"); compile error: void value not ignored */
return 0;
}
Output
HI

C also uses void, and storing a void result is a compile error.

Side effects and pure functions

A side effect is any change a function makes beyond returning a value: printing, modifying a global variable, writing a file, changing an argument that was passed in. A pure function has none, and its result depends only on its arguments. Call it twice with the same arguments and you get the same answer.

In this program add is pure. add_and_count returns the same number but also bumps a counter that lives outside the function.

Python

pure_impure.py
calls = 0
def add(a, b):
return a + b
def add_and_count(a, b):
global calls # lets the function change the outer variable; see the scope lesson
calls += 1
return a + b
print(add(2, 3), add(2, 3))
print(add_and_count(2, 3))
print(add_and_count(2, 3))
print("calls:", calls)

JavaScript

pure_impure.js
let calls = 0;
function add(a, b) {
return a + b;
}
function addAndCount(a, b) {
calls += 1;
return a + b;
}
console.log(add(2, 3), add(2, 3));
console.log(addAndCount(2, 3));
console.log(addAndCount(2, 3));
console.log("calls:", calls);

Java

PureImpure.java
public class PureImpure {
static int calls = 0;
static int add(int a, int b) {
return a + b;
}
static int addAndCount(int a, int b) {
calls += 1;
return a + b;
}
public static void main(String[] args) {
System.out.println(add(2, 3) + " " + add(2, 3));
System.out.println(addAndCount(2, 3));
System.out.println(addAndCount(2, 3));
System.out.println("calls: " + calls);
}
}

C

pure_impure.c
#include <stdio.h>
static int calls = 0;
static int add(int a, int b) {
return a + b;
}
static int add_and_count(int a, int b) {
calls += 1;
return a + b;
}
int main(void) {
printf("%d %d\n", add(2, 3), add(2, 3));
printf("%d\n", add_and_count(2, 3));
printf("%d\n", add_and_count(2, 3));
printf("calls: %d\n", calls);
return 0;
}

All four print:

5 5
5
5
calls: 2

add leaves nothing behind. add_and_count returns the same number but also changes calls, so calling it has a visible effect elsewhere. Neither is wrong; programs need side effects, since printing is one. But pure functions are far easier to test and to reason about, so a good habit is to keep the calculating parts pure and push the printing and file-writing to the edges.

The call stack

When a function calls another, the first must wait for the second to finish. The computer tracks this with the call stack: a pile of frames, one per active call, each holding that call’s parameters and local variables. A new call pushes a frame on top; a return pops it off and execution resumes in the frame below.

Here three functions call each other in a chain. Each prints a line when it starts and when it ends, indented to show its depth.

Python

call_stack.py
def outer():
print("outer starts")
middle()
print("outer ends")
def middle():
print(" middle starts")
inner()
print(" middle ends")
def inner():
print(" inner runs")
outer()

JavaScript

call_stack.js
function outer() {
console.log("outer starts");
middle();
console.log("outer ends");
}
function middle() {
console.log(" middle starts");
inner();
console.log(" middle ends");
}
function inner() {
console.log(" inner runs");
}
outer();

Java

CallStack.java
public class CallStack {
static void outer() {
System.out.println("outer starts");
middle();
System.out.println("outer ends");
}
static void middle() {
System.out.println(" middle starts");
inner();
System.out.println(" middle ends");
}
static void inner() {
System.out.println(" inner runs");
}
public static void main(String[] args) {
outer();
}
}

C

call_stack.c
#include <stdio.h>
static void inner(void) {
printf(" inner runs\n");
}
static void middle(void) {
printf(" middle starts\n");
inner();
printf(" middle ends\n");
}
static void outer(void) {
printf("outer starts\n");
middle();
printf("outer ends\n");
}
int main(void) {
outer();
return 0;
}

C needs each function defined before it is called, so the order is reversed: inner first, outer last.

All four print:

outer starts
middle starts
inner runs
middle ends
outer ends

Here is the stack at five moments. Read each column from the bottom up.

(1) (2) (3) (4) (5)
inner
middle middle middle
outer outer outer outer outer
main main main main main

In (1) main has called outer. In (2) outer has called middle, and in (3) middle has called inner, the deepest point. When inner returns, its frame is removed (4), then middle’s (5), and outer carries on. This explains the order of the output (outer starts, middle starts, inner runs, middle ends, outer ends) and, later, what a stack trace in an error message is showing you: the pile of frames at the moment something went wrong.

Designing good functions

  • Do one thing. If you struggle to name it without using “and”, split it.
  • Name it for what it does: to_fahrenheit, not convert or func1.
  • Keep the inputs explicit. Pass in what the function needs rather than having it reach for global variables.
  • Prefer returning to printing, as described above.
  • Keep it short enough to read at a glance. There is no magic number, but if you must scroll, look for a piece to extract.

Common pitfalls

  • Printing instead of returning, then trying to use the result and finding it empty (None or undefined).
  • Forgetting the parentheses. to_fahrenheit refers to the function; to_fahrenheit(100) calls it.
  • Mismatched argument counts or order. report("Ana", scores) and report(scores, "Ana") are different calls.
  • Expecting a function to change your variable. In most languages numbers and strings passed in are copies; reassigning the parameter inside does not affect the caller. (More on this in the memory lesson later in the track.)
  • Code after return that never runs. return ends the function immediately.
  • Hidden dependencies. A function that quietly reads a global behaves differently depending on something not visible in its call.
ExerciseRefactor a script into functions

This script computes an average and a letter grade for three students. The same block of code appears three times, once per student. Rewrite it using three functions, average, letter_grade and report, so that each rule appears once.

The data: Ana scored 80, 90, 70; Ben scored 60, 65, 90; Chi scored 95, 92, 98. A grade is A for an average of at least 85, B for at least 70, otherwise C. Expected output:

Ana 80.0 B
Ben 71.7 B
Chi 95.0 A

Here is the block for Ana in each language. The blocks for Ben and Chi are copies with different numbers and names.

Python

a = (80 + 90 + 70) / 3
if a >= 85:
ga = "A"
elif a >= 70:
ga = "B"
else:
ga = "C"
print("Ana", f"{a:.1f}", ga)

JavaScript

const a = (80 + 90 + 70) / 3;
let ga;
if (a >= 85) {
ga = "A";
} else if (a >= 70) {
ga = "B";
} else {
ga = "C";
}
console.log("Ana", a.toFixed(1), ga);

Java

double a = (80 + 90 + 70) / 3.0;
String ga;
if (a >= 85) {
ga = "A";
} else if (a >= 70) {
ga = "B";
} else {
ga = "C";
}
System.out.printf("Ana %.1f %s%n", a, ga);

C

double a = (80 + 90 + 70) / 3.0;
const char *ga;
if (a >= 85) {
ga = "A";
} else if (a >= 70) {
ga = "B";
} else {
ga = "C";
}
printf("Ana %.1f %s\n", a, ga);
Show solution

Each function does one job. average and letter_grade are pure; only report prints.

Python

functions_refactored.py
def average(scores):
return sum(scores) / len(scores)
def letter_grade(avg):
if avg >= 85:
return "A"
if avg >= 70:
return "B"
return "C"
def report(name, scores):
avg = average(scores)
print(name, f"{avg:.1f}", letter_grade(avg))
report("Ana", [80, 90, 70])
report("Ben", [60, 65, 90])
report("Chi", [95, 92, 98])

JavaScript

functions_refactored.js
function average(scores) {
let sum = 0;
for (const s of scores) {
sum += s;
}
return sum / scores.length;
}
function letterGrade(avg) {
if (avg >= 85) {
return "A";
}
if (avg >= 70) {
return "B";
}
return "C";
}
function report(name, scores) {
const avg = average(scores);
console.log(name, avg.toFixed(1), letterGrade(avg));
}
report("Ana", [80, 90, 70]);
report("Ben", [60, 65, 90]);
report("Chi", [95, 92, 98]);

Java

FunctionsRefactored.java
public class FunctionsRefactored {
static double average(int[] scores) {
int sum = 0;
for (int s : scores) {
sum += s;
}
return (double) sum / scores.length;
}
static String letterGrade(double avg) {
if (avg >= 85) {
return "A";
}
if (avg >= 70) {
return "B";
}
return "C";
}
static void report(String name, int[] scores) {
double avg = average(scores);
System.out.printf("%s %.1f %s%n", name, avg, letterGrade(avg));
}
public static void main(String[] args) {
report("Ana", new int[] {80, 90, 70});
report("Ben", new int[] {60, 65, 90});
report("Chi", new int[] {95, 92, 98});
}
}

C

functions_refactored.c
#include <stdio.h>
static double average(const int *scores, int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += scores[i];
}
return (double)sum / count;
}
static const char *letter_grade(double avg) {
if (avg >= 85) {
return "A";
}
if (avg >= 70) {
return "B";
}
return "C";
}
static void report(const char *name, const int *scores, int count) {
double avg = average(scores, count);
printf("%s %.1f %s\n", name, avg, letter_grade(avg));
}
int main(void) {
int ana[] = {80, 90, 70};
int ben[] = {60, 65, 90};
int chi[] = {95, 92, 98};
report("Ana", ana, 3);
report("Ben", ben, 3);
report("Chi", chi, 3);
return 0;
}

In C an array does not carry its length, so each function also takes a count.

All four print the same three lines as the original script, which is the test that a refactoring has changed the structure but not the behaviour:

Ana 80.0 B
Ben 71.7 B
Chi 95.0 A

If you now want to change the grade boundary from 85 to 90, there is exactly one place to edit instead of three.

ExerciseReturn or print?

What does result hold after this code runs, and why? Expected: say what is printed, and what result holds, in Python and in JavaScript.

Python

def double(n):
print(n * 2)
result = double(4)

JavaScript

function double(n) {
console.log(n * 2);
}
const result = double(4);

Java

static void doubleIt(int n) {
System.out.println(n * 2);
}
// int result = doubleIt(4); // does this compile?

C

void double_it(int n) {
printf("%d\n", n * 2);
}
/* int result = double_it(4); does this compile? */
Show solution

In Python result is None, and in JavaScript it is undefined. Either way, 8 is printed to the screen but the function never returns anything, so the call expression has no value to store. In Java and C the line with result does not compile, because a void function has no value to assign. Replace the print with return n * 2 (and change void to int in Java and C) to get 8 into result.

ExerciseDraw the stack

Draw the call stack at the deepest point of this program, and say what it prints. There is no input. Expected: three lines.

Python

stack_abc.py
def c():
print("c")
def b():
c()
print("b")
def a():
b()
print("a")
a()

JavaScript

stack_abc.js
function c() {
console.log("c");
}
function b() {
c();
console.log("b");
}
function a() {
b();
console.log("a");
}
a();

Java

StackAbc.java
public class StackAbc {
static void c() {
System.out.println("c");
}
static void b() {
c();
System.out.println("b");
}
static void a() {
b();
System.out.println("a");
}
public static void main(String[] args) {
a();
}
}

C

stack_abc.c
#include <stdio.h>
static void c(void) {
printf("c\n");
}
static void b(void) {
c();
printf("b\n");
}
static void a(void) {
b();
printf("a\n");
}
int main(void) {
a();
return 0;
}
Show solution

At the deepest point the stack holds, from bottom to top: the main program, a, b, c. Then c prints and returns, then b prints and returns, then a prints and returns. The output is c, b, a, on separate lines: the innermost call finishes first.

Summary

  • A function is a named, reusable block with parameters (placeholders) that receive arguments (actual values).
  • Return a value from a computing function and let the caller decide whether to print it.
  • Side effects are changes beyond the return value; pure functions have none and are easiest to test.
  • Calls form a stack of frames: the most recent call finishes first.

Next: Scope and lifetime, which answers which variables a function can see.