Generic (generic programming) — is a Swift mechanism that allows writing types and functions with a parameterized type
Key Takeaways
Generic (generic type) — is a syntactic construct in Swift that allows writing flexible, reusable code with type parameterization. Instead of a concrete type, a placeholder is written in angle brackets — usually
Without generics, you would have to write separate functions for each type: swapInts, swapStrings, swapDates. A generic swap function replaces all three variants with a single declaration. This reduces duplication, improves readability, and lowers the chance of errors when copying code.
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)
The Swift compiler generates specialized code for each concrete use of a generic. This means the generic function swapValues
Generic functions are functions that accept one or more type parameters. Type parameters are specified after the function name in angle brackets:
A function can have multiple generic parameters. For example, the findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
The constraints K: Hashable and where V: Equatable ensure that findKey can only be called with a dictionary whose keys are hashable and values are equatable. Such constraints are not bureaucracy but a necessity: without Hashable you cannot index a key lookup, without Equatable you cannot compare value with dictValue.
where — is a Swift keyword that imposes additional constraints on generic parameters. Unlike simply specifying a protocol in angle brackets (
The where clause is written after the function or type signature before the opening brace. In where you can specify that T: Comparable & Hashable (conforming to two protocols simultaneously), or that T.U == Int (concretizing an associated type).
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
The extension with where adds methods only for those types that satisfy the condition. Container
Associated Types — are a way to make a protocol generic without specifying a concrete type at the declaration stage. Instead of
Using where you can establish relationships between associated types of different protocols. For example, you can require that the Item of one protocol matches the Iterator.Element of another. This ensures type compatibility at the compiler level.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack concretizes Element as Int using typealias. The compiler checks that all protocol requirements are fulfilled with this type. Without associated types, you would have to write StackProtocol
Generic extensions allow adding methods to a generic type with additional constraints. This is the “conditional conformance” pattern, where a type gains functionality only under certain conditions. Subscripts can also be generic.
A subscript can be generic: it accepts generic parameters in angle brackets. This is useful for safe access to collections with arbitrary indices, where the index can be of different types.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
This construction provides powerful composition: the sum method appears only on numeric arrays, and the subscript with arbitrary indices works with any sequence of integer indices. The compiler manages the visibility of these members based on where conditions.
Mistakes when using generics are often related to incorrect constraints or an attempt to use generics where a concrete type is needed. Let’s look at three common scenarios that developers encounter.
Adding generic parameters where a concrete type is sufficient is an anti-pattern. If a function always works with String, there is no need to make it
Calling a method on a generic parameter without specifying that the method exists is an error.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
The error is clear: generic
Frequently Asked Questions
Generic is a way to write code that works with any type, without duplicating the same logic for Int, String, and other types. Instead of a concrete type, a placeholder
Generic preserves information about the concrete type at compile time, allowing the compiler to check types and generate optimized code. Any is type erasure: any value can be passed, but the type is lost, and casting (as?) is required.
Use the syntax T: SomeProtocol in angle brackets or a separate where T: SomeProtocol clause. Constraints ensure that the generic parameter supports specific methods and properties.
Associated Type (associatedtype) is a placeholder for a type inside a protocol. The protocol does not specify a concrete type but declares an associatedtype, which is concretized in the implementation: Array has Element, Dictionary has Key and Value.
Swift compiles generic code into specialized versions for each concrete type. In practice, a generic function for Int is no slower than a hand-written function for Int. There is no abstraction overhead.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also