Swift Basics
Variables, optionals, types, control flow, closures, value vs reference
- 01
What is the difference between let and var in Swift?
EasyA
letbinding can be assigned exactly once; avarbinding can be reassigned as often as you like.let name = "Alice" // name = "Bob" // ❌ compile error var counter = 0 counter += 1 // ✅ … - 02
What are optionals in Swift and how do you safely unwrap them?
EasyAn optional makes "this value might be missing" part of the type instead of a runtime surprise.
var name: String? = "Alice" // if let if let unwrapped = name { print(unwrapped.uppercased()) } … - 03
Explain value types vs reference types in Swift.
EasySwift divides every type into value types, which are copied when you assign or pass them, and reference types, which are shared.
// Value type struct Point { var x: Int; var y: Int } var a = Point(x: 1, y: 2) var b = a // copy b.x = 99 print(a.x) // 1 — unchanged … - 04
What are closures in Swift and how do capture lists work?
MediumClosures are self-contained blocks of functionality (named or anonymous) that can be passed around.
// Function-typed value let add: (Int, Int) -> Int = { a, b in a + b } // Trailing closure + shorthand let doubled = [1,2,3].map { $0 * 2 } // [2,4,6] … - 05
What is the difference between Array, Set, and Dictionary in Swift?
EasyArray, Set and Dictionary are the three standard-library collections, and choosing between them is really a choice about ordering and lookup cost.
// Array var fruits = ["apple", "banana", "cherry"] fruits.append("date") let uppercased = fruits.map { $0.uppercased() } let long = fruits.filter { $0.count > 5 } let joined = fruits.joined(separator: ", ") … - 06
Explain Swift error handling: throws, try, do/catch, Result.
MediumSwift makes failure part of a function's signature: a function that can fail is marked
throwsand every caller must writetry.enum AuthError: Error { case invalidCredentials case networkDown case throttled(retryAfter: TimeInterval) } … - 07
Swift has tuples, ranges and
guard— what problem does each one solve?MediumTuples, ranges and
guardare three small features that turn up in almost every Swift file, and each one replaces something heavier.// Tuples func minMax(_ xs: [Int]) -> (min: Int, max: Int)? { guard let first = xs.first else { return nil } var lo = first, hi = first for x in xs { if x < lo { lo = x }; if x > hi { hi = x } } return (lo, hi) … - 08
Why does Swift refuse to let you write name[3] on a String, and what is a Substring?
EasyA Swift Character is an extended grapheme cluster — what a reader calls one character — and clusters take different amounts of memory, so there is no constant-time way to jump to the fourth one.
let s = "Cafe\u{301}" // "Café" — 5 scalars, 4 Characters print(s.count) // 4 (grapheme clusters, O(n)) print(s.unicodeScalars.count) // 5 print(s == "Café") // true — canonical equivalence // ❌ let c = s[3] // no Int subscript exists … - 09
You have an array of optional strings and want the lengths of the ones that exist. map, compactMap or flatMap?
EasycompactMap is the one that transforms and drops the nils in a single pass, and it is the answer here.
let raw: [String?] = ["swift", nil, "ios", nil, "kotlin"] let lengths = raw.compactMap { $0?.count } // [5, 3, 6] ✅ let wrapped = raw.map { $0?.count } // [5, nil, 3, nil, 6] // Parsing, where the transform itself is failable … - 10
What can Swift's switch do that a C-style switch cannot?
MediumIt matches patterns rather than integer values, the compiler forces it to be exhaustive, and no case ever falls through by accident.
enum Download { case idle case running(progress: Double) case failed(Error, retryIn: TimeInterval) } … - 11
What does inout actually do, and how is it different from passing a class instance?
MediumAn inout parameter is copy-in, copy-out: the function works on a mutable copy and the value is written back over your variable when the call returns.
func normalize(_ values: inout [Double]) { guard let maxValue = values.max(), maxValue > 0 else { return } for i in values.indices { values[i] /= maxValue } // in place, no copy } var samples = [2.0, 4.0, 8.0] … - 12
What is the difference between is, as?, as! and a plain as?
EasyThree of them ask a question about a value's real type at runtime, while a plain
asis an upcast the compiler already knows is safe.class Animal {} class Dog: Animal { func bark() {} } let pets: [Animal] = [Dog(), Animal()] for pet in pets { … - 13
Your log line prints Optional("Ada") instead of Ada — what is Swift showing you, and how do you print the value?
EasyString interpolation of an optional prints the debug description of the
Optionalenum itself, so you get the case and its payload rather than the wrapped value.let name: String? = "Ada" let count: Int? = nil // ❌ prints: user Optional("Ada"), items nil print("user \(name), items \(count)") // warning: debug description for an optional … - 14
An @IBOutlet is declared as UILabel! and the app dies with "unexpectedly found nil" — what does that exclamation mark actually mean?
EasyUILabel!is an ordinaryOptional<UILabel>whose declaration carries a flag telling the compiler to insert a force unwrap wherever the value is used as a non-optional.final class ProfileViewController: UIViewController { @IBOutlet var nameLabel: UILabel! // nil between init and viewDidLoad var name: String = "" // plain model: safe at any moment override func viewDidLoad() { super.viewDidLoad() … - 15
Why does Swift refuse to add an Int to a Double, and why does let half = 1 / 2 give you 0?
EasySwift performs no implicit numeric conversion —
IntandDoubleare unrelated types, so mixing them needs an explicit initializer — and1 / 2is integer division because both literals defaulted toInt.let count = 7 let total = 10 // ❌ no implicit promotion between numeric types // let ratio = count / total * 100.0 // error: Int and Double do not mix // ✅ convert deliberately — and convert before dividing … - 16
The receipt screen shows a total of 59.97000000000001 — what is wrong, and what do you store money in?
MediumDoubleis binary floating point: 19.99, 0.1 and most decimal fractions have no exact binary64 representation, so each arithmetic step adds a little error that eventually surfaces in a formatted total.// ❌ Double: the error is invisible until it is formatted let unitPrice = 19.99 let bad = unitPrice * 3 // 59.97000000000001 print(0.1 + 0.2 == 0.3) // false // ✅ integer minor units: exact and trivially summable … - 17
A view model leaves isLoading true whenever the request throws — how does defer fix that, and what are its rules?
Mediumdeferregisters a block that runs when the current scope exits by any path —return, a thrown error,break, or simply falling off the end — so the cleanup sits next to the thing it undoes instead of being repeated on every exit.@MainActor final class FeedViewModel: ObservableObject { @Published var isLoading = false @Published var items: [Item] = [] let api: API … - 18
Two screens each hold their own copy of a struct Draft, yet editing one changes the other — what went wrong?
MediumCopying a struct copies its stored properties, and a stored class reference copies as a reference — both copies then point at the same object, so value semantics stop at the first class you embed.
final class Attachments { // a class hiding inside a struct var files: [String] = [] } struct Draft { var title: String … - 19
let rest = items.dropFirst() and then rest[0] traps with "Index out of range", although rest is not empty — why?
MediumdropFirstreturns anArraySlicethat shares the base array's index space, so the slice's first element still lives at index 1 andrest[0]is genuinely out of bounds.let items = ["a", "b", "c", "d"] let rest = items.dropFirst() // ArraySlice<String>, not [String] print(rest.count) // 3 print(rest.startIndex) // 1 // print(rest[0]) // ❌ crash: Index out of range … - 20
Xcode fails one arithmetic line with "unable to type-check this expression in reasonable time" — what is the compiler actually doing?
MediumSwift type-checks a whole expression as one constraint system, and every unannotated literal, overloaded operator and generic call multiplies the number of candidate solutions it has to try, until the solver hits its time budget and gives up.
// ❌ one expression where every literal and operator is still negotiable // let score = (base + bonus * 2 - penalty) / Double(count) + (extra ?? 0) * 1.5 // error: the compiler is unable to type-check this expression in reasonable time // ✅ annotate and split: each line is a tiny, independent solve let raw: Double = base + bonus * 2 - penalty …