Type erasure concept in Swift - Time & Space Complexity
When using type erasure in Swift, it's important to understand how the code runs as input grows.
We want to see how the number of operations changes when we use type erasure wrappers.
Analyze the time complexity of the following code snippet.
protocol Shape {
func area() -> Double
}
struct AnyShape: Shape {
private let _area: () -> Double
init(_ shape: S) {
_area = shape.area
}
func area() -> Double {
return _area()
}
}
This code wraps any Shape type inside AnyShape to hide its concrete type using type erasure.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Calling the stored closure _area() inside AnyShape.
- How many times: Once per call to area(), no loops or recursion here.
Each call to area() runs one stored closure call, regardless of input size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 calls to _area() |
| 100 | 100 calls to _area() |
| 1000 | 1000 calls to _area() |
Pattern observation: The number of operations grows linearly with the number of calls.
Time Complexity: O(n)
This means the time grows directly in proportion to how many times you call the area() method.
[X] Wrong: "Type erasure adds hidden loops or recursion that slow down the code a lot."
[OK] Correct: Type erasure here just stores a closure and calls it once per use, so it does not add extra loops or repeated work.
Understanding how type erasure affects performance helps you explain your design choices clearly and confidently in real projects or interviews.
"What if the stored closure inside AnyShape captured a collection and iterated over it? How would the time complexity change?"
Practice
type erasure in Swift?Solution
Step 1: Understand type erasure concept
Type erasure is used to hide the concrete underlying type and expose a common interface.Step 2: Identify the main purpose
This allows different types to be used uniformly without exposing their specific details.Final Answer:
To hide specific types behind a common interface -> Option DQuick Check:
Type erasure = hiding types behind interface [OK]
- Confusing type erasure with type conversion
- Thinking it improves performance directly
- Assuming it creates new types
MyProtocol?Solution
Step 1: Recall type erasure syntax
Type erasure uses a struct that conforms to the protocol and stores an instance ofany Protocolto hide the concrete type.Step 2: Identify correct syntax
struct AnyMyProtocol: MyProtocol { private let _base: any MyProtocol } usesany MyProtocolwhich is the correct way to store an existential type in Swift 5.7+.Final Answer:
struct AnyMyProtocol: MyProtocol { private let _base: any MyProtocol } -> Option AQuick Check:
Useany Protocolfor type erasure storage [OK]
- Using generic parameter instead of existential
- Using 'some Protocol' which is opaque, not erased
- Trying to store protocol directly without 'any'
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
var radius: Double
func area() -> Double { return .pi * radius * radius }
}
struct Square: Shape {
var side: Double
func area() -> Double { return side * side }
}
struct AnyShape: Shape {
private let _area: () -> Double
init(_ shape: S) {
_area = shape.area
}
func area() -> Double { _area() }
}
let shapes: [AnyShape] = [AnyShape(Circle(radius: 2)), AnyShape(Square(side: 4))]
let totalArea = shapes.reduce(0) { $0 + $1.area() }
print(totalArea)
What is the output?Solution
Step 1: Calculate area of Circle and Square
Circle area = π * 2 * 2 = 12.566370614359172, Square area = 4 * 4 = 16Step 2: Sum areas in array
Total area = 12.566370614359172 + 16 = 28.566370614359172Step 3: Check code output
Code prints totalArea, which is 28.566370614359172Final Answer:
28.566370614359172 -> Option AQuick Check:
Sum of areas = 28.566370614359172 [OK]
- Calculating area incorrectly
- Confusing type erasure with type casting
- Expecting compile error due to protocol
protocol Printer {
associatedtype Item
func printItem(_ item: Item)
}
struct AnyPrinter: Printer {
private let _print: (Any) -> Void
init(_ printer: P) {
_print = printer.printItem
}
func printItem(_ item: Any) {
_print(item)
}
}
What is the main cause of the error?Solution
Step 1: Analyze closure assignment
The closure _print expects (Any) -> Void but printer.printItem expects (P.Item) -> Void, which is a mismatch.Step 2: Understand type erasure limitation
Associated types prevent direct assignment because the types differ; you cannot assign a generic method to a closure with Any parameter.Final Answer:
Cannot assign generic method to closure with Any parameter -> Option BQuick Check:
Generic method ≠ closure with Any parameter [OK]
- Thinking protocol conformance is missing
- Confusing associatedtype usage
- Assuming return type mismatch
DataSource with an associated type Item and a method func fetch() -> [Item]. Which approach correctly applies type erasure to allow storing different DataSource implementations in a single array?Solution
Step 1: Understand associated type challenge
Protocols with associated types cannot be used directly in collections because the type varies.Step 2: Apply type erasure pattern
Create a struct with a private closure returning [Item], and initialize it generically to wrap any DataSource, hiding the concrete type.Step 3: Evaluate options
Create a structAnyDataSourcewith a private closure_fetch: () -> [Item]and use type erasure with a generic initializer correctly uses a generic initializer and closure with [Item], preserving type safety and enabling storage in arrays.Final Answer:
Create a struct AnyDataSource with a private closure _fetch: () -> [Item] and use type erasure with a generic initializer -> Option CQuick Check:
Type erasure with generic init and closure = Create a structAnyDataSourcewith a private closure_fetch: () -> [Item]and use type erasure with a generic initializer [OK]
- Casting to [Any] loses type safety
- Using generic struct prevents uniform storage
- Trying to remove associated type via inheritance
Ready to prove it?
Pass the test and this pattern moves unknown
