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 (totalTotal)
  • 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: