Swift’s operator set is broad, but a small set covers 95% of what you write daily. This guide focuses on the operators that matter for real code, with quick examples and pitfalls to avoid.
Arithmetic and assignment
let sum = 2 + 3 // 5
var counter = 0
counter += 1 // 1
let average = 7.0 / 2.0 // 3.5
- Prefer
+=/-=overcounter = counter + 1for clarity. - When mixing ints and doubles, convert explicitly (
Double(count)), not implicitly.
Comparisons
let isEqual = "abc" == "abc" // true
let isOrdered = 3 < 5 // true
let isDifferent = UUID() != UUID() // almost certainly true
Use == and != for value equality. Avoid comparing floating numbers directly unless you control the precision (e.g., use tolerances).
Optionals: nil-coalescing and optional chaining
let fallback = username ?? "Guest"
let length = user.profile?.bio?.count ?? 0
??provides a safe default.- Optional chaining (
?.) stops early on nil without crashing.
Ternary is fine when it is short and clear:
let style = isDarkMode ? "dark" : "light"
Avoid nesting ternaries; use if/else when readability drops.
Ranges and loops
for i in 0..<3 { print(i) } // 0,1,2
for i in 1...3 { print(i) } // 1,2,3
Use ..< for half-open ranges (common in arrays) and ... for closed ranges.
Logical operators
if isLoggedIn && hasProfile {
showDashboard()
} else if isLoggedIn || hasTrial {
showLimitedExperience()
}
&&short-circuits: the right side runs only if the left side is true.||short-circuits: the right side runs only if the left side is false.
Combine truthy conditions carefully. When side effects are present, keep them out of the condition to avoid surprises:
guard isAuthenticated, hasProfile else {
return
}
Identity vs equality
class A {}
let a = A(); let b = a; let c = A()
(a === b) // true (same instance)
(a === c) // false (different instances)
Use === only for reference identity checks. For value semantics (structs, enums, strings), stick to ==.
Precedence and grouping
Swift’s precedence rules are sensible, but parentheses beat memory:
let allowed = isAdmin || (isMember && isPaid)
Avoid clever chaining when it obscures intent. If you are not sure about precedence, add parentheses.
Nil-coalescing vs default creation
?? is great until the right-hand side is expensive:
let cached = cache[id] ?? loadFromDisk(id) // loadFromDisk runs only if nil
Prefer a closure when you want lazy creation:
let cached = cache[id] ?? {
let loaded = loadFromDisk(id)
cache[id] = loaded
return loaded
}()
Pattern matching with switch
let code = 200
switch code {
case 200:
print("OK")
case 400...499:
print("Client error")
case 500...599:
print("Server error")
default:
print("Unknown")
}
switch must be exhaustive. Ranges and multiple patterns keep it readable.
You can match tuples to keep validation tight:
let credentials = (user, password)
switch credentials {
case ("admin", "secret"):
print("welcome")
case (_, ""):
print("missing password")
default:
break
}
Custom operators: resist the urge
Swift lets you define your own operators, but most teams ban them. Prefer readable methods unless you have a strong convention (e.g., a DSL).
Bitwise (practical recap)
let read: UInt8 = 0b0001
let write: UInt8 = 0b0010
let rw = read | write // union
let isReadable = (rw & read) != 0
Use bitwise flags when performance or legacy formats require them. Otherwise, favor enums with OptionSet.
Overflow operators (&+, &-, &*) bypass overflow traps. Use them only when you are certain overflow is acceptable (like wrapping counters).
Takeaways
- Reach for
??,?., and range operators daily. - Use
===only for reference checks;==for value equality. - Keep
switchexhaustive and readable with ranges and combined cases. - Avoid custom operators unless your team has a clear standard.
- Reach for parentheses when precedence is unclear, and keep side effects out of conditions.
- Prefer
OptionSetover raw bitwise flags for readability, and reserve overflow operators for deliberate wrapping.