Swift Generics
Generics let you write flexible, reusable code that works with any type. Instead of writing the same function three times — once for Int, once for String, once for Double — you write it once using a placeholder type, and Swift fills in the real type when you call it. Think of a generic function like a universal power adapter: one design that fits any country's outlet.
The Problem Generics Solve
Without generics, you repeat yourself for every type:
func swapInts(_ a: inout Int, _ b: inout Int) {
let temp = a; a = b; b = temp
}
func swapStrings(_ a: inout String, _ b: inout String) {
let temp = a; a = b; b = temp
}
// Same logic, different types — duplicated codeGeneric Function
Replace the concrete type with a placeholder in angle brackets <T>. Swift replaces T with the actual type at the call site.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
print("x: \(x), y: \(y)") // Output: x: 20, y: 10
var hello = "Hello"
var world = "World"
swapValues(&hello, &world)
print("\(hello) \(world)") // Output: World HelloDiagram: Generic Type Placeholder
func swapValues<T>(_ a: inout T, _ b: inout T)
^
|
T is a placeholder
Call with Int: T becomes Int
Call with String: T becomes String
Call with Double: T becomes Double
One function → works for all types
Generic Types (Structs and Classes)
You can make entire types generic. Swift's own Array and Dictionary are generic types.
struct Box<T> {
var value: T
func describe() {
print("Box contains: \(value)")
}
}
let intBox = Box(value: 42)
intBox.describe() // Output: Box contains: 42
let stringBox = Box(value: "Swift")
stringBox.describe() // Output: Box contains: Swift
let boolBox = Box(value: true)
boolBox.describe() // Output: Box contains: trueGeneric Stack
A stack is a last-in, first-out (LIFO) collection. A generic stack works for any type.
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
var top: Element? {
return items.last
}
var isEmpty: Bool {
return items.isEmpty
}
}
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
intStack.push(3)
print(intStack.top!) // Output: 3
intStack.pop()
print(intStack.top!) // Output: 2Diagram: Stack Behavior (LIFO)
push(1) → [1]
push(2) → [1, 2]
push(3) → [1, 2, 3]
↑
top = 3
pop() → [1, 2] removed: 3
↑
top = 2
Type Constraints
Add constraints to limit which types a generic can accept. Use where or : notation.
func largest<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? a : b
}
print(largest(3, 9)) // Output: 9
print(largest("Apple", "Banana")) // Output: BananaT: Comparable means T must conform to Comparable. This gives the function access to the > operator.
Multiple Type Parameters
func pair<A, B>(_ first: A, _ second: B) -> String {
return "(\(first), \(second))"
}
print(pair(1, "one")) // Output: (1, one)
print(pair(true, 3.14)) // Output: (true, 3.14)Generic Protocol with Associated Types
Protocols use associatedtype as their version of a generic placeholder.
protocol Container {
associatedtype Item
var count: Int { get }
mutating func add(_ item: Item)
func item(at index: Int) -> Item
}
struct Bag<T>: Container {
private var items: [T] = []
var count: Int { items.count }
mutating func add(_ item: T) {
items.append(item)
}
func item(at index: Int) -> T {
return items[index]
}
}
var bag = Bag<String>()
bag.add("Notebook")
bag.add("Pen")
print(bag.item(at: 0)) // Output: Notebook
print(bag.count) // Output: 2Where Clauses
A where clause adds extra requirements to a generic type or function.
func equalContainers<C1: Container, C2: Container>(_ c1: C1, _ c2: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
guard c1.count == c2.count else { return false }
for i in 0..<c1.count {
if c1.item(at: i) != c2.item(at: i) {
return false
}
}
return true
}Opaque Types with some
The some keyword hides the specific return type while guaranteeing the protocol is met. This is common in SwiftUI.
protocol Shape {
func area() -> Double
}
struct Square: Shape {
var side: Double
func area() -> Double { side * side }
}
func makeShape() -> some Shape {
return Square(side: 4.0)
}
let s = makeShape()
print(s.area()) // Output: 16.0Summary
Generics eliminate code duplication by replacing concrete types with placeholders like T. You can constrain generics with protocols (T: Comparable), use multiple type parameters, and build generic data structures like stacks and bags. Protocols use associatedtype for the same concept. Generics make Swift's standard library possible and are a core skill for professional Swift development.
