Functional Programming in Kotlin

Thu, 22 Jan 2026 12:47:00 +0000
Functional Programming, Kotlin, Programming Languages

When I think about functional programming, three things come to mind: immutability, monads, and side effects. Immutability has earned its reputation - it genuinely simplifies reasoning about code and makes concurrency safer. Monads? They're powerful but intimidating (if you're not sure what they are, here's a great explainer). And side effects? Every useful program has them. It reads files, calls APIs, and writes to databases. The question isn't how to eliminate them, but how to keep them under control.

Kotlin promises multi-paradigm support, and FP is part of that promise. But how much actually holds up? Can you write idiomatic functional Kotlin without external libraries?

Who is this blog post for?

New to functional programming? Start with our introduction to FP concepts. This post builds on that foundation. If you already know what immutability and higher-order functions are, keep reading. I won't be explaining the map and filter. If you're wondering how functional Kotlin really is, or you're just tired of the "it depends" answer, you're in the right place. If you're already deep into effect systems, you probably know most of this, but the casual vs. pure FP framing might still be useful.

What makes a language "functional"?

Before we judge Kotlin, we need to define what we're measuring against. Functional programming isn't a binary checkbox. It's more like a spectrum, with "I use map sometimes" on one end and "everything is an IO monad" on the other.

A useful distinction comes from the blog mentioned earlier: there's casual FP, and there's pure FP. Casual FP means you treat functions as first-class citizens (pass them as arguments, return them from other functions), embrace immutability, use higher-order functions, model your domain with algebraic data types, and generally prefer expressions over statements. Pure FP goes further: side effects are tracked in the type system, referential transparency is enforced, and your entire program is essentially a composition of pure functions.

An interesting insight? Most of the practical benefits come from crossing into casual FP territory. Going from imperative to casual FP is a bigger leap than going from casual to pure. You get cleaner code, fewer bugs, and better testability. The jump to pure FP gives you mathematical guarantees, but the ROI is debatable for most business applications.

Casual FP: Where Kotlin delivers

Let's start with what Kotlin offers for casual functional programming. This covers the fundamentals that most teams need: immutability, pure functions, higher-order functions, and algebraic data types.

Immutability

Kotlin distinguishes between mutable and immutable collections and encourages immutability through language design.

val users = listOf(
    User("Alice", 28),
    User("Bob", 35)
)

listOf() creates an immutable list. You can't add, remove, or modify elements after creation.

val updatedUsers = users + User("Charlie", 42)

Adding an element returns a new list, leaving the original unchanged.

data class User(val name: String, val age: Int)

data class with val properties is immutable by default. Combined with the copy() method, this makes immutable domain modeling natural.

While Kotlin doesn't enforce...