Protocol-Oriented Programming
Reviewed & published by Brayan K
POP is Swift's signature design philosophy: build behavior from protocols and protocol extensions rather than deep class hierarchies. You'll see how default implementations, composition, and value types combine into flexible, reusable code.
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
- • Why POP is preferred over deep class inheritance
- • Share behavior with protocol extensions (default methods)
- • Let structs and enums gain shared behavior
- • Favor composition over inheritance with small protocols
- • Override a default when a type needs something different
- • Specialize defaults with constrained extensions
1️⃣ Behavior in Protocol Extensions
The heart of POP: put shared behavior in a protocol extension. Every conformer gets it for free — and crucially, that includes value types like structs and enums, which can't share a superclass.
// In POP, behavior lives in PROTOCOLS + their EXTENSIONS, not a base class.
protocol Vehicle {
var name: String { get }
var topSpeed: Int { get }
}
// A protocol extension gives every conformer shared behavior for FREE.
extension Vehicle {
func describe() -> String {
return "\(name) can do \(topSpeed) mph"
}
}
// Both a struct and an enum can conform — no shared superclass needed.
struct Car: Vehicle { let name = "Car"; let topSpeed = 120 }
struct Bike: Vehicle { let name = "Bike"; let topSpeed = 20 }
print(Car().describe()) // Car can do 120 mph
print(Bike().describe()) // Bike can do 20 mph2️⃣ Composition Over Inheritance
Instead of one tall base class, assemble capabilities from small protocols. A type can conform to many at once — something single class inheritance simply can't express.
// Composition over inheritance: build capability from SMALL protocols.
protocol Named { var name: String { get } }
protocol Flyable { func fly() -> String }
protocol Swimmable { func swim() -> String }
extension Flyable { func fly() -> String { "Flying!" } }
extension Swimmable { func swim() -> String { "Swimming!" } }
// A Duck mixes in several protocols — something single class inheritance
// cannot express.
struct Duck: Named, Flyable, Swimmable {
let name: String
}
let d = Duck(name: "Donald")
print("\(d.name): \(d.fly()) \(d.swim())") // Donald: Flying! Swimming!3️⃣ Overriding a Default
Defaults are a starting point, not a cage. A conformer that needs different behavior simply provides its own implementation, which takes precedence.
// Conformers can OVERRIDE a default when they need different behavior.
protocol Greeter {
var name: String { get }
}
extension Greeter {
func greet() -> String { "Hi, I'm \(name)" }
}
struct Friendly: Greeter { let name: String } // uses the default
struct Formal: Greeter {
let name: String
func greet() -> String { "Good day. I am \(name)." } // overrides
}
print(Friendly(name: "Sam").greet()) // Hi, I'm Sam
print(Formal(name: "Dr. Lee").greet()) // Good day. I am Dr. Lee.Your turn. Fill in the blanks to add a default method and conform to the protocol.
// 🎯 YOUR TURN — fill in the blanks, then run it in SwiftFiddle.
protocol Identifiable {
var id: Int { get }
}
// 1) Add a DEFAULT method in a protocol extension. Replace ___ with the keyword.
___ Identifiable { // 👉 replace ___ with 'extension'
func tag() -> String { return "ID-\(id)" }
}
// 2) Make Product conform. Replace ___ with the protocol name.
struct Product: ___ { // 👉 replace ___ with 'Identifiable'
let id: Int
}
print(Product(id: 7).tag()) // ID-7Pro Tips
- 💡 Start with small protocols. Compose them rather than building one giant class.
- 💡 Put shared logic in extensions. Conformers stay lean and focused.
- 💡 Reach for value types first. Structs + protocols cover most needs.
- 💡 Use classes deliberately when you need identity or reference semantics.
Common Errors (and the fix)
- Putting method bodies in the protocol itself — defaults belong in a protocol extension, not the protocol declaration.
- Reaching for a base class to share code — a protocol extension shares it with structs and enums too.
- One huge protocol — split it into small focused protocols that compose.
- "Type does not conform to protocol" — the conformer is still missing a non-default requirement; implement it.
📋 Quick Reference
| Idea | Code | Note |
|---|---|---|
| Default method | extension P { func f() {…} } | shared behaviour |
| Compose | struct S: A, B, C | mix protocols |
| Override | func f() { … } | in the conformer |
| Value type | struct/enum: P | no superclass needed |
| Constrained | extension P where … | specialized default |
Mini-Challenge: Composable Behaviors
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: composable behaviors
// 1. Define a protocol Drawable with a method draw() -> String.
// 2. In a protocol extension, give draw() a default returning "Drawing...".
// 3. Define a protocol Resizable with a method resize() -> String, and a
// default in an extension returning "Resizing...".
// 4. Make a struct Shape conform to BOTH protocols (no overrides).
// 5. Print shape.draw() and shape.resize().
//
// ✅ Expected output:
// Drawing...
// Resizing...
// your code here🎉 Lesson Complete!
- ✅ POP builds behavior from protocols and protocol extensions
- ✅ Default implementations live in protocol extensions
- ✅ Structs and enums gain shared behavior, not just classes
- ✅ Compose small protocols instead of deep inheritance
- ✅ Conformers can override any default they need to change
Practice quiz
What is the core idea of Protocol-Oriented Programming (POP)?
- Use only classes
- Avoid all functions
- Build behavior from protocols and protocol extensions rather than class inheritance
- Use global variables
Answer: Build behavior from protocols and protocol extensions rather than class inheritance. POP favors composing behavior from protocols and their extensions over deep class inheritance hierarchies.
Where do default implementations for protocol methods live?
- In a protocol extension
- In a base class
- In the protocol body
- In a global function
Answer: In a protocol extension. Protocol extensions provide default implementations that all conformers inherit for free.
A key advantage of POP over class inheritance is that...
- It only works with classes
- It forbids protocols
- It removes type safety
- Structs and enums can also gain shared behavior
Answer: Structs and enums can also gain shared behavior. Because structs and enums (value types) can conform to protocols, they too get shared behavior.
Which Swift principle does POP emphasize?
- Inheritance over composition
- Composition over inheritance
- Mutation over immutability
- Globals over locals
Answer: Composition over inheritance. POP favors composing small protocols together rather than inheriting from a big base class.
How does a type get multiple sets of behavior in POP?
- By conforming to multiple protocols
- By subclassing many classes
- By copying code
- It can't
Answer: By conforming to multiple protocols. A type can conform to many protocols, each contributing behavior — Swift has no multiple class inheritance.
What does a protocol with a default method let a conformer do?
- Nothing
- Only override it
- Use the default OR override it with its own version
- Only use the default
Answer: Use the default OR override it with its own version. A conformer inherits the default and may override it by providing its own implementation.
Why is POP often called 'Swift's signature paradigm'?
- Because classes are banned
- Because the standard library is built heavily on protocols and extensions
- Because it's slower
- Because it avoids generics
Answer: Because the standard library is built heavily on protocols and extensions. Apple promoted POP at WWDC 2015; the standard library relies on protocols like Collection and Equatable.
In POP, small focused protocols are preferred because they...
- Run faster
- Use less memory
- Avoid generics
- Compose flexibly and keep responsibilities clear
Answer: Compose flexibly and keep responsibilities clear. Small, single-purpose protocols compose well and keep each responsibility isolated.
Can a protocol extension method be specialized for only some conformers?
- No
- Yes — using a constrained extension with a where clause
- Only for classes
- Only with inheritance
Answer: Yes — using a constrained extension with a where clause. A constrained protocol extension (extension P where ...) adds behavior only when the condition holds.
What problem with deep class inheritance does POP help avoid?
- Faster code
- Too few features
- Rigid, fragile hierarchies and the 'diamond problem'
- Lack of properties
Answer: Rigid, fragile hierarchies and the 'diamond problem'. POP avoids brittle, deep inheritance trees and the ambiguity of multiple inheritance.
Continue this course
- Previous: Generics
- Next: Error Handling (do/try/catch) — Throw and catch errors with the Error protocol, try?, and try!
Frequently asked questions
What is Protocol-Oriented Programming?
Protocol-Oriented Programming (POP) is a design approach Apple introduced at WWDC 2015 that builds behavior from protocols and protocol extensions instead of deep class inheritance. You define capabilities as small protocols, give them default implementations in extensions, and compose them onto your types — including value types like structs and enums.
How is POP different from object-oriented programming?
Classic OOP shares behavior by inheriting from a base class, which forms rigid single-inheritance hierarchies and only works with classes. POP shares behavior through protocol extensions, so structs and enums benefit too, and a type can mix in many protocols. POP favors composition over inheritance.
Where do default method implementations come from in POP?
From protocol extensions. When you write extension SomeProtocol { func doThing() { ... } }, every type that conforms to SomeProtocol automatically gets doThing() without writing it. A conformer can still provide its own version to override the default when it needs different behavior.
Does POP mean I should never use classes?
No. POP is about preferring protocols and composition where they fit, not banning classes. Classes are still the right tool when you need reference semantics, identity, deinitializers, or interoperability with Objective-C. Many real apps blend value types, classes, and protocols.
What does 'composition over inheritance' mean here?
Instead of building one tall base class that subclasses inherit from, you assemble a type's capabilities from several small, focused protocols. A type can conform to Named, Flyable, and Swimmable at once, gaining all three behaviors. This is more flexible than single class inheritance and avoids fragile hierarchies.