When I first encountered val and var in Kotlin, they seemed trivial. Three letters each. Just two keywords for declaring variables, right?
Here's the thing though: behind those three letters lies one of the most powerful ideas in software engineering. Immutability — the principle that once a value is set, it stays set.
Think of it this way: val is a locked safe. You put something inside, close the door, and that's it. Nobody can change what's in there. var is an open mailbox — anyone walking by can swap the contents.
// ❌ Before: var — anyone can reassign
var name = "Pavel"
name = "Alex" // Sure, why not?
name = "Sandra" // Changed again...
name = null // Wait, now it's null?!
// ✅ After: val — set once, done
val name = "Pavel"
name = "Alex" // Compilation error! The compiler stops you.When you see val in code, you know something powerful: this value will not change. No need to scroll down 50 lines to check if someone reassigned it. No need to trace through three functions to see if it got mutated. The value is what it says it is.
With var, every time you read the variable, you have to wonder: "Is this still the same value I saw 10 lines ago?" That cognitive load adds up. Fast.
This is predictability — and it's not just a nice-to-have. It's the difference between code you can reason about in your head and code that requires a debugger to understand.
Here's where val goes from "nice convention" to "real superpower": thread safety.
Imagine two threads running at the same time, both reading a variable. If it's a val, both threads see the same value. Always. No locks needed, no synchronization, no race conditions. The data is immutable, so there's nothing to fight over.
But if it's a var? Thread A reads the value. Thread B changes it. Thread A uses the old value. Boom — a bug that only appears under load, at 3 AM, in production. These are the worst bugs to find.
The diagram below shows exactly why.
Real talk: In my Kotlin codebase, about 95% of variables are val. When I see var, I treat it as a red flag — something that needs justification. Why does this need to change? Could I restructure the code to avoid it?
This single habit — defaulting to val — eliminates entire categories of bugs.
So when should you use var? Honestly, not often. But there are legitimate cases:
- Loop counters (
for (i in 0..n)— though Kotlin'sforoften avoids this) - State machines that must mutate
- Performance-critical code where object allocation matters
- Building something in stages where you can't use a constructor
The key is intentionality. Don't use var out of habit. Use it because you understand why this specific value needs to change. And when you do, add a comment explaining why.
Start with val. Let the compiler tell you when you actually need var.