Variables and Data Types
Reviewed & published by Brayan K
By the end of this lesson you'll store text, numbers, and true/false values in Swift, choose let or var correctly, and handle missing values safely with optionals — the bedrock of every Swift program.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Declare values with var (mutable) and let (constant) — and why to prefer let
- • Let Swift infer types, or write explicit annotations like : Int
- • Use the core types: Int, Double, String, Bool
- • Build clean text with string interpolation \(name)
- • Represent "maybe no value" with optionals (Int?, nil)
- • Unwrap optionals safely with if let and ??, and know why ! is risky
📊 The Core Data Types
| Type | Holds | Example | When to use |
|---|---|---|---|
| Int | Whole numbers | let age = 25 | Counts, ages, indexes |
| Double | Decimal numbers | let h = 1.75 | Measurements, money, averages |
| String | Text | let name = "Al" | Names, messages, input |
| Bool | true / false | let ok = true | Yes/no decisions |
| Int? | An Int or nil | var n: Int? = nil | A value that might be missing |
Text always uses "double quotes". A ? after any type (like Int?) makes it optional — it can also hold nil.
1️⃣ var vs let — Mutable or Constant
Every value you name is either a variable or a constant. Use var when the value will change, and let when it won't. The golden rule in Swift is prefer let: it documents that the value is fixed, prevents accidental changes, and helps the compiler optimise. Read this worked example, run it, then you'll write your own.
// A variable is a named box that stores a value. Swift gives you two kinds.
// 'var' makes a MUTABLE box — you can change what's inside later.
var score = 10
score = 25 // ✅ fine: score is a var
print("score = \(score)") // score = 25
// 'let' makes a CONSTANT — it's set once and can NEVER change.
let pi = 3.14159
print("pi = \(pi)") // pi = 3.14159
// pi = 3.2 // ❌ compile error if you uncomment this
// Rule of thumb: reach for 'let' first. Only switch to 'var' when you
// actually need the value to change. Swift even warns you if a 'var'
// is never mutated — a nudge to use 'let'.
let appName = "Weatherly"
var loginCount = 0
loginCount = loginCount + 1 // this one really does change
print("\(appName) opened \(loginCount) time(s)")2️⃣ Types: Inference vs Annotations
Swift is statically typed — every value has one fixed type the compiler knows in advance. Usually you don't write the type: type inference reads the value and picks it for you. When there's no starting value, or you want a specific type, add an explicit annotation with : Type. Remember that Int and Double never mix automatically — convert on purpose with Double(...) or Int(...).
// TYPE INFERENCE: Swift reads the value and figures out the type for you.
let age = 25 // inferred as Int (whole number)
let height = 1.75 // inferred as Double (decimal number)
let name = "Alice" // inferred as String (text, in "double quotes")
let isOnline = true // inferred as Bool (true or false)
// EXPLICIT ANNOTATION: you write ": Type" yourself. Useful when there is
// no starting value yet, or when you want a specific type.
let count: Int = 0
let ratio: Double = 2 // store 2 as a Double, not an Int
var nickname: String = "Al"
var isReady: Bool = false
print("\(name) is \(age), height \(height)m, online: \(isOnline)")
print("count=\(count) ratio=\(ratio) nickname=\(nickname) ready=\(isReady)")
// Int holds whole numbers; Double holds decimals. They are different types,
// so Swift will NOT mix them automatically — you must convert on purpose.
let apples = 3 // Int
let pricePer = 0.5 // Double
let total = Double(apples) * pricePer // convert Int -> Double, then multiply
print("total = \(total)") // total = 1.5Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints, then run it and check the output.
// 🎯 YOUR TURN — replace each ___ then run it in SwiftFiddle.
// 1) Make a constant 'city' set to a city name (text in "double quotes")
let city = ___ // 👉 e.g. "London"
// 2) Make a constant 'year' set to a whole number
let year = ___ // 👉 e.g. 2026 (an Int — no decimal point)
// 3) Make a constant 'temperature' set to a decimal number
let temperature = ___ // 👉 e.g. 18.5 (a Double)
// This line already works once your three constants exist:
print("In \(city) in \(year) it was \(temperature)°C")
// ✅ Expected output (example):
// In London in 2026 it was 18.5°C3️⃣ String Interpolation
String interpolation lets you drop a value straight into text using \(...) — the backslash, then your value in parentheses. It reads far better than joining pieces with +, and you can even run a small calculation inside the parentheses.
// String interpolation drops a value straight into text using \(...).
let userName = "Sam"
let messages = 5
// Everything inside \(...) is replaced with the value.
print("Hi \(userName), you have \(messages) new messages.")
// You can run real code inside the parentheses, not just a name:
let price = 4.0
let qty = 3
print("\(qty) coffees cost £\(price * Double(qty))") // 3 coffees cost £12.0
// Build up longer strings the same way — it reads far better than gluing
// pieces together with '+'.
let first = "Ada"
let last = "Lovelace"
print("Full name: \(first) \(last)") // Full name: Ada Lovelace4️⃣ Optionals — Handling Missing Values
Optionals are Swift's standout safety feature. An optional type (written with ?, e.g. Int?) holds either a value or nil — Swift's word for "nothing here". Because "might be missing" is part of the type, the compiler forces you to deal with the empty case, which eliminates the null-crash bugs that plague other languages. You must unwrap an optional before using it: the safe ways are optional binding (if let) and nil-coalescing (??). Force-unwrapping with ! skips the check and crashes on nil, so use it rarely.
// An OPTIONAL means "a value, OR nothing (nil)". You write it with a ? .
// nil is Swift's word for "no value here yet".
var middleName: String? = nil // might hold a String, currently holds nothing
middleName = "Quinn" // now it has a value
// Optionals appear naturally: Int("...") may fail, so it returns Int?.
let good = Int("42") // Optional(42) — the conversion worked
let bad = Int("hello") // nil — "hello" is not a number
// You can't use an optional directly — you must UNWRAP it first.
// Safest unwrap: optional binding with 'if let'. It runs the block ONLY
// when a value is present, giving you a plain (non-optional) constant.
if let number = good {
print("Parsed a number: \(number)") // runs: Parsed a number: 42
} else {
print("That wasn't a number")
}
if let number = bad {
print("Parsed a number: \(number)")
} else {
print("That wasn't a number") // runs: That wasn't a number
}
// Nil-coalescing '??' supplies a default when the optional is nil.
let typed: String? = nil
let display = typed ?? "Guest" // use "Guest" because typed is nil
print("Welcome, \(display)!") // Welcome, Guest!
// Force-unwrap with '!' grabs the value WITHOUT checking. If it's nil your
// program CRASHES. Only use it when you are 100% certain a value exists.
let definitely = Int("7")! // safe here — "7" is clearly a number
print("Force-unwrapped: \(definitely)") // Force-unwrapped: 7Now you try. Fill in the two blanks to handle values that might be missing — one with ??, one with if let.
// 🎯 YOUR TURN — handle a value that might be missing.
// favouriteColour is an optional String that is currently nil.
let favouriteColour: String? = nil
// 1) Use nil-coalescing to fall back to "unknown" when it's nil.
let shown = favouriteColour ___ "unknown" // 👉 replace ___ with the ?? operator
print("Favourite colour: \(shown)")
// 2) Int("twelve") can't be parsed, so it is nil. Safely unwrap it with
// optional binding, printing the number OR a friendly message.
let parsed = Int("twelve")
if ___ value = parsed { // 👉 replace ___ with the keyword 'let'
print("Got the number \(value)")
} else {
print("Not a valid number")
}
// ✅ Expected output:
// Favourite colour: unknown
// Not a valid numberPro Tips
- 💡 Default to let. Start every value as a constant; only change it to var if the compiler tells you it must change.
- 💡 Avoid ! on real data. Anything that came from a user, a file, or a conversion can be nil — reach for if let or ?? instead.
- 💡 Convert numbers explicitly. Wrap with Double(x) or Int(x) when mixing Int and Double.
- 💡 Name things well. loginCount tells a story; x doesn't.
Common Errors (and the fix)
- "Fatal error: Unexpectedly found nil while unwrapping an Optional value" — you force-unwrapped with ! on something that was nil, which crashes. Use if let or ?? to handle the empty case safely.
- "Cannot assign to value: 'pi' is a 'let' constant" — you tried to change a let. Either keep it constant, or declare it with var if it really needs to change.
- "Binary operator '*' cannot be applied to operands of type 'Int' and 'Double'" — you mixed number types. Convert first: Double(apples) * price.
- "Value of optional type 'Int?' must be unwrapped" — you used an optional directly. Unwrap it first with if let, or provide a default with ??.
📋 Quick Reference
| Task | Code | Result |
|---|---|---|
| Constant | let pi = 3.14 | can't change |
| Variable | var n = 0 | can change |
| Annotation | let c: Int = 0 | explicit type |
| Interpolate | "Hi \(name)" | Hi Sam |
| Optional | var x: Int? = nil | value or nil |
| Safe unwrap | if let v = x { … } | runs if not nil |
| Default | x ?? 0 | 0 when nil |
| Convert | Double(myInt) | Int → Double |
Mini-Challenge: Profile Line
No blanks this time — just a brief and an outline. Build it, run it, and check your output against the example in the comments. This is exactly the kind of small program real apps are made of.
// 🎯 MINI-CHALLENGE: Profile line
// 1. Make a 'let' constant 'name' (a String) with your name.
// 2. Make a 'var' 'age' (an Int) with your age, then add 1 to it
// (it's your birthday!) using: age = age + 1
// 3. Make an OPTIONAL 'nickname' of type String? — set it to nil.
// 4. Use ?? to choose the nickname, or "no nickname" when it's nil.
// 5. Print one tidy line using \(...) interpolation, e.g.:
// "Sam is now 21, also known as no nickname"
//
// ✅ Example output (name=Sam, age was 20, nickname=nil):
// Sam is now 21, also known as no nickname
// your code here🎉 Lesson Complete!
- ✅ let = constant (prefer this), var = mutable
- ✅ Type inference usually picks the type; add : Int-style annotations when needed
- ✅ Core types: Int, Double, String, Bool — convert numbers with Double(...)/Int(...)
- ✅ Build text cleanly with interpolation \(value)
- ✅ Optionals (Int?) hold a value or nil; unwrap with if let or ??, and avoid !
Practice quiz
Which keyword makes a value that can change later?
- let
- var
- const
- mut
Answer: var. var declares a mutable variable; let is a constant.
Which type holds whole numbers in Swift?
- Double
- Float
- Int
- Number
Answer: Int. Int stores whole numbers; Double stores decimals.
What does an optional type like Int? hold?
- Only an Int
- Always nil
- Two Ints
- An Int or nil
Answer: An Int or nil. An optional holds either a value or nil.
Which is the SAFEST way to unwrap an optional?
- if let
- Force-unwrap with !
- Ignore it
- Cast it
Answer: if let. if let runs only when a value is present, avoiding crashes.
What does the ?? operator do?
- Compares two optionals
- Supplies a default when the value is nil
- Force-unwraps
- Declares an optional
Answer: Supplies a default when the value is nil. Nil-coalescing ?? provides a fallback when the optional is nil.
Why won't Swift add an Int and a Double automatically?
- It always rounds
- Doubles aren't numbers
- They are different types and Swift never converts implicitly
- Int is read-only
Answer: They are different types and Swift never converts implicitly. Swift requires explicit conversion like Double(myInt) to mix number types.
What does Int("hello") return?
- 0
- An error
- "hello"
- nil
Answer: nil. The failable initialiser returns nil when the text isn't a number.
When is force-unwrapping with ! dangerous?
- It crashes if the optional is nil
- It never compiles
- It is always safe
- Only with strings
Answer: It crashes if the optional is nil. Force-unwrapping a nil optional crashes the program.
How does Swift pick a type when you don't write one?
- It defaults to String
- It asks at runtime
- Type inference from the value
- It uses Any
Answer: Type inference from the value. Type inference reads the assigned value and chooses the type.
Which keyword should you prefer by default?
- var
- mutable
- Either is equal
- let
Answer: let. Prefer let; switch to var only when the value must change.
Continue this course
- Previous: Introduction to Swift
- Next: Control Flow — if/else, switch statements, and guard clauses for safer code
Frequently asked questions
Should I use var or let?
Prefer let. Use let for any value that never changes after you set it — it makes your intent clear and lets the compiler catch accidental changes. Only switch to var when the value genuinely needs to change (a counter, a running total). Swift even warns you when a var is never modified, suggesting you change it to let.
What is an optional in Swift?
An optional is a type that holds either a value or nothing (nil). You write it by adding ? to a type, like Int? or String?. Optionals make 'might be missing' explicit in the type system, so the compiler forces you to handle the empty case. This eliminates the null-pointer crashes common in other languages.
What does the ! force-unwrap operator do, and why is it dangerous?
Putting ! after an optional unwraps it without checking, giving you the plain value. If the optional is nil at that moment, your program crashes immediately ('Unexpectedly found nil while unwrapping an Optional value'). Prefer if let or the ?? operator, which handle nil safely. Only force-unwrap when you are certain a value exists.
Why won't Swift let me add an Int and a Double together?
Int (whole numbers) and Double (decimals) are different types, and Swift never converts between number types automatically — that prevents silent precision bugs. Convert explicitly: write Double(myInt) to turn an Int into a Double, or Int(myDouble) to go the other way (which drops the decimal part).
When do I need to write the type, like : Int?
Type inference covers most cases — Swift reads the value and picks the type for you. You write an explicit annotation when there is no starting value (var name: String?), when you want a specific type that differs from the inferred one (let ratio: Double = 2), or to document intent for the next reader.