val and var: immutability by default
The very first choice you make on every line that stores something is val or var. It looks
trivial. It is one of the most consequential habits in the language, because it decides whether a
value can change under you later — and "a value changed when I did not expect it to" is behind a
large share of real bugs.
Two keywords
val city = "Pune" // read-only: cannot be reassigned
var count = 0 // mutable: can be reassigned
val(from value) declares a read-only reference. Once set, you cannot point it at something else.var(from variable) declares a mutable reference. You can reassign it as often as you like.
Try to reassign a val and the compiler stops you before the program ever runs:
val city = "Pune"
city = "Mumbai" // error: val cannot be reassigned
error: val cannot be reassigned
That is not an inconvenience — it is the compiler catching, at compile time, a category of mistake that in other languages you only discover at runtime.
The rule: val by default
Reach for val every time. Use var only when you have a specific reason to reassign. This is
idiomatic Kotlin, and it is the opposite of the habit many people bring from languages where
everything is mutable by default.
Why it matters, concretely:
- A
valcannot surprise you. When you readval total = a + b, you knowtotalis that, for the rest of its life. Withvar, you have to scan every line below to check whether something reassigned it. Immutability is readability: it shrinks how much you have to hold in your head. - It prevents a whole class of bug. A value you did not mean to change, changed by code far
away, is a bug that is miserable to find. If it is a
val, that bug is impossible. - It signals intent.
valsays "this does not change";varsays "this is expected to". A reader learns your intentions from the keyword alone.
The instinct to fight is "but I might need to change it later". You almost never do, and if you do,
changing val to var is a one-word edit the compiler will demand anyway. Start with val; let
the compiler tell you when you genuinely need var.
What val does and does not promise
Here is the subtlety that catches people, and it is worth getting straight now. val means the
reference cannot be reassigned. It does not mean the object cannot change.
val list = mutableListOf(1, 2, 3)
list.add(4) // fine! the list changed
println(list) // [1, 2, 3, 4]
list = mutableListOf(5) // error: val cannot be reassigned
list is a val, so you cannot point it at a different list. But the list it points at is itself
mutable, so add works. The val locks the box, not the contents.
This is why "immutable" has two layers in Kotlin, and the collections module returns to it: a
read-only reference (val) is one thing; a genuinely immutable object (like a listOf(...), which
has no add) is another. For now, hold the distinction: val stops reassignment, not mutation.
Type inference — and when to be explicit
Notice none of the examples wrote a type. Kotlin infers it from the value:
val city = "Pune" // inferred String
val count = 0 // inferred Int
val price = 199.99 // inferred Double
val active = true // inferred Boolean
You can write the type, and sometimes should:
val city: String = "Pune"
val total: Long = 0 // you want a Long, not the inferred Int
Let Kotlin infer the type when it is obvious from the value; state it when it is not, or when the
inferred type is not the one you want. A function's return type and a public property are places
where an explicit type aids the reader even when inference would work — but for an ordinary local
val, inference keeps the code clean.
One thing inference cannot do: figure out the type with nothing to infer from. This is an error:
val name // error: this variable must either have a type annotation or be initialized
Either initialise it, or annotate it (and initialise it later, which is a var situation or a
class-property situation you will meet in the classes module).
const — a compile-time constant, briefly
You will see a third form:
const val MAX_RETRIES = 3
const val is a val whose value is known at compile time — a fixed number or string, not the
result of a function call. It is used for true constants (top level or in a companion object,
which the classes module covers). For now: it is a stricter val, and if const gives an error
("const val has no meaning"), you wanted a plain val.
The habit, stated once
Every time you type var, pause for one second and ask: does this genuinely need to change? Most
of the time the honest answer is no, and it becomes a val. That one-second habit, applied
thousands of times, is a large part of what makes code readable and correct. It is the smallest
good habit in the language and one of the highest-value.
Check your work
val versus var. val is a read-only reference (cannot be reassigned); var is mutable.
When the error appears for reassigning a val. At compile time — before the program runs.
The default to reach for, and why. val — it cannot surprise you, prevents a class of bug, and
signals intent; use var only with a specific reason.
What val does not promise. That the object cannot change — only the reference. A val mutableListOf can still be added to.
The two layers of immutability. A read-only reference (val) versus a genuinely immutable
object (like listOf).
What type inference does. Deduces the type from the value, so you need not write it.
When to write the type explicitly. When it is not obvious, when the inferred type is not what you want, and for public/return types as a courtesy to the reader.
Why val name alone is an error. Nothing to infer from — it must be typed or initialised.
What const val adds. A value known at compile time — a true constant.
Practice
- Declare a
valand try to reassign it. Read the exact compiler error. - Change it to
varand confirm the reassignment now works. - Create a
valpointing at amutableListOf(1, 2, 3),add(4)to it, and confirm it changed — then try to reassign thevalitself and watch that fail. - Declare five
vals of different types with no annotation and confirm inference gets each right (hover in the IDE, or print and reason). - Declare
val total: Long = 0and aval n = 0, and check (in the IDE) that their types differ. - Write
val namewith no value and read the error. Then fix it two ways: annotate, and initialise. - Go through any code you have and count
varversusval. For eachvar, decide honestly whether it needed to be one.
Official documentation
- Kotlin — Basic syntax: variables —
valandvarin the language's own words. - Kotlin — Properties — How
val/varbehave as class members, for later. - Kotlin — Coding conventions — Including the guidance to prefer
val.
Next: the basic types, and the inference you just met, in full.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship