Val vs Var: Immutable and Mutable Variables
The Problem with Mutable State
Most bugs in software trace back to unexpected state changes. A function modifies a variable that another function depends on, and the system breaks in a way that is hard to reproduce. Kotlin addresses this at the language level by making immutability the default.
Val: Immutable Binding
A val declaration creates a read-only reference. Once assigned, you cannot reassign it. The underlying object may still be mutable (a list can have elements added), but the binding itself cannot change.
val name = "Kotlin"
// name = "Java" // Compile error: Val cannot be reassigned
val numbers = mutableListOf(1, 2, 3)
numbers.add(4) // Allowed: the list is mutable
// numbers = mutableListOf(5, 6) // Compile error: Val cannot be reassigned
Var: Mutable Binding
A var declaration allows reassignment. Use it only when the value genuinely needs to change during execution, such as loop counters or accumulators.
var counter = 0
println(counter) // 0
counter = 1
println(counter) // 1
When to Use Which
| Scenario | Use | Why |
|---|---|---|
| Configuration values | val |
They should not change after initialization |
| Function parameters | val (implicit) |
Parameters are always immutable in Kotlin |
| Loop counters | var |
Must increment |
| Accumulator in a reduce | var |
Must update during iteration |
| Any value that should not change | val |
Prevents accidental mutation |
The Kotlin compiler enforces this. If you declare a val and try to reassign it, you get a compile-time error. This eliminates an entire class of bugs before the code runs.
Type Inference
Kotlin infers types from the initializer, so explicit type annotations are usually unnecessary:
val age = 25 // Inferred as Int
val temperature = 36.6 // Inferred as Double
val active = true // Inferred as Boolean
// Explicit type only when needed for clarity
val score: Int = 100
Shadowing
Kotlin allows variable shadowing within nested scopes, but it is discouraged because it reduces readability:
val x = 10
run {
val x = 20 // Shadows outer x; compiler warning
println(x) // 20
}
println(x) // 10
Basic Types and String Templates
Kotlin's Type System
Kotlin is statically typed, meaning every expression has a known type at compile time. However, type inference means you rarely need to write types explicitly. All types in Kotlin are objects, including primitives like Int and Boolean.
Number Types
val intVal: Int = 42
val longVal: Long = 42L
val floatVal: Float = 3.14f
val doubleVal: Double = 3.14
val byteVal: Byte = 127
val shortVal: Short = 32767
Kotlin does not distinguish between primitives and boxed types at the language level. The compiler optimizes to JVM primitives when possible, so there is no performance penalty for using Int instead of int.
Boolean and Char
val isActive: Boolean = true
val letter: Char = 'A'
val newline: Char = '\n'
String Templates
String templates eliminate string concatenation and make output readable. Prefix the string with """ for multi-line raw strings:
val name = "Amazon"
val year = 2026
// Simple variable reference
println("Company: $name")
// Expression in braces
println("Year: ${year + 1}")
// Multi-line raw string
val receipt = """
Company: $name
Year: $year
Total: ${100 * 1.08}
"""
println(receipt)
Without string templates, the same output requires concatenation:
// Java-style (avoid in Kotlin)
println("Company: " + name)
println("Year: " + (year + 1))
String templates work with any expression, not just variables:
val items = listOf("apples", "bananas")
println("Items: ${items.joinToString(", ")}") // Items: apples, bananas
println("Count: ${items.size}") // Count: 2
Type Conversion
Kotlin does not perform implicit type widening. You must convert explicitly:
val intValue = 42
val doubleValue: Double = intValue.toDouble() // Explicit conversion
val stringValue = intValue.toString() // "42"
// This does NOT compile:
// val result: Double = intValue
Explicit conversion prevents silent precision loss. If you assign an Int to a Double without converting, the compiler rejects it, forcing you to acknowledge the conversion.
Operators in Kotlin
Arithmetic Operators
Kotlin supports the standard arithmetic operators with clear precedence rules:
val a = 10
val b = 3
println(a + b) // 13 (addition)
println(a - b) // 7 (subtraction)
println(a * b) // 30 (multiplication)
println(a / b) // 3 (integer division)
println(a % b) // 1 (modulus)
Integer division truncates toward zero. To get a decimal result, convert one operand:
println(a.toDouble() / b) // 3.3333...
Comparison Operators
println(5 > 3) // true
println(5 < 3) // false
println(5 >= 5) // true
println(5 <= 4) // false
println(5 == 5) // true (structural equality)
println(5 != 3) // true (structural inequality)
Kotlin distinguishes between structural equality (==) and referential equality (===). == calls equals() under the hood, so it compares content, not memory addresses.
Logical Operators
val x = true
val y = false
println(x && y) // false (AND)
println(x || y) // true (OR)
println(!x) // false (NOT)
Kotlin uses && and || which short-circuit: the right-hand side is not evaluated if the left-hand side determines the result.
Increment and Decrement
var count = 0
println(count++) // Post-increment: prints 0, then count becomes 1
println(++count) // Pre-increment: count becomes 2, then prints 2
println(count--) // Post-decrement: prints 2, then count becomes 1
println(--count) // Pre-decrement: count becomes 0, then prints 0
Infix Functions
Any function marked infix can be called with dot-call notation removed:
infix fun Int.times(str: String): String {
return str.repeat(this)
}
println(3 times "ha") // "hahaha"
This is not just syntax sugar. Infix functions make certain operations read like natural language, which is why Kotlin uses them extensively in its standard library (to, until, downTo, step).
Elvis Operator
The Elvis operator (?:) provides a default value when the left side is null. It is essential for null safety and covered in detail in the null safety topic:
val length = name?.length ?: 0
Range Operator
The .. operator creates ranges, which are iterated with for loops:
for (i in 1..5) {
print("$i ") // 1 2 3 4 5
}
// Downward range
for (i in 5 downTo 1) {
print("$i ") // 5 4 3 2 1
}
Practice Problems
Write a Kotlin function that converts Celsius to Fahrenheit using the formula F = C * 9/5 + 32. Use val for the input and var for the result. Print the result with a string template.
Solution
fun convertCelsiusToFahrenheit(celsius: Double): Double {
val fahrenheit = celsius * 9.0 / 5.0 + 32.0
println("$celsius°C = ${"%.1f".format(fahrenheit)}°F")
return fahrenheit
} The following code has a compile error. Identify which line causes the error and explain why. Fix it without changing the program's output.
Solution
fun main() {
val name = "Kotlin"
var year = 2027 // Changed to var since we reassign
println("$name was created in $year")
}
// The error is on the line that reassigns 'year'.
// Since year needs to change, it must be declared with 'var'. Write a function that takes two numbers and an operator character ('+', '-', '*', '/') and returns the result as a Double. Handle division by zero by returning 0.0. Use string templates to log the operation.
Solution
fun calculate(a: Double, b: Double, op: Char): Double {
val result = when (op) {
'+' -> a + b
'-' -> a - b
'*' -> a * b
'/' -> if (b != 0.0) a / b else 0.0
else -> 0.0
}
println("$a $op $b = $result")
return result
} Quiz
1. What is the difference between val and var in Kotlin?
2. What does the following code print? ```kotlin val x = 5 println("${x * 2}") ```
3. Which statement about Kotlin type inference is correct?
4. What is the result of `10 / 3` in Kotlin when both operands are Int?
Flashcards
Question
What does val do in Kotlin?
Click to reveal answer
Answer
Declares an immutable binding. Once assigned, the variable cannot be reassigned. The underlying object may still be mutable (e.g., a mutableListOf), but the binding is fixed.
Question
How do string templates work in Kotlin?
Click to reveal answer
Answer
Prefix the string with $variableName for simple references, or ${expression} for complex expressions. Multi-line strings use triple quotes """. Templates are evaluated at runtime and interpolated into the string.
Question
What is the difference between == and === in Kotlin?
Click to reveal answer
Answer
== checks structural equality (calls equals()), comparing content. === checks referential equality, comparing memory addresses. Use == for value comparison.
Question
Why does Kotlin require explicit type conversion?
Click to reveal answer
Answer
Kotlin does not perform implicit widening (e.g., Int to Double). Explicit conversion prevents silent precision loss and makes type changes visible in the code.
Revision Notes
Key Takeaways
- 1. Prefer val over var. Only use var when reassignment is genuinely required.
- 2. Kotlin infers types at compile time, so explicit annotations are usually unnecessary.
- 3. String templates replace concatenation and make output readable.
- 4. Kotlin does not perform implicit type widening. Use explicit conversion methods.
- 5. == calls equals() for content comparison; === checks referential identity.
Interview Tips
- • Explain why immutability matters: it prevents accidental state changes and makes code easier to reason about.
- • Be ready to describe the difference between val and var with concrete examples.
- • Know that Kotlin's type inference is compile-time, not runtime.
- • If asked about == vs ===, give an example where they differ (e.g., two new String objects with the same content).
Cheat Sheet
Kotlin Basics Cheat Sheet
Val vs Var:
val= immutable binding (preferred)var= mutable binding (use only when needed)
Types:
- Int, Long, Float, Double, Boolean, Char, String
- All types are objects; compiler optimizes to primitives
- No implicit widening: use
.toDouble(),.toInt(), etc.
String Templates:
"Hello, $name"— simple variable"${expression}"— complex expression"""..."""— multi-line raw string
Operators:
- Arithmetic: +, -, *, /, %
- Comparison: >, <, >=, <=, ==, !=
- Logical: &&, ||, !
- Range: .., downTo, until, step
- Elvis: ?: (null default)