iZONE

Swift Cheatsheet

A practical Swift guide covering syntax, optionals, protocols, SwiftUI, concurrency, collections, and iOS development workflows.

Resources

What is Swift?

Swift is Apple's programming language for building iOS, macOS, and watchOS apps. It's fast, safe by design, and honestly a lot more readable than Objective-C — which is what it replaced.

where swift is used

iOS and macOS appsprimary use
  • The primary language for all Apple platform development.
  • Replaced Objective-C as the standard — all new Apple APIs are Swift-first.
watchOS and tvOSall Apple platforms
  • Same language, same concepts — runs on Apple Watch and Apple TV.
  • SwiftUI makes sharing UI code across platforms easier.
server-side SwiftVapor framework
  • Vapor is the main Swift web framework.
  • Less common than iOS but growing — same language, different target.

your first swift program

Swift

// no class needed — just start writing
print("Hello, World!")

// print with string interpolation
let name = "Alice"
print("Hello, \(name)!")
// Hello, Alice!

// print without newline
print("Hello ", terminator: "")
print("World")
// Hello World

comments and special markers

Swift

// single-line comment

/*
  multi-line comment
  can span many lines
*/

/// documentation comment
/// shown in Xcode's Quick Help panel
/// - Parameter name: the user's name
/// - Returns: a greeting string
func greet(name: String) -> String {
    return "Hello, \(name)!"
}

// MARK: - View Settings
// appears as a divider in Xcode jump bar

// TODO: update this logic later
// shown as a reminder in Xcode

// FIXME: crashes on empty input
// shown as a bug reminder in Xcode

Variables & Data Types

Swift has two kinds of storage: constants and variables. The rule is simple — use let by default. Switch to var only when the value genuinely needs to change.

let and var

Swift

// let: constant — cannot be changed
let pi = 3.14159
let appName = "MyApp"
// pi = 3 ← compile error

// var: variable — can be changed
var score = 0
score = 10   // fine
score += 5   // fine

// Swift infers the type automatically
let city    = "Colombo"  // String
let age     = 25         // Int
let price   = 9.99       // Double
let isReady = true       // Bool

basic data types

Swift

// explicit type annotations
let count:   Int     = 42
let price:   Double  = 19.99
let ratio:   Float   = 0.5
let flag:    Bool    = true
let name:    String  = "Alice"
let initial: Character = "A"

// typealias — give a type a new name
typealias UserID = Int
let myID: UserID = 1001

// type conversion — always explicit
let intVal  = 10
let dblVal  = Double(intVal)  // 10.0
let strVal  = String(intVal)  // "10"
let backInt = Int("42") ?? 0  // 42

tuples — group values without a class

Swift

// basic tuple
let player = ("Alice", 5, 150)
player.0  // "Alice"
player.1  // 5
player.2  // 150

// named tuple — easier to read
let user = (name: "Alice", age: 28, score: 150)
user.name   // "Alice"
user.age    // 28
user.score  // 150

// decompose into variables
let (userName, userAge, userScore) = user
print(userName)  // Alice

// ignore parts you don't need
let (justName, _, _) = user

// return multiple values from a function
func minMax(arr: [Int]) -> (min: Int, max: Int) {
    return (arr.min()!, arr.max()!)
}
let result = minMax(arr: [3, 1, 9, 2])
result.min  // 1
result.max  // 9

Strings

Swift strings are full Unicode by default. String interpolation with \() is cleaner than concatenation and works with any value — not just text.

string interpolation and multiline

Swift

let name  = "Alice"
let score = 95
let pi    = 3.14159

// interpolation — clean and readable
print("Hello, \(name)!")
print("Score: \(score * 2)")
print("Pi is about \(pi)")

// expression inside interpolation
print("Next year: \(2024 + 1)")

// concatenation with +
let full = "Hello, " + name + "!"

// multiline string — triple quotes
let message = """
    Dear \(name),
    Your score is \(score).
    Well done!
    """
// leading spaces trimmed from each line

common string methods

Swift

var s = "  Hello, Swift!  "

