LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1117 min read

Protocols & Extensions

Learn to define and conform to protocols, use protocols as types, extend existing types, and write protocol extensions with default implementations.

Introduction

A protocol defines a blueprint of properties, methods, or other requirements that a type can conform to, without dictating how that type must be implemented. Combined with extensions — which let you add functionality to any existing type, even ones you don't own — protocols are the foundation of what Apple calls "protocol-oriented programming," a style the Swift standard library itself is built around.

This lesson covers defining and conforming to protocols, using protocols as types, adding functionality with extensions, and protocol extensions that supply default implementations.

Defining Protocols

A protocol lists requirements — properties and methods a conforming type must implement — but contains no implementation of its own.

protocol Vehicle {
var wheels: Int { get }
func describe() -> String
}

Conforming to Protocols

Any struct, class, or enum can conform to a protocol by implementing every requirement it lists. Multiple unrelated types can conform to the same protocol, each with its own implementation.

struct Car: Vehicle {
var wheels: Int = 4
func describe() -> String {
"A car with \(wheels) wheels"
}
}
struct Bicycle: Vehicle {
var wheels: Int = 2
func describe() -> String {
"A bicycle with \(wheels) wheels"
}
}

Protocols as Types

A protocol can be used as a type itself, letting you write code that works with any conforming type without caring what the concrete type actually is.

let vehicles: [Vehicle] = [Car(), Bicycle()]
for v in vehicles {
print(v.describe())
}
Output

Click Run to see what this code prints.

Extensions Basics

An `extension` adds new functionality — computed properties, methods, initializers, and protocol conformance — to an existing type, without modifying its original source code.

extension Int {
var isEven: Bool {
self % 2 == 0
}
func squared() -> Int {
self * self
}
}
print(4.isEven)
print(5.squared())
Output

Click Run to see what this code prints.

Extending Existing Types

The example above extends `Int`, a type built into the Swift standard library that you don't own. This is one of the most useful features of extensions: you can add convenience functionality to types from the standard library, third-party frameworks, or Apple's own frameworks, without subclassing or wrapping them.

Protocol Extensions

You can also extend a protocol itself to provide a default implementation for one of its requirements (or add entirely new methods). Every conforming type gets the default implementation automatically, unless it provides its own.

protocol Greetable {
var name: String { get }
}
extension Greetable {
func greet() -> String {
"Hello, \(name)!"
}
}
struct User: Greetable {
var name: String
}
print(User(name: "Zara").greet())
Output

Click Run to see what this code prints.

Protocol-Oriented Programming

Apple introduced the term "protocol-oriented programming" at WWDC 2015, describing a style where shared behavior is composed through small, focused protocols and protocol extensions rather than deep class inheritance hierarchies. The Swift standard library itself follows this pattern extensively — types like `Equatable`, `Comparable`, `Hashable`, and `Collection` are all protocols, not base classes, which is why both structs and classes can conform to the exact same shared behaviors.

Common Mistakes

Avoid These Mistakes
  • Forgetting the `mutating` keyword on a protocol method requirement that a conforming struct needs to modify itself in.
  • Writing one large, do-everything protocol instead of several small, focused ones combined where needed with `&` (protocol composition).
  • Assuming a protocol extension's default implementation always overrides a conforming type's own version when called on a value typed as the concrete type — dispatch behavior here has real, sometimes surprising, nuances.
  • Reaching for class inheritance out of habit when a protocol plus a protocol extension would let unrelated types share the same behavior more flexibly.

Best Practices

  • Keep protocols small and focused on one capability; compose multiple protocols together with `&` when a type needs several.
  • Use protocol extensions to share default behavior across conforming types instead of introducing a shared base class.
  • Reach for protocols before class inheritance when modeling a capability that unrelated types (structs, classes, and enums) should all be able to share.
  • Extend existing types (including standard library types) for small, genuinely reusable convenience additions — not to work around a design problem elsewhere.

Frequently Asked Questions

All three — structs, classes, and enums — can conform to protocols. This is a big part of why protocol-oriented programming works across Swift's entire type system, not just class hierarchies.

Combining multiple protocol requirements into one type constraint using `&`, for example `func process(_ item: Codable & Equatable)`, meaning the argument must conform to both protocols.

Protocols let value types (structs, enums) and reference types (classes) share the exact same behavior contracts, whereas inheritance only works for classes — protocols are simply more broadly applicable.

Key Takeaways

  • A protocol defines requirements — properties and methods — without providing implementation.
  • Structs, classes, and enums can all conform to protocols, and a protocol can be used as a type.
  • Extensions add new functionality to any existing type, including ones from the standard library.
  • Protocol extensions supply default implementations that conforming types inherit automatically.
  • Protocol-oriented programming favors composing small protocols over deep class inheritance chains.

Summary

You now understand how protocols and extensions let Swift share behavior flexibly across unrelated types. In the final lesson, you'll pull everything together with Swift best practices, error handling, memory management, and where to go next.

Next Lesson →

Best Practices & Next Steps