Values, constants and variables
How Swift stores a value, why let is the default, and what type inference is doing behind your back.
By the end you will be able to
- Declare values with let and var and explain when to reach for each
- Read a type annotation and say what it promises
- Predict which of two similar programs the compiler will reject
Before reading anything — take a guess. Which of these programs does Swift refuse to compile?
Two ways to name a value
Every value in a Swift program has a name, and every name is declared one of two ways.
let maximumAttempts = 3 // a constant — never changes
var currentAttempt = 0 // a variable — can change
let says: this name will always mean this value. var says: this name may mean something different later.
That is the whole distinction. But which one you pick is the single most frequent decision you make in Swift, so it is worth understanding why the language pushes so hard toward let.
Why let is the default
A constant is a promise the compiler enforces. Once you see let total = …, you know that for the rest of that scope, total means exactly one thing. You never have to scan downward looking for the line that changed it.
Reach for var only when the value genuinely needs to change — a running total, a counter, an index. Everything else is a let. In practice, well-written Swift is mostly let.
Type inference
You have not written a single type yet, and yet every one of those values has one.
let maximumAttempts = 3 // Int
let ratio = 0.75 // Double
let greeting = "Hello" // String
let isReady = true // Bool
Swift reads the literal on the right and works out the type. This is type inference, and it is not the same as being untyped: maximumAttempts is an Int permanently. Try to put a string in it later and the program will not build.
You can also write the type yourself with a type annotation — a colon and a type name after the name:
let maximumAttempts: Int = 3
var progress: Double = 0
Annotate when the literal alone would give you the wrong type (as with progress above, where 0 would otherwise infer as Int), or when spelling out the type makes the code clearer to a reader.
Read this program and decide what it prints before you run it. Write your guess down — actually commit to it.
let a = 7
let b = 2
print(a / b)Swift will not convert types for you
This is the rule that surprises people arriving from Python or JavaScript:
let count = 5 // Int
let price = 2.5 // Double
// let total = count * price // error: this does not compile
Swift refuses — and not because the conversion is hard. The compiler could convert either value in an instant; what it will not do is choose for you. Look at the line again and ask what it should mean. Converted toward Int, the total is 10 — the .5 silently gone. Converted toward Double, it is 12.5. Both are reasonable programs, and they produce different money. A language that picks silently hides that decision inside the compiler, and you meet it weeks later as a number that is quietly wrong. Swift makes you write the decision down:
let total = Double(count) * price // 12.5 — you chose the Double meaning, in writing
Double(count) builds a new Double from an existing Int. That is a normal initializer call, not a cast — and it is the answer to "which program did you mean?", recorded where every reader can see it.
Why money is never a Double
One more thing about Double before we move on, because it decides how every price, balance and payment in a real codebase must be typed. Double stores numbers as binary fractions — and most decimal amounts (0.1, 19.99) have no exact binary form, so the nearest representable value is stored instead.
The mental model is one you already own: try writing one third in decimal. 0.333… never lands — no finite row of decimal digits equals 1/3. Binary has the same blind spot, just in different places: one tenth is to binary what one third is to decimal — unwritable in finitely many digits. So the moment you type let price = 19.99, the stored value is already a near-miss. People summarise this as "Double loses precision", but notice that's subtly off: nothing degrades — the exact value was never there to begin with. The arithmetic that follows doesn't cause the error; it makes the pre-existing miss visible, and compounds it:
19.99 * 100 should be 1999 — that's how you'd convert pounds to pence. Commit to your guess for both lines before running.
print(0.1 + 0.2)
let price = 19.99
print(Int(price * 100))String interpolation
You have already seen it. \(…) inside a string literal drops in the value of any expression:
let name = "Ada"
let years = 36
print("\(name) is \(years) years old, and \(years * 12) months.")
Anything can go inside the parentheses, including arithmetic and function calls. This is how you build almost every string in Swift.
Names carry meaning
Swift's own API guidelines are explicit about this: clarity at the point of use beats brevity. maximumAttempts is better than maxAtt, and much better than m. You write a name once and read it fifty times.
Use camelCase for values and functions, UpperCamelCase for types. That is not a style preference; it is the convention every Swift codebase and every Apple framework follows.
Fix the constant
This program tries to update a value that was declared as a constant. Make it compile and print Score: 15 — change as little as possible.
Convert before you divide
averageScore should be the mean of the three scores, as a Double. The program currently loses the fractional part. Fix it so it prints Average: 84.0.
Describe an order
Declare the values below and print exactly one line:
3 x Flat white at 4.5 = 13.5
Use string interpolation, not string concatenation.
Write two or three sentences, in your own words, explaining to a JavaScript developer why Swift refuses to add an Int to a Double. Do not look back at the lesson while you write it.
Nobody grades this. The value is entirely in the retrieval — putting an idea into your own words is a far stronger test of understanding than re-reading is.
You can now:
- Declare values with
letandvar, and explain whyletis the default - Read and write type annotations
- Explain why
7 / 2is3and whyIntandDoubledo not mix - Say why exact decimal quantities — money above all — never live in a
Double - Build strings with
\(interpolation)
Next up: optionals — Swift's answer to the question "what if there is no value at all?", and the feature that eliminates an entire category of crash.