Pure functions & side effects in Java
Why methods that don't mutate state are easier to reason about.
Like a calculator button
A pure function's result depends only on its arguments, and it changes nothing outside itself. Same input, same output, every single time. Math.max(3, 7) is pure: it will be 7 today, tomorrow and forever.
static int add(int a, int b) { // pure
return a + b;
}
static int square(int x) { // pure
return x * x;
}Side effects
A side effect is anything a method does that's visible outside, besides returning a value: printing, changing fields, mutating its arguments, writing files or calling the network. Math.random() isn't pure either: it returns something different each call.
static int total = 0;
static void addToTotal(int x) {
total += x; // changes a field!
}Same input, twice
What prints?
static int bonus = 0;
static int score(int base) {
bonus += 5;
return base + bonus;
}
void main() {
System.out.println(score(10));
System.out.println(score(10));
}15 1515 2010 1020 20
Show the answer
The method changes and reads shared state: bonus becomes 5, then 10. So the same argument gives 15 the first time and 20 the second. Not pure!
Is printing a side effect?
A method returns the right answer but also prints a log line. Is it pure?
Think about it, then reveal the answer
No. Printing changes something outside the method (the console), so it's a side effect, even if the return value is perfect. Side effects aren't bad; just keep them obvious and contained.
The hidden side effect
A teammate's sortAndSum(data) returns the sum but also sorts the caller's array. Later code that relied on the original order breaks, far from the cause. The fix: sort a copy (data.clone()), or name and document the effect clearly.
static int sortAndSum(int[] data) {
Arrays.sort(data); // sorts caller's!
int sum = 0;
for (int x : data) sum += x;
return sum;
}Keeping state out
static int calls = 0;
static int addCalls(int x) {
calls++;
return x + calls;
}Result depends on how often it was called before.
static int add(int x, int y) {
return x + y;
}Everything it needs comes in as arguments; the result is all it gives back.
In real projects
Pure functions are a joy to unit-test: give input, check output, no fields, files or console to set up or inspect. (They can still throw on bad input.) They're also safe to call any number of times and from many threads at once, which is why modern Java style leans toward them.
Key takeaways
- Pure: output depends only on input, with no visible side effects
- Side effects: printing, changing fields, mutating arguments, I/O
- Pure methods are easy to test and safe to call any number of times
- Math.max is pure; Math.random is not
💡 A pure function is like a calculator: the same keys give the same answer, and pressing them doesn't rearrange your desk.
Java 8's java.time classes are immutable: date.plusDays(1) returns a new date instead of changing the old one. It was a deliberate fix for the old, mutable java.util.Date.
Practice questions
Which method is NOT pure?
- static int square(int x) { return x * x; }
- static int abs(int x) { return x < 0 ? -x : x; }
- static int next() { return ++counter; }
- static String twice(String s) { return s + s; }
Check your answer
static int next() { return ++counter; }. next() changes a field (counter) and returns a different value every call. The others depend only on their argument.
What does this print?
static int calls = 0;
static int addCalls(int x) {
calls++;
return x + calls;
}
void main() {
System.out.println(addCalls(10));
System.out.println(addCalls(10));
}- 11 11
- 10 10
- 11 12
- Compile error
Check your answer
11 12. The method reads and changes a shared static variable, so the same argument gives 11 the first time and 12 the second.