// length and checks
s.count               // 17
s.isEmpty             // false
"".isEmpty            // true

let clean = s.trimmingCharacters(
    in: .whitespaces
)
// "Hello, Swift!"

// case
clean.uppercased()    // "HELLO, SWIFT!"
clean.lowercased()    // "hello, swift!"

// search
clean.contains("Swift")      // true
clean.hasPrefix("Hello")     // true
clean.hasSuffix("!")         // true

// modify (returns new String — Strings are value types)
clean.replacingOccurrences(
    of: "Swift",
    with: "World"
)
// "Hello, World!"

// split
"a,b,c".components(
    separatedBy: ","
)
// ["a", "b", "c"]

// starts and ends
clean.first  // Optional("H")
clean.last   // Optional("!")

Operators

Most operators work exactly as you'd expect. The ones worth paying attention to in Swift are the range operators and nil coalescing — they show up constantly in real code.

arithmetic, comparison, and logical

Swift

// arithmetic
5 + 3   // 8
5 - 3   // 2
5 * 3   // 15
5 / 2   // 2   (integer division — decimal dropped)
5 % 2   // 1   (remainder)
5.0 / 2 // 2.5 (Double division)

// compound assignment
var x = 10
x += 5  // 15
x -= 3  // 12
x *= 2  // 24
x /= 4  // 6

// comparison — returns Bool
5 == 5   // true
5 != 3   // true
5 > 3    // true
5 < 3    // false
5 >= 5   // true
5 <= 4   // false

// logical
true && false  // false (AND)
true || false  // true  (OR)
!true          // false (NOT)

// identity — checks same object in memory
// (for classes only)
let a = SomeClass()
let b = a
a === b  // true (same instance)
a !== b  // false

range operators and nil coalescing

Swift

// closed range: includes both ends
for i in 1...5 {
    print(i)  // 1 2 3 4 5
}

// half-open range: excludes upper end
for i in 1..<5 {
    print(i)  // 1 2 3 4
}

// one-sided range: useful for array slicing
let names = ["Alice", "Bob", "Carol", "Dave"]
names[2...]   // ["Carol", "Dave"]
names[...1]   // ["Alice", "Bob"]
names[..<2]   // ["Alice", "Bob"]

// nil coalescing: ?? provides a fallback
let input: String? = nil
let value = input ?? "default"
// "default"

// chain ?? for multiple fallbacks
let a: String? = nil
let b: String? = nil
let c = "found"
let result = a ?? b ?? c  // "found"

Control Flow

Swift's switch is one of the most powerful in any language — it matches ranges, tuples, and types without fall-through. And guard is worth learning early: it makes code much easier to follow.

if, else if, else

Swift

let score = 75

if score >= 90 {
    print("A")
} else if score >= 75 {
    print("B")
} else if score >= 60 {
    print("C")
} else {
    print("F")
}

// ternary operator
let label = score >= 50 ? "Pass" : "Fail"

// if as an expression (Swift 5.9+)
let grade = if score >= 90 { "A" }
            else if score >= 75 { "B" }
            else { "F" }

switch — pattern matching

Swift

let score = 82

// match ranges
switch score {
case 90...100:
    print("A")
case 75..<90:
    print("B")
case 60..<75:
    print("C")
default:
    print("F")
}

// match multiple values
let day = "Sat"
switch day {
case "Mon", "Tue", "Wed", "Thu", "Fri":
    print("Weekday")
case "Sat", "Sun":
    print("Weekend")
default:
    print("Unknown")
}

// match with where clause
let point = (2, -3)
switch point {
case let (x, y) where x == y:
    print("On diagonal")
case let (x, y) where x > 0 && y > 0:
    print("First quadrant")
case let (x, _) where x < 0:
    print("Left side")
default:
    print("Somewhere else")
}

guard — early exit

Swift

// without guard — nested and hard to read
func processUser(name: String?) {
    if let name = name {
        if name.count > 2 {
            print("Hello, \(name)")
            // real logic buried in nesting
        }
    }
}

// with guard — flat and readable
func processUser(name: String?) {
    guard let name = name else {
        print("Name is missing")
        return  // must exit here
    }

    // name is available here as non-optional
    guard name.count > 2 else {
        print("Name too short")
        return
    }

    print("Hello, \(name)")
    // real logic at the top level
}

// guard with multiple conditions
func login(user: String?, pass: String?) {
    guard
        let user = user,
        let pass = pass,
        !user.isEmpty,
        pass.count >= 8
    else {
        print("Invalid credentials")
        return
    }
    print("Welcome, \(user)")
}

Loops

for-in is the workhorse of Swift loops. It works with ranges, arrays, dictionaries, and anything that conforms to Sequence — which is most things you'll loop over.

for-in loops

Swift

// range
for i in 1...5 {
    print(i)  // 1 2 3 4 5
}

// ignore the loop variable
for _ in 1...3 {
    print("hello")  // prints 3 times
}

// stride — custom step
for i in stride(from: 0, to: 10, by: 2) {
    print(i)  // 0 2 4 6 8
}

// count down
for i in stride(from: 5, through: 1, by: -1) {
    print(i)  // 5 4 3 2 1
}

// iterate array
let fruits = ["Apple", "Mango", "Grape"]
for fruit in fruits {
    print(fruit)
}

// with index using enumerated
for (index, fruit) in fruits.enumerated() {
    print("\(index): \(fruit)")
}

// filter during loop with where
for fruit in fruits where fruit.count > 5 {
    print(fruit)  // "Mango", "Grape" skipped
}

// forEach — closure style
fruits.forEach { fruit in
    print(fruit)
}

while, repeat-while, break, continue

Swift

// while
var i = 0
while i < 5 {
    print(i)
    i += 1
}

// repeat-while — runs at least once
var j = 10
repeat {
    print(j)  // prints 10
    j += 1
} while j < 5

// break — exit loop immediately
for i in 1...10 {
    if i == 5 { break }
    print(i)  // 1 2 3 4
}

// continue — skip this iteration
for i in 1...5 {
    if i == 3 { continue }
    print(i)  // 1 2 4 5
}

// labelled loops — break outer loop
outer: for i in 1...3 {
    for j in 1...3 {
        if i == 2 && j == 2 {
            break outer
        }
        print("\(i),\(j)")
    }
}

Optionals

Optionals are one of Swift's best features. Every value is non-nil by default — you have to explicitly say a variable can be nil by adding ?. That one rule eliminates an entire category of crashes.

what optionals are

Swift

// non-optional: cannot be nil
var name: String = "Alice"
// name = nil ← compile error

// optional: might be nil
var nickname: String? = nil
var city: String? = "Colombo"

// you cannot use an optional directly
// print(nickname.count) ← compile error
// must unwrap first

// optional values wrap in Optional()
print(city)    // Optional("Colombo")
print(nickname) // nil

if let, guard let, and nil coalescing

Swift

var username: String? = "Alice"

// if let — value lives inside the block
if let name = username {
    print("Hello, \(name)")
} else {
    print("No name provided")
}

// shorthand (Swift 5.7+) — same name
if let username {
    print("Hello, \(username)")
}

// guard let — value lives AFTER the guard
func greet(username: String?) {
    guard let name = username else {
        print("No name")
        return
    }
    // name available here
    print("Hello, \(name)")
}

// nil coalescing ?? — fallback value
let display = username ?? "Guest"
print(display)  // "Alice"

let noName: String? = nil
let fallback = noName ?? "Guest"
print(fallback) // "Guest"

optional chaining and force unwrap

Swift

// optional chaining — stops at nil, returns nil
struct Address {
    var city: String?
}
struct Person {
    var address: Address?
}

let person: Person? = Person(
    address: Address(city: "Colombo")
)

// each ? propagates nil if anything is nil
let city = person?.address?.city
// Optional("Colombo")

// chain with methods
let upper = person?.address?.city?.uppercased()
// Optional("COLOMBO")

// if any link is nil, whole thing returns nil
let nobody: Person? = nil
nobody?.address?.city  // nil (no crash)

// force unwrap — use with caution
let forced = person!.address!.city!
// "Colombo" — crashes if any is nil

// safe pattern: check first
if let city = person?.address?.city {
    print(city.uppercased())
}

Every ! in your code is a potential crash. If you see yourself using ! often, use if let or guard let instead.

Functions

Swift functions have a feature most languages don't — argument labels. The label is what the caller writes, the parameter name is what you use inside. It makes function calls read almost like English.

defining and calling functions

Swift

// no return value
func greet() {
    print("Hello!")
}

// with parameter and return type
func add(a: Int, b: Int) -> Int {
    return a + b
}

// single expression — implicit return
func square(n: Int) -> Int {
    n * n
}

// multiple return values with tuple
func minMax(nums: [Int]) -> (min: Int, max: Int) {
    return (nums.min()!, nums.max()!)
}

// call the functions
greet()               // Hello!
add(a: 3, b: 4)      // 7
square(n: 5)          // 25
let r = minMax(nums: [1, 9, 3, 7])
r.min  // 1
r.max  // 9

argument labels and default parameters

Swift

// argument label vs parameter name
// func name(label paramName: Type)
func greet(to name: String) {
    print("Hello, \(name)!")
    // 'name' used inside
}
greet(to: "Alice")  // 'to' used by caller

// suppress label with _
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}
add(3, 4)  // no labels needed

// default parameters
func createUser(
    name: String,
    role: String = "user",
    isActive: Bool = true
) {
    print("\(name) | \(role) | \(isActive)")
}

createUser(name: "Alice")
// Alice | user | true

createUser(name: "Bob", role: "admin")
// Bob | admin | true

createUser(name: "Carol", isActive: false)
// Carol | user | false

closures — basics and trailing syntax

Swift

// full closure syntax
let add = { (a: Int, b: Int) -> Int in
    return a + b
}
add(3, 4)  // 7

// type inferred — shorter
let double = { (n: Int) -> Int in n * 2 }

// shorthand argument names
let triple = { $0 * 3 }
triple(5)  // 15

// passing a closure as an argument
func operate(
    _ a: Int,
    _ b: Int,
    using op: (Int, Int) -> Int
) -> Int {
    return op(a, b)
}

// inline closure
operate(3, 4, using: { $0 + $1 })  // 7

// trailing closure — last arg goes outside ()
operate(3, 4) { $0 * $1 }  // 12

// trailing closure with named params
[1, 2, 3].map { number in
    number * 10
}
// [10, 20, 30]

Collections

Swift has three main collection types. Array for ordered lists, Dictionary for key-value pairs, and Set for unique unordered values. All three are value types — assigning one copies it.

Array

Swift

// create
var fruits = ["Apple", "Mango", "Grape"]
var nums   = [Int]()  // empty array
var zeros  = Array(repeating: 0, count: 5)

// access
fruits[0]          // "Apple"
fruits.first       // Optional("Apple")
fruits.last        // Optional("Grape")
fruits.count       // 3
fruits.isEmpty     // false

// modify
fruits.append("Kiwi")
fruits.insert("Berry", at: 1)
fruits.remove(at: 0)
fruits.removeFirst()
fruits.removeLast()
fruits[0] = "Lemon"  // update

// search
fruits.contains("Mango")   // true
fruits.firstIndex(of: "Grape")  // Optional(1)

// sort
fruits.sort()              // in-place (var only)
let sorted = fruits.sorted()  // new array

// iterate
for fruit in fruits { print(fruit) }

Dictionary and Set

Swift

// ── Dictionary ──────────────────────────────
var scores: [String: Int] = [
    "Alice": 90,
    "Bob":   85
]

// access — always returns Optional
scores["Alice"]           // Optional(90)
scores["Alice"] ?? 0      // 90

// add and update
scores["Carol"] = 92
scores["Alice"] = 95      // update

// remove
scores["Bob"] = nil       // removes Bob
scores.removeValue(forKey: "Bob")

// check
scores.keys.contains("Alice")  // true
scores.count                   // 2

// iterate
for (name, score) in scores {
    print("\(name): \(score)")
}

// ── Set ──────────────────────────────────
var tags: Set = ["swift", "ios", "swift"]
// {"swift", "ios"} — duplicates removed

tags.insert("xcode")
tags.remove("ios")
tags.contains("swift")  // true
tags.count              // 2

// set operations
let a: Set = [1, 2, 3, 4]
let b: Set = [3, 4, 5, 6]

a.union(b)         // {1,2,3,4,5,6}
a.intersection(b)  // {3,4}
a.subtracting(b)   // {1,2}

filter, map, reduce, and compactMap

Swift

let nums = [1, 2, 3, 4, 5, 6]

// filter: keep only even numbers
nums.filter { $0 % 2 == 0 }
// [2, 4, 6]

// map: multiply each by 10
nums.map { $0 * 10 }
// [10, 20, 30, 40, 50, 60]

// reduce: sum all values
// 0 is the starting value
nums.reduce(0) { $0 + $1 }  // 21
// shorthand:
nums.reduce(0, +)            // 21

// compactMap: map and remove nils
let strings = ["1", "two", "3", "four"]
strings.compactMap { Int($0) }
// [1, 3]  — "two" and "four" returned nil

// chain operations
let result = nums
    .filter { $0 % 2 == 0 }  // [2, 4, 6]
    .map    { $0 * $0 }       // [4, 16, 36]
    .reduce(0, +)             // 56

// sorted with custom comparator
let names = ["Carol", "Alice", "Bob"]
names.sorted { $0 < $1 }      // A→Z
names.sorted { $0 > $1 }      // Z→A
names.sorted { $0.count < $1.count }  // by length

Structs & Classes

Swift has both structs and classes — and unlike most languages, structs are the preferred choice. The key difference is that structs are value types and classes are reference types. That distinction matters a lot.

struct — value type

Swift

struct Person {
    var name: String
    var age:  Int

    // memberwise init is generated automatically
    // no need to write it unless you want custom logic

    // method that modifies a property
    // must be marked 'mutating'
    mutating func birthday() {
        age += 1
    }

    // computed property
    var greeting: String {
        "Hi, I'm \(name)"
    }
}

var alice = Person(name: "Alice", age: 28)
var copy  = alice       // makes a full copy

copy.name = "Carol"     // only copy is changed

print(alice.name)       // "Alice" — unchanged
print(copy.name)        // "Carol"

alice.birthday()
print(alice.age)        // 29

class — reference type

Swift

class Animal {
    var name: String

    // class requires explicit init
    init(name: String) {
        self.name = name
    }

    func speak() {
        print("\(name) makes a sound")
    }
}

// inheritance
class Dog: Animal {
    var breed: String

    init(name: String, breed: String) {
        self.breed = breed
        super.init(name: name)
    }

    override func speak() {
        print("\(name) barks")
    }
}

let dog1 = Dog(name: "Rex", breed: "Lab")
let dog2 = dog1  // same object, NOT a copy

dog2.name = "Max"
print(dog1.name)  // "Max" — both changed!

// prevent subclassing
final class Singleton {
    static let shared = Singleton()
    private init() {}
}

struct vs class — when to use which

Swift

// ✅ struct — for data models
struct Point {
    var x: Double
    var y: Double
}

struct User {
    let id: Int
    var name: String
    var email: String
}

// ✅ class — for shared services
class NetworkManager {
    static let shared = NetworkManager()
    var baseURL = "https://api.example.com"

    func fetch(path: String) {
        // network call
    }
}

// use the shared instance anywhere
NetworkManager.shared.fetch(path: "/users")

Enums

Swift enums are far more capable than in most languages. They can carry associated values, have methods, and conform to protocols. Once you start using them properly, you'll reach for them constantly.

basic enums and raw values

Swift

// basic enum
enum Direction {
    case north
    case south
    case east
    case west
}

let dir = Direction.north

