val, var & Type Inference
Declaring variables: val (immutable, like final) vs var, and type inference that drops the ceremony without losing static typing - immutability as the default habit.
On this page
Let's start writing Kotlin. The very first thing you type - declaring a variable - already shows Kotlin's
personality: immutability is the default, and the compiler figures out types so you don't repeat yourself.
Two keywords, val and var, replace Java's final-or-not distinction, and type inference removes the
noise. It's a small change you'll use in every line, and it nudges you toward safer code.
val vs var: immutable by default
Kotlin has exactly two ways to declare a variable:
val(value) - a read-only reference, assigned once. It's like Java'sfinal: you can't reassign it.var(variable) - a mutable reference you can reassign, like a plain Java variable.
val name = "Ada" // read-only: reassigning is a compile error
var count = 0 // mutable: count = 1 is fine
count = 1 // OK
// name = "Grace" // ERROR: val cannot be reassignedCompare the Java you'd write for the same intent:
final String name = "Ada"; // Kotlin: val name = "Ada"
int count = 0; // Kotlin: var count = 0The difference is cultural: in Java, final is extra typing most people skip, so mutability is the lazy default.
In Kotlin, val is the shorter word, so immutability is the path of least resistance. You reach for val
first and only switch to var when you truly need to reassign - which, in practice, is less often than you'd
think.
Type inference: say it once
Kotlin is statically typed - every variable has a fixed type checked at compile time, exactly like Java. But you rarely write the type, because the compiler infers it from the value:
val name = "Ada" // inferred: String
val count = 0 // inferred: Int
val price = 19.99 // inferred: Double
val ready = true // inferred: BooleanThis is not dynamic typing (like JavaScript or Python) - name is a String forever, and name = 5 is a
compile error. It's the same idea as Java's var (since Java 10), but Kotlin uses it pervasively. You can write
the type explicitly when it aids clarity or when there's no initializer:
val total: Double = 0.0 // explicit type for clarity
val user: User = fetchUser() // explicit when the inferred type isn't obvious at a glance
var result: String // required when there's no initializer yetThe guideline: let inference handle the obvious cases (a String literal is clearly a String), and annotate when
the type isn't obvious from the right-hand side or when you want to document intent.
Why immutability-first matters
Defaulting to val isn't just tidiness - it prevents a whole class of bugs. An immutable reference can't be
accidentally reassigned halfway through a method, can't be mutated by another thread, and makes code easier to
reason about (once set, it stays). It's the same lesson as value objects and final fields from the Java and
architecture paths - Kotlin just makes the safe choice the default choice, so you fall into good habits without
effort.
val controls the reference, not the object
A subtlety Java developers know from final: val makes the reference read-only, not the object it points to. A
val list = mutableListOf(1, 2) can't be reassigned to a different list, but you can still list.add(3) - the
reference is fixed, the contents aren't. For deep immutability you also need an immutable type (a read-only
List, a data class with val properties). Kotlin's collections make this explicit with separate List (read-only)
and MutableList types - more on that in the collections module.
Declaring a variable is putting a label on a jar. var is a jar with a resealable lid: you can pour out its
contents and refill it with something else later (reassign). val is a jar you seal once filled - the label
permanently names this content, and you can't swap what's inside for something different (no reassignment).
Kotlin hands you the sealed jar by default and makes you ask for the resealable one, because most things you label
never need refilling - and a sealed jar can't be accidentally emptied by someone reaching past you (another
thread). Type inference is just the shopkeeper reading the label off the contents so you don't have to write 'this
jar contains jam' when you literally just poured jam into it.
Rewrite these Java declarations in idiomatic Kotlin, choosing val or var appropriately and using type inference
where sensible: (1) final String greeting = "hi"; (2) int attempts = 0; that gets incremented in a loop; (3)
final List<String> names = new ArrayList<>(); that has items added to it later. For (3), explain the subtlety.
What's the difference between val and var in Kotlin, and how does it compare to Java?
Key takeaways
- Kotlin has two variable keywords: val (read-only reference, like Java's final) and var (reassignable) - and val is the natural default, so immutability comes for free.
- Kotlin is statically typed with pervasive type inference: the compiler infers the type from the value, so you rarely write it - but it's fixed at compile time, not dynamic.
- Write explicit types when there's no initializer or when the inferred type isn't obvious at a glance; let inference handle the clear cases.
- Defaulting to val prevents a class of bugs (accidental reassignment, cross-thread mutation) - the same value-object lesson, made the default.
- val fixes the reference, not the object: a val mutable collection can still have items added; deep immutability needs an immutable type.