Variables (val & var)
Reviewed & published by Brayan K
Kotlin is a modern, concise language that gives you two ways to name data: val for read-only values and var for ones you can change.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll know exactly when to reach for each, how type inference saves you keystrokes, and how Kotlin's basic types — Int, Double, Boolean, Char, and String — work.
What You'll Learn in This Lesson
- The difference between val (read-only) and var (mutable)
- Why idiomatic Kotlin prefers val
- How type inference removes the need for explicit types
- Kotlin's basic types: Int, Long, Double, Boolean, Char, String
- How to convert between number types explicitly
- That val freezes the reference, not the object
1️⃣ val vs var
Kotlin has exactly two keywords for declaring variables. val creates a read-only reference that can be assigned once. var creates a mutable reference you can reassign as many times as you like. The rule of thumb: prefer val, and switch to var only when you must.
fun main() {
// val = read-only ("value"). You can NEVER reassign it.
val name = "Ada"
println(name) // -> Ada
// var = mutable ("variable"). You CAN reassign it.
var score = 10
println(score) // -> 10
score = 25 // reassigning a var is fine
println(score) // -> 25
// Trying to reassign a val is a COMPILE error:
// name = "Grace" // ❌ Val cannot be reassigned
// Note: val freezes the variable, not the object it points to.
val items = mutableListOf("a", "b")
items.add("c") // ✅ allowed — the LIST changed, not the val
println(items) // -> [a, b, c]
}The last two lines are the subtle part: val items can't point at a different list, but the list itself is mutable, so add still works. val freezes the reference, not the contents.
2️⃣ Types and Type Inference
Kotlin is statically typed — every variable has a type — but you rarely have to write it. The compiler infers the type from the value. When you do want to be explicit, add a colon and the type after the name. Kotlin's basic types include Int, Long, Double, Boolean, Char, and String.
fun main() {
// Kotlin INFERS the type, but you can write it explicitly after a colon.
val country: String = "Norway" // text
val population: Int = 5_400_000 // whole number (underscores aid reading)
val area: Double = 385207.0 // decimal number
val isNordic: Boolean = true // true or false
val initial: Char = 'N' // a single character in 'single quotes'
println("$country has $population people.")
println("Area: $area km², Nordic: $isNordic, Initial: $initial")
// Numbers have helpful conversion functions (no implicit widening):
val asLong: Long = population.toLong()
val asDouble: Double = population.toDouble()
println("As Long: $asLong, as Double: $asDouble")
}Notice Kotlin won't silently widen numbers: to treat an Int as a Double you call .toDouble(). This prevents accidental precision loss.
Your turn. Fill in the TODOs, following the 👉 hints, then run and compare.
// 🎯 YOUR TURN — replace each TODO then run main()
fun main() {
// 1) A read-only variable for your favourite language
val language = "TODO" // 👉 use val, put text in quotes
// 2) A mutable counter that starts at 0
var clicks = 0 // 👉 this should be a var so we can change it
// 3) Increase the counter twice
clicks = clicks + 1
clicks += 1 // shorthand for clicks = clicks + 1
// 4) Print both values
println("Language: $language, clicks: $clicks")
// ✅ Expected (example): Language: Kotlin, clicks: 2
}Mini-Challenge: A Shopping Total
Use what you've learned about val, var, and types to build a small total. Write it yourself and check it against the example output.
// 🎯 MINI-CHALLENGE: a tiny shopping total.
// Save as Cart.kt and run main().
//
// 1. Make a val for an item name and a val for its price (Double).
// 2. Make a var quantity that starts at 1.
// 3. Increase quantity to 3 (reassign it).
// 4. Compute total = price * quantity and print a summary line.
//
// ✅ Example output:
// 3 x Notebook @ 4.5 = 13.5
fun main() {
// your code here
}Common Errors (and the fix)
- "Val cannot be reassigned" — You tried to give a new value to a val. If it needs to change, declare it as var instead.
- "Type mismatch: Int vs Double" — You mixed number types without converting. Call .toDouble() (or the type you need) to convert explicitly.
- "Variable must be initialized" — A val or var needs a value or an explicit type. Either assign a value, or write the type so the compiler knows what it is.
- Using single quotes for text — 'hi' is a Char error; only one character fits in single quotes. Use "hi" with double quotes for a String.
Pro Tips
- 💡 Underscores in numbers: write 1_000_000 instead of 1000000 — the underscores are ignored but make big numbers readable.
- 💡 Compound assignment: score += 1 is shorthand for score = score + 1 (also -=, *=, /=).
- 💡 Lint nudges: IntelliJ will suggest changing a var to a val if it's never reassigned. Take the hint.
📋 Quick Reference — Variables
| Concept | Kotlin Syntax |
|---|---|
| Read-only variable | val x = 5 |
| Mutable variable | var y = 5 |
| Explicit type | val n: Long = 5 |
| Convert a number | n.toDouble() |
| Compound assign | y += 1 |
| Readable big number | 1_000_000 |
🎉 Lesson Complete!
- ✅ val is read-only; var can be reassigned
- ✅ Idiomatic Kotlin prefers val
- ✅ Type inference means you rarely write the type
- ✅ Basic types: Int, Long, Double, Boolean, Char, String
- ✅ Number conversions are explicit: .toDouble()
Practice quiz
What does val declare in Kotlin?
- A read-only reference you cannot reassign
- A mutable variable
- A function
- A constant only known at runtime
Answer: A read-only reference you cannot reassign. val creates a read-only reference assigned once.
Which keyword lets you reassign a variable later?
- val
- var
- const
- let
Answer: var. var creates a mutable reference you can reassign.
What happens if you try to reassign a val?
- It silently works
- It compiles with a warning
- A compile error: Val cannot be reassigned
- It becomes a var
Answer: A compile error: Val cannot be reassigned. Reassigning a val is a compile-time error.
What does Kotlin's type inference let you do?
- Skip declaring variables
- Convert types automatically
- Run without a compiler
- Omit the type when it is clear from the value
Answer: Omit the type when it is clear from the value. val x = 5 infers Int, so you rarely write the type explicitly.
Does val make the object it points to immutable?
- No, it only stops reassigning the reference; a mutable object can still change
- Yes, everything becomes frozen
- Only for numbers
- Only inside functions
Answer: No, it only stops reassigning the reference; a mutable object can still change. val freezes the reference, not the contents; a mutable list can still be modified.
Which literal correctly declares a Char?
- "N"
- 'N'
- Char(N)
- `N`
Answer: 'N'. A Char uses single quotes, like 'N'; double quotes make a String.
How do you turn an Int into a Double in Kotlin?
- It happens automatically
- Cast with (Double)
- Call .toDouble() explicitly
- Add 0.0 silently
Answer: Call .toDouble() explicitly. Kotlin avoids implicit widening; call .toDouble() explicitly.
What is the value and type of val x = 5?
- A String "5"
- A Double 5.0
- A function returning 5
- An Int with value 5
Answer: An Int with value 5. An integer literal is inferred as Int.
What is idiomatic Kotlin's default choice between val and var?
- Prefer val, use var only when reassignment is needed
- Always use var
- Always use const
- It does not matter
Answer: Prefer val, use var only when reassignment is needed. Default to val to signal that the value will not change.
What do underscores do in a number like 1_000_000?
- Cause a compile error
- Are ignored but make big numbers readable
- Multiply the value
- Convert it to a String
Answer: Are ignored but make big numbers readable. Underscores are visual separators the compiler ignores.
Continue this course
- Previous: Introduction to Kotlin
- Next: Null Safety — Kotlin's signature feature — eliminate NullPointerExceptions at compile time
- Quick reference: Kotlin cheat sheet › Basics & Variables
Frequently asked questions
When should I use val instead of var?
Default to val for everything and only switch to var when you genuinely need to reassign the variable later. Using val communicates intent — it tells readers (and the compiler) that the value will not change — and prevents accidental reassignment bugs. Most idiomatic Kotlin code is heavily val-based; a codebase full of var is often a sign that something could be simplified.
Does val make the object immutable?
No — val only stops you from reassigning the variable to a different object. The object it points to can still change if it is mutable. For example, val list = mutableListOf(1) is read-only as a reference, but you can still call list.add(2). To make the contents immutable too, use a read-only type such as listOf(...) instead of mutableListOf(...).
Do I have to declare the type of every variable?
No. Kotlin has type inference, so val x = 5 automatically gives x the type Int and val name = "Ada" gives it String. You only need to write the type explicitly when the compiler cannot infer it (for example, a property with no initial value) or when you want to be more specific — such as declaring a Long or a nullable type. Many Kotlin developers omit types where they are obvious.
Why doesn't Kotlin convert numbers automatically like other languages?
Kotlin avoids silent, lossy conversions to prevent bugs. An Int will not automatically become a Long or a Double — you must call .toLong() or .toDouble() explicitly. This makes conversions visible in the code, so you never accidentally lose precision or truncate a value without realising it.