Generics
Reviewed & published by Brayan K
Generics let you write code once that works with any type — without giving up type safety. You'll write generic functions and types with <T>, add type constraints, and refine them with where clauses.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Write generic functions with a type placeholder <T>
- • Build generic types like Stack<Element>
- • Add type constraints such as <T: Comparable>
- • Refine generics with where clauses
- • See why generics beat Any for type safety
- • Recognize associatedtype in protocols
1️⃣ Generic Functions
Put a placeholder in angle brackets after the function name — <T> — and use T wherever a concrete type would go. The same function then works with arrays of any element type.
// A GENERIC function works with any type. T is a placeholder the caller fills.
func firstAndLast<T>(_ items: [T]) -> (T, T)? {
guard let first = items.first, let last = items.last else { return nil }
return (first, last)
}
// Works with Ints:
if let pair = firstAndLast([10, 20, 30]) {
print(pair) // (10, 30)
}
// ...and Strings, with the SAME function:
if let pair = firstAndLast(["a", "b", "c"]) {
print(pair) // ("a", "c")
}A classic generic is a swap. One function handles every type instead of one per type.
// Without generics you'd write one swap per type. With <T>, one covers all.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 1
var y = 2
swapValues(&x, &y)
print("x=\(x) y=\(y)") // x=2 y=1
var s1 = "hello"
var s2 = "world"
swapValues(&s1, &s2)
print("\(s1) \(s2)") // world hello2️⃣ Type Constraints
Sometimes you need the placeholder to support an operation. A constraint like <T: Comparable> guarantees T conforms to a protocol, unlocking its operators and methods.
// A type CONSTRAINT requires the placeholder to conform to a protocol.
// Here T must be Comparable so we can use the < operator.
func largest<T: Comparable>(_ items: [T]) -> T? {
guard var best = items.first else { return nil }
for item in items where item > best {
best = item
}
return best
}
print(largest([3, 9, 2, 7]) ?? 0) // 9
print(largest(["pear", "fig", "kiwi"]) ?? "") // pear3️⃣ Generic Types
Types can be generic too. A Stack<Element> stores any kind of value, and you choose the element type when you create one.
// A GENERIC TYPE — a Stack that holds any element type.
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { return items.popLast() }
var count: Int { items.count }
}
var ints = Stack<Int>()
ints.push(1)
ints.push(2)
print(ints.pop() ?? -1) // 2
print(ints.count) // 1
var words = Stack<String>()
words.push("hi")
print(words.pop() ?? "") // hiYour turn. Fill in the blanks to make a function generic and to add a constraint.
// 🎯 YOUR TURN — fill in the blanks, then run it in SwiftFiddle.
// 1) Make this function generic. Replace ___ with the placeholder <T>.
func middle___(_ items: [T]) -> T? { // 👉 replace ___ with <T>
guard !items.isEmpty else { return nil }
return items[items.count / 2]
}
print(middle([1, 2, 3]) ?? 0) // 2
// 2) Constrain T to Equatable so we can use ==. Replace ___ accordingly.
func allSame<T: ___>(_ items: [T]) -> Bool { // 👉 replace ___ with Equatable
guard let first = items.first else { return true }
return items.allSatisfy { $0 == first }
}
print(allSame([4, 4, 4])) // truePro Tips
- 💡 Name placeholders meaningfully. Element, Key, Value beat a bare T in types.
- 💡 Constrain only as much as needed. Add : Comparable when you actually compare.
- 💡 Prefer generics over Any to keep compile-time safety.
- 💡 Use where for constraints on associated/element types.
Common Errors (and the fix)
- "Binary operator '>' cannot be applied to two 'T' operands" — add a constraint: <T: Comparable>.
- "Cannot convert value of type 'Int' to expected argument type 'String'" — using the same T forces both arguments to match.
- "Reference to generic type 'Stack' requires arguments" — specify the element type, e.g. Stack<Int>() (or let it be inferred).
- Using Any then needing a cast — switch to a generic to keep the real type.
📋 Quick Reference
| Task | Code | Note |
|---|---|---|
| Generic func | func f<T>(_ x: T) | placeholder T |
| Generic type | struct Box<T> { … } | any element |
| Constraint | <T: Comparable> | must conform |
| where clause | where Element == Int | extra rule |
| Use it | Stack<String>() | fill the type |
Mini-Challenge: Generic Pair
No blanks this time — just a brief and an outline. Build it, run it, and check your output against the example in the comments.
// 🎯 MINI-CHALLENGE: a generic Pair
// 1. Make a generic struct Pair<A, B> with two stored properties 'first: A'
// and 'second: B'.
// 2. Add a method swapped() -> Pair<B, A> that returns a new Pair with the
// values flipped.
// 3. Create Pair(first: 1, second: "one"), call swapped(), and print both
// values of the result.
//
// ✅ Expected output:
// one 1
// your code here🎉 Lesson Complete!
- ✅ Generics write code once that works with any type
- ✅ <T> is a type placeholder filled at the call site
- ✅ Types can be generic too: Stack<Element>
- ✅ Constraints like <T: Comparable> unlock protocol operations
- ✅ where clauses add finer requirements
Practice quiz
What problem do generics solve?
- Slow runtime
- Memory leaks
- Writing flexible, reusable code that works with any type
- Optional unwrapping
Answer: Writing flexible, reusable code that works with any type. Generics let you write code once that works with many types, avoiding duplication.
What does <T> mean in a generic function?
- A type placeholder filled in when the function is called
- A fixed type named T
- A tuple
- A constant
Answer: A type placeholder filled in when the function is called. T is a type parameter — a placeholder the caller's type fills in.
Is Array a generic type?
- No
- Only Int arrays are
- Only in extensions
- Yes — Array<Element> is generic over its element type
Answer: Yes — Array<Element> is generic over its element type. Array<Element> and Dictionary<Key, Value> are generic standard-library types.
What is a type constraint?
- A way to forbid generics
- A requirement that a type parameter conform to a protocol or class
- A runtime check
- An optional
Answer: A requirement that a type parameter conform to a protocol or class. A constraint like <T: Equatable> requires the type parameter to conform to a protocol.
How do you require T to be comparable with < ?
- <T: Comparable>
- <T>
- <T == Comparable>
- <T?>
Answer: <T: Comparable>. <T: Comparable> constrains T to types that conform to Comparable.
What does a 'where' clause do?
- Declares a variable
- Loops over a collection
- Adds extra requirements on type parameters
- Imports a module
Answer: Adds extra requirements on type parameters. A where clause adds additional constraints, e.g. where T.Element == Int.
Can structs and classes be generic?
- No, only functions
- Yes — you can write generic types like struct Stack<Element>
- Only classes
- Only enums
Answer: Yes — you can write generic types like struct Stack<Element>. Types can be generic too: struct Stack<Element> works with any element type.
Why prefer a generic function over one using Any?
- It's shorter
- Any is faster
- There's no difference
- Generics keep full type information and type safety
Answer: Generics keep full type information and type safety. Generics preserve the concrete type at compile time, so you keep type safety and avoid casts.
In func swapValues<T>(_ a: inout T, _ b: inout T), why must both be T?
- For speed
- So the two values are guaranteed to be the same type
- To allow nil
- It's optional
Answer: So the two values are guaranteed to be the same type. Using the same placeholder T for both ensures the arguments share one type.
What is associatedtype used for?
- Generic functions
- Type casting
- A placeholder type inside a protocol
- Error handling
Answer: A placeholder type inside a protocol. associatedtype declares a placeholder type that a conforming type specifies — generics for protocols.
Continue this course
- Previous: Extensions
- Next: Protocol-Oriented Programming — Swift's signature paradigm — default behavior via protocol extensions
Frequently asked questions
What are generics in Swift and why use them?
Generics let you write flexible, reusable functions and types that work with any type while keeping full type safety. Instead of duplicating a function for Int, String, and Double, you write it once with a type placeholder like <T>. Array and Dictionary in the standard library are themselves generic.
What does the <T> syntax mean?
T is a type parameter — a placeholder for a real type that gets filled in when you use the function or type. You can name it anything (T, Element, Value), but single uppercase letters or descriptive names are conventional. The compiler substitutes the actual type at each call site.
What is a type constraint?
A type constraint restricts a generic placeholder to types that meet a requirement, written like <T: Comparable> or <T: Equatable>. This lets you use the operations that protocol guarantees — for example, you need T: Comparable to use the < operator inside a generic function.
When do I need a where clause?
A where clause adds extra constraints that can't be expressed in the angle brackets alone, often about associated types. For example, extension Array where Element == Int { } adds behavior only to integer arrays, and func f<S: Sequence>(_ s: S) where S.Element: Equatable { } constrains the sequence's element type.
What is the difference between generics and using Any?
Any erases the type — you lose compile-time type information and must cast to do anything useful, which can fail at runtime. Generics preserve the exact type, so the compiler checks your code and there are no casts. Prefer generics for reuse with safety; reach for Any only when you genuinely need to mix unrelated types.