Swift Basics
Irbisa · cheatsheetSeptember 13, 2026

Swift Basics

Variables, optionals, types, control flow, closures, value vs reference

Junior Developer20 itemscompressed for a skim
  1. 01

    What is the difference between let and var in Swift?

    Easy

    A let binding can be assigned exactly once; a var binding can be reassigned as often as you like.

    let name = "Alice"
    // name = "Bob" // ❌ compile error
    
    var counter = 0
    counter += 1 // ✅
    …
  2. 02

    What are optionals in Swift and how do you safely unwrap them?

    Easy

    An 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())
    }
    …
  3. 03

    Explain value types vs reference types in Swift.

    Easy

    Swift 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
    …
  4. 04

    What are closures in Swift and how do capture lists work?

    Medium

    Closures 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]
    …
  5. 05

    What is the difference between Array, Set, and Dictionary in Swift?

    Easy

    Array, 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: ", ")
    …
  6. 06

    Explain Swift error handling: throws, try, do/catch, Result.

    Medium

    Swift makes failure part of a function's signature: a function that can fail is marked throws and every caller must write try.

    enum AuthError: Error {
      case invalidCredentials
      case networkDown
      case throttled(retryAfter: TimeInterval)
    }
    …
  7. 07

    Swift has tuples, ranges and guard — what problem does each one solve?

    Medium

    Tuples, ranges and guard are 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)
    …
  8. 08

    Why does Swift refuse to let you write name[3] on a String, and what is a Substring?

    Easy

    A 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
    …
  9. 09

    You have an array of optional strings and want the lengths of the ones that exist. map, compactMap or flatMap?

    Easy

    compactMap 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. 10

    What can Swift's switch do that a C-style switch cannot?

    Medium

    It 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. 11

    What does inout actually do, and how is it different from passing a class instance?

    Medium

    An 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. 12

    What is the difference between is, as?, as! and a plain as?

    Easy

    Three of them ask a question about a value's real type at runtime, while a plain as is 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. 13

    Your log line prints Optional("Ada") instead of Ada — what is Swift showing you, and how do you print the value?

    Easy

    String interpolation of an optional prints the debug description of the Optional enum 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. 14

    An @IBOutlet is declared as UILabel! and the app dies with "unexpectedly found nil" — what does that exclamation mark actually mean?

    Easy

    UILabel! is an ordinary Optional<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. 15

    Why does Swift refuse to add an Int to a Double, and why does let half = 1 / 2 give you 0?

    Easy

    Swift performs no implicit numeric conversion — Int and Double are unrelated types, so mixing them needs an explicit initializer — and 1 / 2 is integer division because both literals defaulted to Int.

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

    The receipt screen shows a total of 59.97000000000001 — what is wrong, and what do you store money in?

    Medium

    Double is 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. 17

    A view model leaves isLoading true whenever the request throws — how does defer fix that, and what are its rules?

    Medium

    defer registers 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. 18

    Two screens each hold their own copy of a struct Draft, yet editing one changes the other — what went wrong?

    Medium

    Copying 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. 19

    let rest = items.dropFirst() and then rest[0] traps with "Index out of range", although rest is not empty — why?

    Medium

    dropFirst returns an ArraySlice that shares the base array's index space, so the slice's first element still lives at index 1 and rest[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. 20

    Xcode fails one arithmetic line with "unable to type-check this expression in reasonable time" — what is the compiler actually doing?

    Medium

    Swift 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
    …