Usage of protocols as array types and function parameters in Swift
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Protocols are one of Swift's main tools for writing code against behavior instead of concrete types. They become especially useful when you want a function to accept multiple implementations or when you need a collection that can hold different types that still share the same capabilities.
Protocols as function parameters
A protocol describes the operations a value must support. When a function takes a protocol type, the caller can pass any conforming value.
In current Swift, an existential protocol value is written with any. That makes the intent explicit: the function accepts any concrete type that conforms to the protocol.
This works well when the function only needs the protocol's declared interface. The function does not care whether the caller passes a struct, class, or enum.
Protocols as array element types
The same idea applies to collections. An array of any Drawable can hold multiple concrete types as long as they all conform to Drawable.
This gives you a heterogeneous array with a shared interface. That is useful for view models, rendering pipelines, plugin points, and command dispatch systems.
When generics are a better fit
Using any Protocol introduces existential storage and dynamic dispatch. That is often acceptable, but sometimes you want a function to preserve the caller's concrete type. In that case, a generic constraint is better.
This version requires every array element to have the same concrete type, but it avoids existential boxing and allows the compiler to keep more type information.
Protocols with associated types or Self requirements
Not every protocol can be used directly as an array type or parameter type. If a protocol has an associated type or uses Self in a way that ties behavior to the conforming type, the compiler may reject existential usage.
You cannot usually write [any Parser] and then call parse in a useful way, because each parser may produce a different output type. In these cases, you normally use generics, a type-erased wrapper, or a more specific protocol design.
Choosing between arrays of protocols and arrays of concrete types
Use an array of protocol values when you need to mix implementations. Use an array of a concrete type when every element is the same and the extra abstraction adds no value.
For example, a menu system that stores buttons, toggles, and sliders behind one Interactable protocol is a good match for [any Interactable]. A list of users fetched from an API should probably stay [User].
Common Pitfalls
A frequent mistake is omitting any in modern Swift examples. Many code samples written before the newer syntax still compile in some contexts, but current Swift style expects existential protocol values to be explicit.
Another common issue is trying to store protocols with associated types directly in arrays. If the protocol describes values with different output types, existential use becomes limited or impossible.
It is also easy to overuse protocols when a generic function or a concrete model would be simpler. If there is only one implementation today and no real abstraction boundary, protocol-based design may add indirection without a clear benefit.
Summary
- Protocols let functions accept behavior-based inputs rather than concrete types.
- Use
any ProtocolNamefor existential parameters and heterogeneous arrays in modern Swift. - Arrays like
[any Drawable]are useful when different types share the same interface. - Prefer generics when all items have the same concrete type and you want stronger compile-time guarantees.
- Protocols with associated types often require generics or type erasure instead of direct existential storage.

