Swift Immutability First: Why You Should Prefer let Over var

In Swift, the default habit should be to make values immutable with let and only use var when mutation is truly needed. This rule makes code easier to reason about, reduces bugs, and helps you see which values are meant to change.

Quick answer: Use let whenever a value does not need to change after it is created. Use var only for values that must be reassigned later, such as counters, caches, or state that genuinely changes.

Difficulty: Beginner

You'll understand this better if you know: basic Swift variables, assignment, and how simple values like String and Int are stored.

1. What Is Immutability First?

Immutability first means you start by declaring values as constants with let. A constant can be assigned once, then it stays fixed for the rest of its scope. That makes the code easier to trust because the value cannot change unexpectedly later.

This idea is especially important in Swift because the language is designed to encourage safety, clarity, and predictable behavior.

2. Why Immutability First Matters

Using let by default helps you write code that is easier to debug and safer to refactor. When a value cannot change, you can rule out a whole category of bugs caused by accidental reassignment.

It also makes intent clearer. Another developer reading your code can immediately tell whether a value is meant to be stable or evolving. That clarity matters in small functions, large codebases, and especially in code that is shared across a team.

In practice, immutability first is useful when you want to:

There are cases where var is the right tool, but it should usually be the exception rather than the starting point.

3. Basic Syntax or Core Idea

The core difference is simple: let defines a constant, and var defines a variable. The compiler enforces that difference.

Constant with let

Use let when the value should not change after it is assigned.

let appName = "Trail Tracker"
let maxRetries = 3

Both values are fixed after creation. If you try to assign a new value later, Swift will stop you at compile time.

Variable with var

Use var when a value really needs to change.

var score = 0
score = score + 10

Here, mutation is intentional because the score changes over time.

4. Step-by-Step Examples

Example 1: A fixed greeting

A greeting string does not need to change, so let is the right choice.

let greeting = "Hello, Swift!"
print(greeting)

This example shows the simplest case: if the value is final, declare it as a constant.

Example 2: Building a derived value

Even if you calculate a value in steps, you can often still keep the result immutable once it is complete.

let basePrice = 50
let taxRate = 0.2
let finalPrice = basePrice + (basePrice * taxRate)
print(finalPrice)

The computed result does not need to remain mutable just because it was derived from other values.

Example 3: A changing counter

When the value changes repeatedly, use var.

var attempts = 0
attempts = attempts + 1
attempts = attempts + 1

This is a valid use of mutation because the counter’s purpose is to change.

Example 4: Reassigning only when needed

Sometimes a variable starts out mutable because you do not yet know the final value, but you can often narrow the mutable portion to a small part of the code.

let rawName = "  Ada  "
let cleanName = rawName.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
print(cleanName)

Instead of mutating one value over and over, create a new constant for each stage of transformation. That keeps each step easy to inspect.

5. Practical Use Cases

Immutability first is especially useful in real Swift code such as:

As a practical rule, ask whether the value represents state or fact. Facts usually belong to let. State that evolves over time may need var.

6. Common Mistakes

Mistake 1: Using var by default for everything

Many beginners declare every local value with var out of habit. That works, but it hides intent and makes accidental mutation easier.

Problem: The code allows values to change even when they should be fixed, which can lead to bugs that are harder to trace later.

var username = "maria"
var country = "DE"
var accountID = 7421

Fix: Use let for values that do not need to change.

let username = "maria"
let country = "DE"
let accountID = 7421

The corrected version makes the programmer’s intent explicit and prevents accidental reassignment.

Mistake 2: Trying to reassign a constant

If a value is declared with let, Swift will not let you change it later. This is good, but it can surprise beginners who expect a value to be editable.

Problem: Swift reports an error such as Cannot assign to value: 'name' is a 'let' constant because constants cannot be reassigned.

let name = "Lina"
name = "Mina"

Fix: Use var only if the value must change.

var name = "Lina"
name = "Mina"

The fixed version works because the variable is designed for reassignment.

Mistake 3: Rebuilding a value with unnecessary mutation

Sometimes developers create a mutable variable, change it several times, and only then use the final result. In many cases, the same logic can be expressed with constants and a single final expression.

Problem: Excessive mutation makes it harder to see which version of the value is important and can introduce mistakes if one step is forgotten.

var fullName = "Ada Lovelace"
fullName = fullName.trimmingCharacters(in: CharacterSet.whitespaces)
fullName = fullName.uppercased()

Fix: Create new constants for each stage of transformation.

let rawFullName = "Ada Lovelace"
let trimmedFullName = rawFullName.trimmingCharacters(in: CharacterSet.whitespaces)
let displayName = trimmedFullName.uppercased()

The corrected version keeps each transformation step clear and avoids accidental overwrite of the final value.

7. Best Practices

Practice 1: Default to let, then relax only when needed

Starting with let forces you to justify mutation. That usually leads to cleaner code because you only introduce var when the design actually requires it.

let pageTitle = "Settings"
let sectionCount = 4

Use mutation as an intentional design choice, not a default habit.

Practice 2: Keep mutable scope as small as possible

When you do need var, limit it to the smallest scope that requires change. That reduces the chance that unrelated code will alter it.

func makeMessage(name: String) -> String {
    var message = "Hello, " + name
    message += "!"
    return message
}

This is better than making a wider-scope variable mutable for no reason.

Practice 3: Prefer transforming into new constants

Instead of changing one value repeatedly, create a new constant for each meaningful stage. This is especially useful for strings, arrays, and parsed data.

let input = "  swift  "
let trimmed = input.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let uppercased = trimmed.uppercased()

This style makes the pipeline easier to read and debug.

8. Limitations and Edge Cases

Note: A constant binding is not the same thing as a deep freeze of every object graph. In Swift, the exact behavior depends on whether you are using value types or reference types.

9. Practical Mini Project

Here is a small example that processes a purchase total. The code uses let for stable facts and var only for the one value that must change as discounts are applied.

let itemPrice = 120
let shippingFee = 8
let discountRate = 0.15

var subtotal = itemPrice + shippingFee
let discountAmount = Double(subtotal) * discountRate

subtotal = Int(Double(subtotal) - discountAmount)

print("Final total: \(subtotal)")

This example shows a practical balance: constants for fixed values, a variable for the one changing intermediate total, and a final output that is easy to follow.

10. Key Points

11. Practice Exercise

Try rewriting a small piece of Swift code so that every value uses let unless mutation is truly required.

Expected output: a clean display name such as "Ada Lovelace" or "ADA LOVELACE", depending on your formatting choice.

Hint: Build the result in stages with separate constants instead of changing one value repeatedly.

let firstName = "  Ada"
let lastName = "Lovelace  "

let fullName = firstName + " " + lastName
let trimmedName = fullName.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let displayName = trimmedName.uppercased()

print(displayName)

12. Final Summary

Swift’s immutability-first style is simple: prefer let and reach for var only when you need mutation. This habit makes your code safer, clearer, and easier to maintain because the compiler helps enforce your intent.

In everyday Swift code, most values are facts rather than state, so they belong in constants. When you do need changing data, keep the mutable scope small and make the reason for mutation obvious. That balance gives you the readability benefits of immutability without limiting real-world flexibility.

As a next step, practice converting a few functions in your own codebase from var-heavy style to let-first style and watch how much simpler the logic becomes.