// String raw values
// each case's rawValue is its name by default
enum Status: String {
    case active   = "active"
    case inactive = "inactive"
    case pending  = "pending"
}

let s = Status.active
s.rawValue   // "active"

// create from raw value — returns Optional
let fromRaw = Status(rawValue: "pending")
// Optional(Status.pending)

// Int raw values — auto-increments from 0
enum Priority: Int {
    case low    // 0
    case medium // 1
    case high   // 2
}
Priority.high.rawValue  // 2

// CaseIterable — loop over all cases
enum Day: String, CaseIterable {
    case mon, tue, wed, thu, fri, sat, sun
}

for day in Day.allCases {
    print(day.rawValue)
}

associated values and switch

Swift

// associated values
enum NetworkResult {
    case loading
    case success(data: String)
    case failure(error: String, code: Int)
}

let result = NetworkResult.success(
    data: "User data loaded"
)

// switch to extract associated values
switch result {
case .loading:
    print("Loading...")

case .success(let data):
    print("Got: \(data)")

case .failure(let error, let code):
    print("Error \(code): \(error)")
}

// if case — check one specific case
if case .success(let data) = result {
    print("Success: \(data)")
}

// enum with methods
enum Coin {
    case penny, nickel, dime, quarter

    var value: Int {
        switch self {
        case .penny:   return 1
        case .nickel:  return 5
        case .dime:    return 10
        case .quarter: return 25
        }
    }
}

Coin.quarter.value  // 25

Protocols & Extensions

Protocols define what something can do — without saying how. Extensions let you add new capabilities to any existing type, even ones you didn't write. Together they're the foundation of Swift's design philosophy.

protocols — define a contract

Swift

// define a protocol
protocol Greetable {
    var name: String { get }
    func greet() -> String
}

// protocol with default implementation
protocol Describable {
    var description: String { get }

    func describe()
}

extension Describable {
    func describe() {
        // default — conforming types get this free
        print(description)
    }
}

// conform to a protocol
struct User: Greetable, Describable {
    var name: String

    func greet() -> String {
        "Hello, I'm \(name)"
    }

    var description: String {
        "User: \(name)"
    }
    // describe() comes from the extension — free
}

let u = User(name: "Alice")
u.greet()     // "Hello, I'm Alice"
u.describe()  // "User: Alice"

// built-in protocols worth knowing
struct Point: Equatable, Hashable, Codable {
    var x: Double
    var y: Double
    // compiler generates == and hash automatically
}

extensions — add to any type

Swift

// add a method to an existing type
extension String {
    func shout() -> String {
        return self.uppercased() + "!!!"
    }

    var wordCount: Int {
        self.split(separator: " ").count
    }

    var isPalindrome: Bool {
        self == String(self.reversed())
    }
}

"hello".shout()              // "HELLO!!!"
"hello world".wordCount      // 2
"racecar".isPalindrome       // true

// extend Int
extension Int {
    var isEven: Bool { self % 2 == 0 }
    var doubled: Int { self * 2 }

    func times(_ action: () -> Void) {
        for _ in 0..<self { action() }
    }
}

4.isEven    // true
5.doubled   // 10
3.times { print("Hello") }  // prints 3 times

// add protocol conformance via extension
extension User: CustomStringConvertible {
    var description: String {
        "User(\(name))"
    }
}

Error Handling

Swift error handling is explicit — functions that can fail must say so with throws. That said, you have options for how to deal with errors. try?, Result, and try! all have their place.

throws, try, and do-catch

Swift

// define custom errors
enum LoginError: Error {
    case emptyUsername
    case wrongPassword
    case accountLocked(reason: String)
}

// function that can throw
func login(user: String, pass: String) throws {
    guard !user.isEmpty else {
        throw LoginError.emptyUsername
    }
    guard pass == "secret" else {
        throw LoginError.wrongPassword
    }
    print("Logged in as \(user)")
}

