# Swift Immutability First: Why You Should Prefer let Over var

Source: https://devdocs10.com/articles/swift/swift-immutability-first  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

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.

- `let` creates a value that cannot be reassigned.
- `var` creates a value that can be changed after initialization.
- Immutability is a default mindset, not a strict rule that forbids all mutation.
- The goal is to minimize change until change is actually required.

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:

- protect values from accidental changes
- make functions easier to understand
- reduce side effects
- spot logic mistakes earlier through compiler errors
- keep data flow more predictable

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

## 3. Basic Syntax

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.

```swift
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.

```swift
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.

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

Output (tested with Swift 6.4):

```text
Hello, Swift!
```

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.

```swift
let basePrice = 50.0
let taxRate = 0.2
let finalPrice = basePrice + (basePrice * taxRate)
print(finalPrice)
```

Output (tested with Swift 6.4):

```text
60.0
```

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`.

```swift
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.

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

Output (tested with Swift 6.4):

```text
Ada
```

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:

- configuration values like API endpoints, theme names, and feature flags
- intermediate results in data transformation pipelines
- function parameters that should not be changed inside the function
- return values that represent a finished result
- IDs, dates, labels, and other values that should remain stable
- local helper values in parsing, formatting, and validation code

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.

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

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

```swift
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.

```swift
let name = "Lina"
name = "Mina"
```

**Fix:** Use `var` only if the value must change.

```swift
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.

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

**Fix:** Create new constants for each stage of transformation.

```swift
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.

```swift
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.

```swift
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.

```swift
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

- `let` prevents reassignment of the binding, but it does not always make every underlying piece of data deeply immutable.
- Some reference types can still have internal state that changes even when the reference itself is constant.
- You may need `var` for counters, accumulators, and stateful operations such as building a result over time.
- Changing a stored property inside a `let` instance of a value type is not allowed because the whole instance is constant.
- Mutable collections can still be used in limited ways if the collection itself is declared with `var`.

> **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.

```swift
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)")
```

Output (tested with Swift 6.4):

```text
Final total: 108
```

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

- Prefer `let` when the value should stay the same.
- Use `var` only when reassignment is necessary.
- Immutable values make intent clearer and reduce accidental bugs.
- Swift will catch attempts to mutate a constant at compile time.
- You can still write flexible code without making everything mutable.

## 11. Practice Exercise

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

- Create a greeting string from a first name and last name.
- Trim extra whitespace from the full name.
- Format the result for display.
- Only use `var` if you cannot express the result with new constants.

**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.

```swift
let firstName = "  Ada"
let lastName = "Lovelace  "

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

print(displayName)
```

Output (tested with Swift 6.4):

```text
ADA LOVELACE
```

## 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.
