Swift Protocol Inheritance Explained with Syntax and Examples
Swift protocol inheritance lets one protocol build on top of one or more existing protocols. This is useful when you want to define shared requirements once, then create more specific protocols that add new rules. In this article, you will learn what protocol inheritance is, how the syntax works, when to use it, and how it differs from related ideas such as protocol composition.
Quick answer: In Swift, a protocol can inherit from one or more other protocols by listing them after a colon. The new protocol includes all inherited requirements plus any new ones you add, which helps you model layered behavior in a clean and reusable way.
Difficulty: Beginner
Helpful to know first: You will understand this better if you know basic Swift syntax, how functions and properties are declared, and what a simple protocol does.
1. What Is Protocol Inheritance?
Protocol inheritance means creating a protocol that includes the requirements of another protocol. The child protocol does not replace the original one. Instead, it adds to it.
For example, if one protocol says a type must have a name property, another protocol can inherit that requirement and add a new rule such as a start() method.
protocol Car: Vehicle {}
- protocol
- declaration keyword
- Car
- new protocol
- Vehicle
- parent protocol
- {}
- protocol body
A child protocol includes the requirements of its parent protocol.
- A protocol can inherit from one protocol or several protocols.
- The child protocol gains all inherited property, method, and initializer requirements.
- Any type conforming to the child protocol must satisfy every inherited requirement too.
- This helps you build small reusable protocols, then combine them into more specific ones.
- Protocol inheritance is about protocol definitions, not object-oriented class inheritance.
A common point of confusion is the difference between protocol inheritance and class inheritance. A class inherits implementation from a superclass, but a protocol only defines requirements. Protocol inheritance combines requirements, not stored behavior.
2. Why Protocol Inheritance Matters
Protocol inheritance matters because real programs often have related capabilities that should be modeled in layers instead of repeated everywhere.
Imagine an app that works with people, employees, managers, and contractors. Many of these types may share a name or identifier requirement, but only some of them need payroll or reporting responsibilities. Instead of rewriting those shared requirements in every protocol, you can define the common pieces once and build on them.
- It reduces duplicated protocol requirements.
- It makes APIs easier to read because related capabilities are grouped logically.
- It supports abstraction by letting you describe broad roles and then narrower roles.
- It improves maintainability because changes to shared requirements happen in one place.
- It helps generic code work with families of related protocols.
You should use protocol inheritance when one protocol is clearly a more specific version of another. You should avoid it when protocols are unrelated and are only being grouped for convenience. In those cases, protocol composition is often a better fit.
3. Basic Syntax or Core Idea
The syntax is straightforward: write a protocol name, then a colon, then one or more parent protocols separated by commas.
Declaring a protocol that inherits from one protocol
This first example shows the smallest useful form. The Employee protocol inherits from Person, so any employee must also satisfy the person requirements.
protocol Person {
var name: String { get }
}
protocol Employee: Person {
var employeeID: Int { get }
}Here, Employee includes both requirements: a name and an employeeID. A conforming type must provide both.
Making a type conform to the child protocol
When a struct conforms to Employee, it must implement everything required by Employee and everything inherited from Person.
struct Developer: Employee {
let name: String
let employeeID: Int
}This works because Developer provides both required properties.
Inheriting from multiple protocols
A protocol can also inherit from more than one protocol. This is useful when the new protocol represents a type that must satisfy several roles at once.
protocol Identifiable {
var id: String { get }
}
protocol Payable {
func calculatePay() -> Double
}
protocol SalariedEmployee: Identifiable, Payable {
var annualSalary: Double { get }
}Now a SalariedEmployee must have an id, provide calculatePay(), and expose an annualSalary.
Protocol inheritance vs protocol composition
These ideas are related but not the same. Protocol inheritance creates a new protocol definition. Protocol composition usually describes a type that must conform to multiple existing protocols at the point of use.
protocol Readable {
func read()
}
protocol Writable {
func write()
}
// Protocol inheritance creates a new protocol type
protocol ReadWrite: Readable, Writable {}
// Protocol composition uses existing protocols directly
func useDevice(device: Readable & Writable) {
device.read()
device.write()
}Use inheritance when you want to define a reusable, named protocol that extends other protocols. Use composition when you only need to require multiple protocols in a specific place.
4. Step-by-Step Examples
The best way to understand protocol inheritance is to see it used in realistic situations. Each example below adds a little more complexity.
Example 1: Building a more specific role from a basic protocol
This example starts with a very general protocol and then creates a more specific one for users who can sign in.
protocol User {
var username: String { get }
}
protocol AuthenticatedUser: User {
func signIn()
}
struct Member: AuthenticatedUser {
let username: String
func signIn() {
print("\(username) signed in")
}
}Member must provide both the inherited username property and the new signIn() method. This pattern is common when general entities need specialized capabilities.
Example 2: Inheriting multiple shared requirements
Here, a protocol combines two separate areas of responsibility: identification and contact information.
protocol HasID {
var id: String { get }
}
protocol ContactInfo {
var email: String { get }
}
protocol Customer: HasID, ContactInfo {
func sendWelcomeMessage()
}
struct OnlineCustomer: Customer {
let id: String
let email: String
func sendWelcomeMessage() {
print("Welcome email sent to \(email)")
}
}This keeps each small protocol focused while still allowing a larger protocol to express a richer contract.
Example 3: Adding default behavior with protocol extensions
Protocol inheritance becomes especially useful when combined with protocol extensions. You can define broad requirements in parent protocols and then add shared behavior in the child protocol.
protocol Named {
var name: String { get }
}
protocol Describable: Named {
func describe()
}
extension Describable {
func describe() {
print("This item is named \(name).")
}
}
struct Book: Describable {
let name: String
}
let book = Book(name: "Swift Essentials")
book.describe()Book gets the inherited requirement name and also uses the default implementation of describe(). This is a clean way to share behavior across many conforming types.
Example 4: Using inherited protocols in generic code
Protocol inheritance also helps when writing functions that work with a family of related types. You can accept the more specific protocol and know that all parent requirements are also available.
protocol Vehicle {
var brand: String { get }
}
protocol ElectricVehicle: Vehicle {
var batteryLevel: Int { get }
}
struct Scooter: ElectricVehicle {
let brand: String
let batteryLevel: Int
}
func printVehicleStatus(vehicle: ElectricVehicle) {
print("Brand: \(vehicle.brand), Battery: \(vehicle.batteryLevel)%")
}The function can safely use both brand and batteryLevel because ElectricVehicle includes the inherited Vehicle requirements.
5. Practical Use Cases
Protocol inheritance is most helpful when you want protocols to represent levels of capability or responsibility.
- Modeling user roles such as User, AuthenticatedUser, and AdminUser.
- Building data layer contracts where a type must be both identifiable and serializable, then adding app-specific requirements.
- Defining business entities such as Person, Employee, and Manager with increasingly specific responsibilities.
- Creating reusable networking or storage abstractions that combine several smaller capabilities into a named protocol.
- Writing generic utility functions that operate on a specialized protocol while still benefiting from all inherited requirements.
- Sharing default behavior through protocol extensions after organizing requirements into a clear protocol hierarchy.
A good rule of thumb is to keep parent protocols small and focused. Then create child protocols only when there is a clear semantic relationship, not just because multiple requirements happen to appear together once.
6. Common Mistakes
Protocol inheritance is straightforward once the syntax clicks, but beginners often confuse it with class inheritance, protocol composition, or automatic implementation sharing. The following mistakes are common in real Swift code.
Mistake 1: Assuming inherited protocol requirements are optional
When one protocol inherits from another, any type conforming to the child protocol must satisfy all requirements from both the child and its parent protocols.
Problem: This type conforms to the child protocol but does not implement the inherited parent requirement, so Swift reports that the type does not conform to the protocol.
protocol Named {
var name: String { get }
}
protocol Employee: Named {
var employeeID: Int { get }
}
struct Developer: Employee {
let employeeID = 101
}
Fix: Implement every required member, including the ones inherited from parent protocols.
struct Developer: Employee {
let name: String
let employeeID: Int
}
The corrected version works because Developer now satisfies both Employee and its parent protocol Named.
Mistake 2: Confusing protocol inheritance with protocol composition
Protocol inheritance creates a new protocol type that includes requirements from another protocol. Protocol composition is used at the point of use when a value must satisfy several protocols at once.
Problem: This code uses inheritance syntax where a concrete type annotation is needed. Swift developers often mix these ideas up when writing function parameters.
protocol Readable {
func read()
}
protocol Writable {
func write()
}
// Not the right idea for a parameter type
func saveDocument(file: Readable: Writable) {
file.write()
}
Fix: Use protocol composition with & for a parameter that must conform to multiple protocols, or create a child protocol if the combination deserves a reusable name.
protocol Readable {
func read()
}
protocol Writable {
func write()
}
func saveDocument(file: Readable & Writable) {
file.write()
}
The corrected version works because Readable & Writable expresses a concrete requirement for the function parameter.
Mistake 3: Expecting protocol inheritance to provide implementation automatically
Inheriting from another protocol brings in requirements, not stored properties or method bodies. Default implementations can come from protocol extensions, but they are separate from inheritance itself.
Problem: This type assumes that inheriting protocol requirements also means it gets a method implementation for free, but no implementation exists.
protocol Greeter {
func greet()
}
protocol FriendlyGreeter: Greeter {
var emoji: String { get }
}
struct Bot: FriendlyGreeter {
let emoji = "đź‘‹"
}
Fix: Either implement the inherited method directly or provide a default implementation in a protocol extension.
protocol Greeter {
func greet()
}
protocol FriendlyGreeter: Greeter {
var emoji: String { get }
}
extension FriendlyGreeter {
func greet() {
print("Hello \(emoji)")
}
}
struct Bot: FriendlyGreeter {
let emoji = "đź‘‹"
}
The corrected version works because the protocol extension supplies the required method implementation.
Mistake 4: Creating deep protocol hierarchies without clear meaning
It is possible to stack many protocols together, but long inheritance chains can make APIs harder to understand. A child protocol should represent a real specialization, not just a random bundle of features.
Problem: This hierarchy is hard to reason about because each level adds loosely related requirements, making conforming types unnecessarily complicated.
protocol A {
var id: Int { get }
}
protocol B: A {
var title: String { get }
}
protocol C: B {
func archive()
}
}
Fix: Keep protocols focused and name them around actual capabilities or domain meaning.
protocol IdentifiableRecord {
var id: Int { get }
}
protocol TitledRecord: IdentifiableRecord {
var title: String { get }
}
protocol ArchivableRecord: TitledRecord {
func archive()
}
}The corrected version works better because each protocol expresses a clearer role and the hierarchy is easier to maintain.
7. Best Practices
Good protocol inheritance design makes Swift code easier to read, test, and extend. The goal is not to build the biggest protocol tree, but to define meaningful layers of behavior.
Practice 1: Keep parent protocols small and capability-focused
A small parent protocol is easier to reuse in unrelated parts of your app. If a protocol tries to describe too much, every child protocol inherits unnecessary baggage.
// Less preferred: too many unrelated requirements
protocol UserData {
var id: Int { get }
var name: String { get }
func login()
func logout()
}
// Preferred: separate the core identity from session behavior
protocol UserIdentity {
var id: Int { get }
var name: String { get }
}
protocol SessionUser: UserIdentity {
func login()
func logout()
}
This practice matters because smaller protocols are easier to compose, test, and understand.
Practice 2: Use inheritance only when the child is truly a more specific form
If two protocols are merely being used together in one function, composition may be enough. Inheritance is better when the child protocol deserves its own identity in your domain model.
// Composition at the use site
func export(item: Readable & Writable) {
item.read()
item.write()
}
// Inheritance when the combination has a domain meaning
protocol Document: Readable, Writable {
var title: String { get }
}
This keeps your API honest: composition solves temporary combinations, while inheritance defines a named abstraction.
Practice 3: Add shared behavior with protocol extensions
After you define a useful protocol hierarchy, protocol extensions are a clean way to provide default behavior for all conforming types.
protocol Named {
var name: String { get }
}
protocol Describable: Named {
var details: String { get }
}
extension Describable {
func summary() -> String {
"\(name): \(details)"
}
}
This is useful because conforming types only provide their specific data, while the extension centralizes repeated logic.
Practice 4: Choose names that show hierarchy clearly
A child protocol name should suggest that it extends the idea of the parent. This helps other developers understand the relationship without reading every requirement first.
// Less clear
protocol DataThing {
var id: Int { get }
}
protocol ExtraStuff: DataThing {
var lastUpdated: Date { get }
}
// Clearer
protocol Record {
var id: Int { get }
}
protocol TimestampedRecord: Record {
var lastUpdated: Date { get }
}
Clear names reduce confusion and make protocol hierarchies feel natural rather than arbitrary.
8. Limitations and Edge Cases
Protocol inheritance is powerful, but there are a few practical limits and behaviors worth knowing.
- Protocols can inherit from multiple protocols, but this can make conformance requirements grow quickly if the parent protocols are large.
- Protocol inheritance does not create stored properties. It only adds requirements that conforming types must satisfy.
- Default implementations from protocol extensions can be helpful, but they can also hide where behavior comes from if overused.
- A child protocol can refine a parent requirement, but the refined requirement must still remain compatible with the original contract.
- If you find yourself creating many protocols with tiny differences, your abstraction may be too granular for the problem.
- When a type uses protocol composition like A & B, that is not the same thing as defining a new protocol with semantic meaning.
- Class-only protocols can participate in inheritance, but they affect which types are allowed to conform rather than changing how inheritance itself works.
- If a conformance error says a type does not conform to a child protocol, the missing requirement may actually come from one of its parent protocols, so always check the full chain.
A common “not working” scenario is seeing Type 'X' does not conform to protocol 'Y' and only checking the child protocol's own members. Inherited requirements are often the real cause.
9. Practical Mini Project
Let’s build a small but complete example that models staff members in a company. The protocol hierarchy will start with a basic identity, then add contact information, then add employee-specific requirements.
protocol Person {
var name: String { get }
}
protocol Contactable: Person {
var email: String { get }
}
protocol Employee: Contactable {
var employeeID: Int { get }
func workSummary() -> String
}
extension Employee {
func profileSummary() -> String {
"\(name) (#\(employeeID)) - \(email)"
}
}
struct Designer: Employee {
let name: String
let email: String
let employeeID: Int
func workSummary() -> String {
"Designing user interface mockups"
}
}
let designer = Designer(
name: "Ava",
email: "[email protected]",
employeeID: 204
)
print(designer.profileSummary())
print(designer.workSummary())
This mini project shows the core idea in a realistic way. Designer conforms to Employee, which means it must also satisfy all requirements from Contactable and Person.
The protocol extension on Employee adds a reusable helper method called profileSummary(). That shared behavior works because the child protocol already guarantees access to name, email, and employeeID.
10. Key Points
- Protocol inheritance lets one protocol include the requirements of one or more parent protocols.
- A type conforming to a child protocol must implement both the child requirements and all inherited parent requirements.
- Protocol inheritance defines a new named abstraction, while protocol composition combines protocols at the point of use.
- Inheritance adds requirements, not stored properties or automatic implementation bodies.
- Protocol extensions work especially well with protocol inheritance because they can provide shared behavior to all conforming types.
- Small, focused parent protocols usually lead to cleaner and more reusable API design.
- If Swift says a type does not conform to a child protocol, the missing requirement may come from a parent protocol in the chain.
11. Practice Exercise
Try building your own protocol hierarchy for a media app.
- Create a protocol named Playable with a method called play().
- Create a protocol named Titled with a read-only title property.
- Create a child protocol named MediaItem that inherits from both Playable and Titled.
- Add a read-only duration property to MediaItem.
- Create a struct named Song that conforms to MediaItem.
- Print the song title, duration, and a playback message.
Expected output: A title, a numeric duration, and a message showing that the song is playing.
Hint: Remember that Song must implement requirements from both parent protocols as well as the child protocol.
protocol Playable {
func play()
}
protocol Titled {
var title: String { get }
}
protocol MediaItem: Playable, Titled {
var duration: Int { get }
}
struct Song: MediaItem {
let title: String
let duration: Int
func play() {
print("Now playing: \(title)")
}
}
let song = Song(title: "Sunrise", duration: 210)
print(song.title)
print(song.duration)
song.play()
This solution works because MediaItem inherits from both Playable and Titled, so Song must satisfy all three sets of requirements.
12. Final Summary
Swift protocol inheritance lets you build larger, more meaningful abstractions from smaller protocol pieces. A child protocol inherits the requirements of its parent protocols, and any conforming type must satisfy the full set of requirements. This is a clean way to model layered capabilities such as identity, contact details, and employee responsibilities without duplicating declarations.
You also saw an important distinction between protocol inheritance and protocol composition. Inheritance creates a reusable named protocol with semantic meaning, while composition combines requirements at the usage site. Combined with protocol extensions, inheritance becomes even more useful because you can attach shared behavior to a well-defined protocol hierarchy.
If you want to go further, a good next step is to study Swift protocol composition, protocol extensions, and existential types such as any Protocol. Those topics will help you understand when to define a new inherited protocol and when to combine protocols only where needed.