Skip to main content

var vs val in Kotlin

One of the first and most important decisions you make in Kotlin is whether a value can change or must remain constant. Kotlin makes this decision explicit using two keywords: var for mutable values and val for immutable ones. Understanding this distinction will make your code safer, cleaner, and easier to reason about.

What is an Identifier?​

An identifier is simply a name that refers to data in your program. Before declaring any identifier, you must choose whether it should be mutable or immutable.

Identifiers.kt
val count = 10        // Immutable
val userName = "Alice" // Immutable
var score = 0 // Mutable

Identifier Naming Rules​

  • Must start with a letter or underscore (_)
  • Can contain letters, numbers, and underscores
  • Case-sensitive (total ≠ Total)
  • Follow camelCase convention for variables

The Core Question​

Before choosing var or val, always ask yourself:

Should this value ever change after it is created?

Your answer determines which keyword you use.


var — Mutable (Changeable)​

Use var when the value must change during execution.

Syntax​

var identifier = initialValue

Simple Example​

MutableExample.kt
fun main() {
var score = 10
score = 15
score += 5
println(score) // Output: 20
}

Key points:

  • score changes multiple times
  • This is valid because it's declared with var
  • The compiler allows reassignment

When var Makes Sense​

Some values naturally change during program execution:

Use CaseExample
Countersvar attempts = 0
Running totalsvar sum = 0
Loop variablesvar index = 0
Temporary statevar isProcessing = false

In such cases, mutation is acceptable and sometimes necessary.


val — Immutable (Preferred)​

A val can be assigned only once. After initialization, it cannot be changed.

Syntax​

val identifier = initialValue

Example​

ImmutableExample.kt
fun main() {
val maxRetries = 3
// maxRetries = 5 // ❌ Compile-time error: Val cannot be reassigned

println("Max retries: $maxRetries")
}

Benefits:

  • Kotlin prevents reassignment at compile time
  • Errors are caught early, before the program runs
  • Intent is clear to other developers

Side-by-Side Comparison​

RecommendedApproach.kt
fun main() {
val coffeeTime = 11
println("Coffee break at: $coffeeTime")
}

Advantages:

  • ✅ Clear intent
  • ✅ Cannot change accidentally
  • ✅ Safer design
  • ✅ Better for concurrency

Why Kotlin Encourages val​

Kotlin Philosophy

Kotlin encourages immutability by default. Code is easier to understand when values don't change unexpectedly. Immutable data structures are inherently thread-safe and lead to fewer bugs.

Benefits of using val​

  • Easier to read and reason about — You know the value won't change
  • Fewer bugs — No accidental modifications
  • Better concurrency support — Immutable data is thread-safe
  • Cleaner APIs — Functions with immutable parameters are predictable

Practical Examples​

Example 1: Configuration Values​

Configuration.kt
class AppConfig {
val apiUrl = "https://api.example.com" // Never changes
val timeout = 30_000L // Never changes
var retryCount = 0 // Changes with each retry
}

Example 2: Calculating Totals​

Calculator.kt
fun calculateTotal(prices: List<Int>): Int {
var total = 0 // Mutable accumulator
for (price in prices) {
total += price
}
return total
}

fun main() {
val prices = listOf(10, 20, 30) // Immutable list reference
val result = calculateTotal(prices)
println("Total: $result") // Output: Total: 60
}

Analysis:

  • total is var because it accumulates values
  • prices is val because the list reference doesn't change
  • Mutation is local, controlled, and easy to understand

Example 3: Good vs Bad Naming​

GoodNaming.kt
val coffeeBreakHour = 11
val userName = "John"
val isAuthenticated = true

Benefits:

  • Clear intent
  • Self-documenting
  • Follows camelCase convention

Common Pitfalls​

Using var Out of Habit

Don't do this:

BadHabit.kt
var userName = "Alice"  // Never changes after initialization
var maxConnections = 100 // Never changes after initialization

Do this instead:

GoodHabit.kt
val userName = "Alice"
val maxConnections = 100

Start with val by default. Only switch to var when you encounter a compilation error or have a clear need for mutability.

Making Values Mutable Without Reason
UnnecessaryMutability.kt
// ❌ WRONG: var not needed
fun getUserGreeting(name: String): String {
var greeting = "Hello, $name!"
return greeting
}

// ✅ CORRECT: val is sufficient
fun getUserGreeting(name: String): String {
val greeting = "Hello, $name!"
return greeting
}

// ✅ EVEN BETTER: Direct return
fun getUserGreeting(name: String): String {
return "Hello, $name!"
}
Poor Variable Naming

Avoid generic names that don't convey meaning:

❌ Avoid✅ Prefer
x, y, zwidth, height, depth
temp, tmpprocessedUser, filteredList
datauserData, configData
flagisActive, hasPermission
Mixing Mutable State Across Large Scopes
ProblematicScope.kt
// ❌ WRONG: Mutable state accessible everywhere
class UserService {
var currentUser: User? = null // Dangerous global state

fun login(user: User) {
currentUser = user
}
}

// ✅ CORRECT: Encapsulate mutable state
class UserService {
private var _currentUser: User? = null
val currentUser: User?
get() = _currentUser

fun login(user: User) {
_currentUser = user
}
}

Best Practices​

Default to val

Always start with val. Only use var when the compiler forces you to or when you have a clear, justified reason for mutability.

DefaultToVal.kt
fun processOrders(orders: List<Order>) {
val filteredOrders = orders.filter { it.isValid } // val by default
val totalAmount = filteredOrders.sumOf { it.amount }

println("Processed ${filteredOrders.size} orders")
println("Total amount: $totalAmount")
}
Keep Mutable Variables Local

When you must use var, keep it in the smallest scope possible:

LocalMutability.kt
fun findMaxValue(numbers: List<Int>): Int {
var max = numbers.firstOrNull() ?: 0 // Local scope only

for (number in numbers) {
if (number > max) {
max = number
}
}

return max
}
Use Functional Alternatives

Replace mutable accumulators with functional operations when possible:

FunctionalStyle.kt
// Instead of this:
fun sumPrices(items: List<Item>): Int {
var total = 0
for (item in items) {
total += item.price
}
return total
}

// Prefer this:
fun sumPrices(items: List<Item>): Int {
return items.sumOf { it.price }
}
Immutable Collections

Use read-only collection types by default:

ImmutableCollections.kt
// ✅ GOOD: Read-only list
val numbers: List<Int> = listOf(1, 2, 3)

// ⚠️ CAUTION: Mutable list (use only when necessary)
val mutableNumbers: MutableList<Int> = mutableListOf(1, 2, 3)
mutableNumbers.add(4)

Key Takeaways​

  • val creates immutable references that cannot be reassigned after initialization
  • var creates mutable references that can be changed during execution
  • Always prefer val by default for safer, more predictable code
  • Use var only when you have a clear need for mutability (counters, accumulators, etc.)
  • Immutability leads to fewer bugs, better concurrency, and more maintainable code
  • Good variable naming makes the difference between clear and confusing code

External References: