If you’re a developer, your main job whether you realize it or not is to make your programs predictable.
Think about the last bug you ran into. Chances are, something happened that you didn’t expect. Your assumptions didn’t match what the program actually did. That’s what unpredictability looks like in code.
Now imagine if your code always did exactly what you expected. No bugs. No surprises. Just reliable behavior.
That’s what this post is about: writing predictable functions, the smallest building blocks of any app.
First, What Is a Function?
A function is just a piece of code that takes input (called arguments) and gives back an output (called a return value).
function add(a, b) {
return a + b;
}
That’s simple, right?
But real-world functions often turn messy. They start small and clean, but as your app grows, so do your functions and eventually, they become hard to understand and debug.
Let’s avoid that.
Rule #1: Avoid Side Effects
A function should only do one thing: take input and return output. If it does anything else, that’s called a side effect.
❌ Side effect example:
let count = 0;
function increment() {
count++;
}
This function relies on count
, which is defined outside the function. It doesn’t return anything either. That’s a side effect.
✅ No side effect version:
function increment(count) {
return count + 1;
}
Now it’s clear: you give it a number, it gives you the result. No surprises.
Here are other common side effects:
- Fetching data from an API
- Changing the DOM (
document.title = 'New Title'
) - Modifying global variables
Side effects aren’t evil sometimes they’re necessary but they make code unpredictable. So you should isolate them as much as possible.
Rule #2: Consistent Outputs
A predictable function always gives you the same result for the same input.
❌ Unpredictable function:
function getRandomNumber() {
return Math.random();
}
Every time you call this, you get something different.
✅ Predictable function:
function square(n) {
return n * n;
}
square(4)
will always give you 16. That’s consistency.
Why This Matters
Writing functions that follow these two rules no side effects and consistent outputs gives you a bunch of benefits:
✅ Easier to test You can just say:
expect(square(4)).toBe(16);
No setup, no mocking, no magic.
✅ Easier to reuse Predictable functions don’t rely on app state or the DOM. You can use them anywhere frontend, backend, tests and they’ll behave the same.
✅ Easier to read When everything a function needs is passed in, and everything it does is returned out, it’s like reading a recipe with all the ingredients listed. No hidden steps.
But What About Real-World Apps?
We can’t avoid side effects forever. You need to fetch data, update the UI, and interact with the outside world. That’s OK.
The trick is to isolate those parts.
For example, here’s a function that breaks both rules:
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(res => res.json());
}
It has a side effect (a network request), and the output is inconsistent (network speed, failure, data changes). So what do we do?
We wrap it in a module, handle errors inside, and expose a clear interface:
// userService.js
export async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
return await res.json();
} catch (e) {
return null;
}
}
Now you’ve contained the unpredictability.
A Real Example: isEven Function
Let’s look at a simple, pure function that checks if a number is even.
function isEven(n) {
return n % 2 === 0;
}
It follows both rules:
✅ No side effects
✅ Always returns the same result for the same input
You can easily test it, reuse it, and trust it.
Wrapping Up
Writing predictable code means:
- No side effects
- Consistent outputs
Functions that follow these rules are easier to test, reuse, and debug and they make your whole app more reliable.
This approach is the foundation of functional programming, and it’s also why libraries like React are designed the way they are. (More on that next time 👀)