Generics
Generics let you write flexible, reusable code that works with any type,
subject to requirements you define. Instead of writing one function per
concrete type, you write it once over a placeholder type and the
compiler specializes it for every type you use it with — keeping full
type safety and zero runtime cost. Generics are among Swift's most
powerful features, and much of the standard library (Array,
Dictionary, Optional) is built on them.
The problem generics solve
Suppose you need a function that swaps two values. Without generics, you'd write a separate near-identical function for every type, and they'd differ only in the type annotations.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a; a = b; b = temp
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temp = a; a = b; b = temp // identical body, different type
}
The bodies are word-for-word the same. Duplicating logic like this is tedious and error-prone. Generics express the idea once: "swap two values of the same type, whatever that type is."
Generic functions and type parameters
A generic function declares one or more
type parameters in angle brackets after its name. A type
parameter is a placeholder — by convention a single capital letter like
T — that stands in for an actual type chosen at each call
site. Swift infers the concrete type from the arguments you pass.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temp = a; a = b; b = temp
}
var x = 3, y = 107
swapTwoValues(&x, &y)
print(x, y) // → 107 3 (T inferred as Int)
var hello = "hello", world = "world"
swapTwoValues(&hello, &world)
print(hello, world) // → world hello (T inferred as String)
Because both parameters are typed T, the compiler guarantees
they're the same type — you can't accidentally swap an Int
with a String. Use descriptive names when a placeholder has a
clear meaning (Key, Value, Element);
otherwise stick with T, U, V.
Note: generics are resolved entirely at compile time.
Swift generates specialized, fully type-checked code for each type you
use — there is no boxing or dynamic dispatch involved, unlike the
any existentials covered in
Opaque and Boxed Protocol Types.
Generic types: building a Stack
You can make your own types generic too. A generic type declares its type parameters after the type name, then uses them anywhere a type is expected. The classic example is a stack — a last-in, first-out collection — that should work for any element type.
struct Stack<Element> {
private var items: [Element] = []
var count: Int { items.count }
var isEmpty: Bool { items.isEmpty }
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
items.popLast()
}
func peek() -> Element? {
items.last
}
}
var stack = Stack<String>()
stack.push("uno")
stack.push("dos")
print(stack.count) // → 2
print(stack.pop() ?? "") // → dos
Here Element is the type parameter for the whole struct. When
you write Stack<String>(), every appearance of
Element becomes String. Swift can also infer the
element type from context, so var ints = Stack<Int>()
and assigning known values both work without restating the type.
Extending a generic type
When you extend a generic type, you do not repeat the type
parameter list — the original parameter names (here Element)
are already in scope inside the extension.
extension Stack {
// Element is available without redeclaring it
var top: Element? { peek() }
}
var numbers = Stack<Int>()
numbers.push(10)
numbers.push(20)
print(numbers.top ?? 0) // → 20
You can also add a where clause to an extension so its
members only exist when the element type meets extra requirements — see
contextual where below.
Type constraints
Sometimes a generic function needs to do something with its values — compare them, hash them, add them. A bare type parameter offers no such capabilities. A type constraint requires a type parameter to inherit from a class or conform to a protocol, unlocking the operations that protocol guarantees. Write the constraint after a colon in the parameter list.
// T must be Equatable so we can use ==
func firstIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
for (index, item) in array.enumerated() {
if item == value {
return index
}
}
return nil
}
print(firstIndex(of: 9, in: [3, 9, 12]) ?? -1) // → 1
print(firstIndex(of: "b", in: ["a", "b"]) ?? -1) // → 1
Without the Equatable constraint, item == value
wouldn't compile, because not every type supports ==.
Constraints let the compiler verify your generic code is valid for
every type it could ever be used with.
Associated types
Protocols can't take angle-bracket type parameters; instead they use
associated types — placeholder type names declared with
associatedtype that each conformer fills in. This is the
protocol-side counterpart to a generic parameter. Here's a
Container protocol; Stack conforms to it, and
Swift infers the associated Item from the implementations.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
extension Stack: Container {
// Item is inferred as Element
mutating func append(_ item: Element) { push(item) }
subscript(i: Int) -> Element { items[i] }
}
For this conformance, make items accessible (e.g. drop private) so the subscript can read it.
Constraints and generic where on associated types
An associated type can carry an inline constraint, and you can add a
where clause to relate it to other associated types. This
lets the protocol express precise relationships the compiler can rely on.
protocol SuffixableContainer: Container {
// Suffix is itself a container of the same Item type
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
protocol ComparableContainer: Container where Item: Comparable {
// every conformer's Item is guaranteed Comparable
}
The where Suffix.Item == Item clause guarantees a container's
suffix holds the same kind of element it does — the compiler enforces this
for every conforming type.
Generic where clauses
A generic where clause lets you state requirements that
can't fit in the parameter list — relationships between type
parameters, or constraints on a type parameter's associated types. Write
where just before the opening brace. This function compares
two containers of possibly different types as long as their items match
and are Equatable.
func allItemsMatch<C1: Container, C2: Container>(
_ first: C1, _ second: C2
) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
if first.count != second.count { return false }
for i in 0..<first.count {
if first[i] != second[i] { return false }
}
return true
}
The clause reads: "C1 and C2 are both
Containers, their Item types are the same, and
that shared item type is Equatable." Inside the body the
compiler then lets you use != and index both containers
freely.
Contextual where clauses
You can attach a where clause to an individual method,
property, or subscript inside a generic type's extension, so that member
is available only when the condition holds — without writing a separate
constrained extension. This is a contextual where clause.
extension Container {
// only available when Item is Equatable
func startsWith(_ item: Item) -> Bool where Item: Equatable {
count >= 1 && self[0] == item
}
// only available when Item is an Int-like average can be taken
func average() -> Double where Item == Int {
var sum = 0
for i in 0..<count { sum += self[i] }
return Double(sum) / Double(count)
}
}
Both methods live in one extension, but each carries its own requirement.
The equivalent without contextual clauses would need two separate
extension Container where ... blocks.
Generic subscripts
Subscripts can be generic, and they can carry their own type parameters
and where clauses too. This subscript takes any sequence of
indices and returns the matching items as an array.
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Element == Int {
var result: [Item] = []
for index in indices {
result.append(self[index])
}
return result
}
}
var letters = Stack<String>()
["a", "b", "c", "d"].forEach { letters.append($0) }
print(letters[[0, 2]]) // → ["a", "c"]
Tip: the generic parameter Indices
belongs to the subscript, not the type, so it goes in angle brackets right
after the subscript keyword. The where clause
constrains it to sequences of integers.
Wrapping up
Generics let you write code once and reuse it across every type that meets
your requirements, with full compile-time safety and no runtime overhead.
You've seen the duplication problem they remove; generic functions and
type parameters; building and extending a generic Stack; type
constraints that unlock protocol operations; associated types with their
own constraints and where clauses; generic where
clauses relating multiple types; contextual where clauses on
individual members; and generic subscripts. Together with
protocols, generics are the backbone of the
Swift standard library and of expressive, reusable Swift code.