// call with do-catch
do {
    try login(user: "Alice", pass: "secret")
} catch LoginError.emptyUsername {
    print("Username is empty")
} catch LoginError.wrongPassword {
    print("Wrong password")
} catch LoginError.accountLocked(let reason) {
    print("Locked: \(reason)")
} catch {
    // catch-all — 'error' is the thrown value
    print("Unknown error: \(error)")
}

try?, try!, and Result type

Swift

// try? — returns nil on failure
let result = try? login(user: "Alice", pass: "wrong")
// result is nil — no crash, error swallowed

// combine with if let
if let _ = try? login(user: "Alice", pass: "secret") {
    print("Login worked")
}

// try! — crashes on failure (use rarely)
try! login(user: "Alice", pass: "secret")

// Result type — explicit success or failure
func fetchUser(id: Int) -> Result<String, Error> {
    if id > 0 {
        return .success("User \(id)")
    } else {
        return .failure(LoginError.emptyUsername)
    }
}

// handle Result with switch
switch fetchUser(id: 1) {
case .success(let user):
    print("Found: \(user)")
case .failure(let error):
    print("Error: \(error)")
}

// or with get()
let user = try? fetchUser(id: 1).get()

try! crashes your app if the function throws. Only use it when you are 100% certain failure cannot happen.

Tips & Good Habits

These habits make Swift code safer, cleaner, and easier to read. Most of them are patterns the Swift community has settled on — following them means other Swift developers will immediately feel at home in your code.

prefer let over var

Swift

// ❌ var everywhere — harder to track
var userId   = 1
var userName = "Alice"
var apiURL   = "https://api.example.com"

// ✅ let where the value won't change
let userId   = 1
let userName = "Alice"
let apiURL   = "https://api.example.com"

// var only when you actually need to change it
var retryCount = 0
retryCount += 1

prefer guard over nested if let

Swift

// ❌ nested if let — hard to follow
func processOrder(
    user: User?,
    item: Item?,
    address: Address?
) {
    if let user = user {
        if let item = item {
            if let address = address {
                // real logic buried 3 levels deep
                print("Order placed")
            }
        }
    }
}

// ✅ guard — flat and readable
func processOrder(
    user: User?,
    item: Item?,
    address: Address?
) {
    guard let user    = user    else { return }
    guard let item    = item    else { return }
    guard let address = address else { return }

    // all values available here, no nesting
    print("Order for \(user.name)")
}

Deep nesting is called the 'pyramid of doom' in Swift. guard flattens it by exiting early on invalid states.

prefer struct over class

Swift

// ✅ struct for data models
// — copied safely
// — no memory management needed
// — compiler can optimise better
struct Product {
    let id: Int
    var name: String
    var price: Double
}

// ✅ class when you need
// — inheritance
// — shared mutable state
// — interop with Objective-C
class CartManager {
    static let shared = CartManager()
    var items: [Product] = []

    func add(_ product: Product) {
        items.append(product)
    }
}

// rule of thumb:
// start with struct
// switch to class when you have a specific reason

handy one-liners worth bookmarking

Swift

// zip: combine two arrays into pairs
let names  = ["Alice", "Bob", "Carol"]
let scores = [90, 85, 92]

for (name, score) in zip(names, scores) {
    print("\(name): \(score)")
}

// defer: run code when scope exits (like finally)
func readFile() {
    let file = openFile()
    defer { file.close() }  // always closes
    // read file here...
}

// lazy: skip computation until it's needed
let bigList = (1...1_000_000)
    .lazy
    .filter { $0 % 2 == 0 }
    .map    { $0 * $0 }
    .first  // computes only what's needed

// flatMap: flatten nested arrays
let nested = [[1, 2], [3, 4], [5, 6]]
nested.flatMap { $0 }  // [1, 2, 3, 4, 5, 6]

// check type with is
let value: Any = "hello"
if value is String {
    print("it's a String")
}

// cast with as?
let asString = value as? String  // Optional("hello")
let asInt    = value as? Int     // nil — safe, no crash
Was this helpful?

No login required to share feedback

More Cheatsheets

Keep your reference handy

Explore more zero-to-hero cheatsheets for the tools you use daily.