Swift Cheatsheet

Memory & Performance

Use this Swift reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Value semantics and copy-on-write

var a = [1, 2, 3]
var b = a                    // no copy yet, storage is shared
b.append(4)                  // the copy happens here (copy-on-write)

Array, Dictionary, Set, and String are value types with COW storage: cheap to pass around, and copies materialize only on mutation of a shared buffer. Plain structs copy eagerly, but small structs are register-cheap.

ARC: strong, weak, unowned

final class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

final class Apartment {
    weak var tenant: Person?      // back-reference stays weak, cycle broken
}
ReferenceOptionalityWhen the target deallocates
strong (default)anyKeeps it alive
weakmust be an optional varBecomes nil
unownednon-optionalCrash on access

Classes are reference-counted (ARC). Two objects holding strong references to each other never deallocate. Use weak for delegates and parent/back pointers, unowned only when the target provably outlives the reference.

Closure capture cycles

final class Downloader {
    var onProgress: ((Double) -> Void)?

    func start() {
        onProgress = { [weak self] p in   // without weak: self → closure → self leak
            guard let self else { return }
            self.render(p)
        }
    }
    func render(_ p: Double) {}
}

Value types never create retain cycles. Only reference types that store closures referring back to themselves need capture lists.

mutating and controlled mutation

struct Counter {
    private(set) var value = 0            // public read, internal write
    mutating func inc() { value += 1 }    // struct methods that write need `mutating`
}

var c = Counter()
c.inc()                                    // requires var, not let

Performance checklist

var out: [Int] = []
out.reserveCapacity(n)                    // skip repeated reallocation

var s = ""
s.reserveCapacity(1 << 16)
for x in answers { s += "\(x)\n" }        // amortized O(1) append
print(s, terminator: "")

// Lazy chains stop early instead of building intermediate arrays
let firstBig = nums.lazy.map { $0 * $0 }.first { $0 > 100 }
PitfallFix
removeFirst() in a loop, O(n) eachHead index, or Deque from swift-collections
insert(_, at: 0) in a loopAppend, then reversed() once
array.contains inside a loop, O(n²) totalBuild a Set first
Growing arrays without capacityreserveCapacity(_:)
Benchmarking debug buildsswift build -c release / swiftc -O

Overflow and explicit numeric conversion

let x: Int32 = 2_000_000_000
// let boom = x + x              // runtime crash: overflow traps by default
let wrapped = x &+ x             // overflow operators wrap: &+ &- &*

let ratio = Double(done) / Double(total)   // conversions are always explicit
UInt8(clamping: 300)             // 255
Int8(truncatingIfNeeded: 300)    // 44, keeps the low bits

Measuring

let start = ContinuousClock.now
work()
print(ContinuousClock.now - start)         // "0.123 seconds"

Always measure optimized builds. Debug builds skip inlining and bounds-check aggressively, so debug timings are meaningless for judging algorithmic code.