..

Dozie

Functional Programming 101

What it actually means to write functions that don't surprise you

Most people encounter functional programming through someone who is too excited about monads. That is not where this post starts.

Functional programming is not a religion or a paradigm war. It is a set of constraints that, when applied with judgment, make code dramatically easier to reason about. That matters more as systems grow.

What a pure function actually is

A pure function does two things. It always returns the same output for the same input. And it does not touch anything outside itself no database writes, no global state, no logging side effects.

That second part is the one people underestimate. A function that reads from a database is not pure. A function that mutates a shared object is not pure. A function that sends an email inside a calculation is definitely not pure.

Pure functions are boring in the best way. You can test them without setting up infrastructure. You can read them and know exactly what they do. You can move them around without breaking something three files away.

let discount = 0.1;

function getPrice(amount: number): number {
  return amount - amount * discount;
}

Immutability

Immutability means you do not change data you create new data. Instead of modifying an object in place, you return a new one with the change applied.

This sounds wasteful until you realize how many bugs come from data being mutated somewhere you did not expect. Immutability makes the flow of data explicit. You always know where a value came from and that it has not been secretly changed by someone else.

In backend systems this becomes relevant quickly. Shared mutable state is one of the most common sources of subtle, hard-to-reproduce bugs in concurrent code.

Higher-order functions

A higher-order function takes a function as an argument or returns one. Map, filter, and reduce are the classic examples. They are not special — they are just functions that operate on behavior rather than data.

The useful insight here is that separating the iteration from the operation makes code composable. Instead of writing a for-loop with logic baked in, you write a small function that describes the transformation, and pass it to something that handles the iteration. Each piece is testable on its own.

Composition

Composition is the idea that you build complex behavior by chaining simple functions together. The output of one becomes the input of the next. Each function does one thing. Together they do something useful.

This is how functional code scales without becoming hard to follow. You do not write one large function that does ten things. You write ten small functions and compose them.

Why this matters for backend work

Pure functions and immutability do not mean you avoid side effects entirely — a backend that never touches a database is not useful. The point is to isolate side effects. Keep the business logic pure and push the side effects to the edges of the system.

This separation makes the core logic of your application easy to test without mocking an entire database. It makes bugs easier to find because you can reason about each piece independently. And it makes the system easier to change because pure functions have no hidden dependencies.

The practical takeaway

You do not need to adopt a fully functional language to get the benefits. Write functions that do one thing. Avoid mutating data that is passed in. Return new values instead of changing existing ones. Push database calls and external effects to the edges.

That is most of what functional programming gives you. The rest is details.

11