What is protocol-oriented programming in Swift, and why does Apple push it over classical inheritance?
Protocols plus extensions give Swift shared behavior without inheritance's fragile base classes, single-axis reuse, and forced reference semantics — at the cost of a few sharp static-dispatch edges.
LEARN BY DOINGPut your intuition to work3 steps
What is protocol-oriented programming in Swift, and why does Apple push it over classical inheritance?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.Apple put a name on this at WWDC 2015 — "Protocol-Oriented Programming in Swift" — and it wasn't a minor style preference. It reframed what a Swift codebase's backbone should be: not a tree of base classes you subclass for shared behavior, but a set of protocols you conform to, with shared behavior supplied by protocol extensions instead of inheritance. A senior candidate should be able to explain the actual problems this solves, not just recite "prefer composition over inheritance."
#What classical inheritance costs you
A class hierarchy gives you exactly one axis of reuse — you can only inherit from one superclass — and every subclass silently depends on the superclass's implementation details, not just its interface. Change a base class method and every subclass down the tree can break in ways the compiler won't catch; that's the fragile base class problem. Classes are also reference types by default, so an inheritance-first design tends to drag reference semantics and shared mutable state into places that would be perfectly happy as simple, cheap, thread-safe value types — a Point, a Money, a UserPreferences struct has no business being a class just because you wanted it to share code with a sibling type.
#What protocols plus extensions give you instead
A protocol declares a contract; a protocol extension can supply a default implementation of that contract, which every conforming type gets for free unless it chooses to override it. Because conformance isn't inheritance, both classes and value types — structs and enums — can adopt the same protocol and share the same default behavior, without forcing anything into a reference type:
protocol Greetable {var name: String { get }func greet() -> String}extension Greetable {// Default implementation — every conformer gets this for free.func greet() -> String {"Hello, \(name)!"}}struct Visitor: Greetable {let name: String}enum SystemUser: Greetable {case admin(name: String)case guestvar name: String {switch self {case .admin(let name): return namecase .guest: return "Guest"}}}let attendees: [Greetable] = [Visitor(name: "Ada"), SystemUser.admin(name: "Grace"), SystemUser.guest]attendees.forEach { print($0.greet()) }
Notice a struct and an enum share one behavior with zero inheritance involved. Protocols also compose, which single inheritance cannot: a function can require several unrelated capabilities at once without forcing them into one hierarchy —
protocol UniquelyIdentified {var id: String { get }}protocol Auditable {var lastModified: Date { get }}func logChange(_ item: some UniquelyIdentified & Auditable) {print("\(item.id) changed at \(item.lastModified)")}
Any type that happens to satisfy both protocols works here, regardless of where it sits in whatever class hierarchy it may or may not have — including types you don't own, via retroactive conformance in an extension.
#Associated types, some, and any
Protocols that need to talk about a placeholder type use associatedtype, which turns the protocol into a generic constraint rather than a plain interface:
protocol Container {associatedtype Itemvar items: [Item] { get }mutating func add(_ item: Item)}struct Stack<Element>: Container {private(set) var items: [Element] = []mutating func add(_ item: Element) { items.append(item) }}// `some Container` — an opaque return type. The compiler knows the concrete// type at compile time, so there's no indirection or dynamic dispatch cost.func makeStack() -> some Container {Stack<Int>()}// `any Container` — a boxed existential. Needed when the concrete type can// vary at runtime, at the cost of an extra layer of indirection.let boxed: any Container = Stack<String>()
A protocol with an associatedtype can't be used as a plain existential (any Container was actually illegal before Swift 5.7's improvements to existentials for such protocols in many cases) the way a simple protocol can, precisely because "a Container of something" isn't a concrete enough type to box safely without more machinery.
#Why Apple actually pushes this
Value semantics eliminate a whole category of shared-mutable-state bugs, because copies are independent by default. Protocol conformance can be bolted onto existing types — including ones you don't own — without touching their class hierarchy or reaching for a subclass you can't create. Mocking for tests becomes "write a small type that conforms to the protocol" instead of "subclass this concrete class and override enough of it to intercept behavior." And SwiftUI itself is the biggest advertisement for the paradigm: View, Shape, and ViewModifier are all protocols with associated types, not base classes, and every SwiftUI view you've ever written is a value type conforming to a protocol, never a subclass.
🧠 Protocol-Oriented Programming
0/2 · 0/2 answered1. A method is added to a protocol only via a protocol EXTENSION (not listed in the protocol's requirements), and a conforming struct defines its own method with the same name and signature. When you call that method on a variable typed as the protocol, which implementation runs?
2. What is the main practical difference between `some Container` and `any Container` for a protocol with an associated type?