# Swift Protocols Explained: What Protocols Are and How to Use Them

Source: https://devdocs10.com/articles/swift/swift-what-are-protocols  
By Murat Bo · Published 2026-05-26 · DevDocs10 (https://devdocs10.com)

Swift protocols are one of the language’s most important building blocks. They let you describe what a type can do without forcing every type to share the same parent class. In this article, you’ll learn what protocols are, how protocol requirements work, how structs, classes, and enums conform to protocols, and how to avoid common mistakes when using them in real Swift code.

> **Quick answer:** A Swift protocol is a blueprint of requirements such as properties, methods, and initializers that a type must provide. Types do not inherit implementation from a protocol by default, but they promise to match its required interface.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You’ll understand this better if you know basic Swift syntax, how structs and classes are declared, and how functions and properties work.

## 1. What Is a Protocol?

A protocol in Swift defines a set of rules that a type can agree to follow. Those rules usually describe required properties, required methods, or required initializers.

Think of a protocol as a contract:

- A protocol says what must exist, not how it must be implemented.
- Multiple different types can conform to the same protocol.
- Structs, classes, and enums can all adopt protocols.
- Protocols help you write flexible code that works with behavior instead of one exact type.

For example, if several types can be displayed as text, they can all conform to one protocol even if they store data in completely different ways.

```swift
protocol Describable {
    var description: String { get }
}

struct Book: Describable {
    let title: String

    var description: String {
        "Book: \(title)"
    }
}

struct Movie: Describable {
    let name: String

    var description: String {
        "Movie: \(name)"
    }
}

let book = Book(title: "Swift Essentials")
let movie = Movie(name: "Code Runner")

print(book.description)
print(movie.description)
```

Output (tested with Swift 6.4):

```text
Book: Swift Essentials
Movie: Code Runner
```

Both types satisfy the same protocol, but each type provides its own implementation.

> Protocols are often compared with classes and inheritance. A class hierarchy shares implementation through parent-child relationships, while a protocol focuses on shared capabilities. In modern Swift, protocols are often preferred when you want flexibility without forcing unrelated types into the same inheritance chain.

## 2. Why Protocols Matters

Protocols matter because they help you design code around behavior instead of concrete types. This makes your code easier to reuse, test, and extend.

Here are some practical reasons protocols are important:

- You can write functions that accept any type that matches a required behavior.
- You can avoid large class hierarchies when types only need to share capabilities.
- You can swap one implementation for another more easily.
- Protocols support Swift’s protocol-oriented programming style.
- They make APIs clearer by showing what a value must be able to do.

Without protocols, you often end up tightly coupling code to one exact struct or class. That makes changes harder later.

```swift
protocol Payable {
    func calculatePay() -> Double
}

struct Employee: Payable {
    let hours: Double
    let rate: Double

    func calculatePay() -> Double {
        hours * rate
    }
}

struct Contractor: Payable {
    let projectsCompleted: Int
    let paymentPerProject: Double

    func calculatePay() -> Double {
        Double(projectsCompleted) * paymentPerProject
    }
}
```

Now any code that needs something payable can work with both types, even though they calculate pay differently.

## 3. Basic Syntax

The basic syntax for a protocol starts with the `protocol` keyword, followed by the protocol name and its requirements.

### Declaring a simple protocol

This protocol requires one property and one method.

```swift
protocol Greetable {
    var name: String { get }
    func greet()
}
```

The property requirement says conforming types must provide a readable `name`. The method requirement says they must also define `greet()`.

### Making a type conform to a protocol

You conform to a protocol by adding its name after a colon in the type declaration.

```swift
struct Person: Greetable {
    let name: String

    func greet() {
        print("Hello, my name is \(name).")
    }
}
```

This works because `Person` provides everything `Greetable` requires.

### Using a protocol as a type

Once a type conforms, you can write code that refers to the protocol instead of the concrete type.

```swift
func introduce(_ item: any Greetable) {
    item.greet()
}

let person = Person(name: "Maya")
introduce(person)
```

Output (tested with Swift 6.4):

```text
Hello, my name is Maya.
```

In Swift 5+, using `any` makes it clear that you are working with a protocol type value.

## 4. Step-by-Step Examples

These examples show how protocols work in increasingly practical situations.

### Example 1: Requiring a method

This is the simplest use case: a protocol requires a behavior.

```swift
protocol Drivable {
    func start()
}

struct Car: Drivable {
    func start() {
        print("Car started")
    }
}

let car = Car()
car.start()
```

Output (tested with Swift 6.4):

```text
Car started
```

The protocol does not care what kind of vehicle this is. It only requires a `start()` method.

### Example 2: Requiring a property

Protocols can require properties, including read-only or read-write properties.

```swift
protocol IdentifiableItem {
    var id: Int { get }
}

struct Order: IdentifiableItem {
    let id: Int
}

let order = Order(id: 101)
print(order.id)
```

Output (tested with Swift 6.4):

```text
101
```

Because the protocol only requires `get`, a constant stored property is enough to satisfy the requirement.

### Example 3: Conforming with a class

Classes can conform to protocols just like structs and enums.

```swift
protocol Resettable {
    func reset()
}

class GameSession: Resettable {
    var score = 10

    func reset() {
        score = 0
    }
}
```

The protocol gives a common shape, while the class chooses the implementation details.

### Example 4: One protocol, many types

Protocols become especially useful when one function should support many types.

```swift
protocol Printable {
    func printDetails()
}

struct User: Printable {
    let username: String

    func printDetails() {
        print("User: \(username)")
    }
}

struct Product: Printable {
    let title: String

    func printDetails() {
        print("Product: \(title)")
    }
}

func showItem(_ item: any Printable) {
    item.printDetails()
}

showItem(User(username: "sam_dev"))
showItem(Product(title: "Mechanical Keyboard"))
```

Output (tested with Swift 6.4):

```text
User: sam_dev
Product: Mechanical Keyboard
```

This makes the function reusable across unrelated types.

## 5. Practical Use Cases

Protocols appear in many real Swift projects. Common uses include:

- Defining shared behavior for multiple models, such as anything that can be saved, printed, or validated.
- Writing flexible functions that accept any type matching a capability instead of one concrete type.
- Separating app logic from implementation details so code is easier to test.
- Replacing deep inheritance trees with smaller, composable behaviors.
- Creating APIs where the caller only needs to know what a type can do, not what it is internally.
- Modeling standard capabilities already used by Swift, such as `Equatable`, `Comparable`, `Hashable`, and `CustomStringConvertible`.

If you have ever wanted one function to work with several different types in a clean way, a protocol is often the right tool.

## 6. Common Mistakes

Beginners often understand the idea of protocols quickly but run into syntax and conformance errors. These are some of the most common ones.

### Mistake 1: Forgetting to implement all required members

When a type claims to conform to a protocol, it must provide every required property, method, and initializer that the protocol declares.

**Problem:** This type says it conforms to the protocol, but it does not implement the required method, so Swift reports a conformance error such as `Type 'Robot' does not conform to protocol 'Speakable'`.

```swift
protocol Speakable {
    func speak()
}

struct Robot: Speakable {
    let name: String
}
```

**Fix:** Add every missing requirement exactly as the protocol describes it.

```swift
protocol Speakable {
    func speak()
}

struct Robot: Speakable {
    let name: String

    func speak() {
        print("Hello, I am \(name).")
    }
}
```

The corrected version works because the type now satisfies the full protocol contract.

### Mistake 2: Mismatching a property requirement

A protocol property requirement must be matched with compatible access and type details. A read-write requirement cannot be satisfied by a read-only property.

**Problem:** The protocol requires both reading and writing, but this type only exposes a read-only property, so the conformance fails.

```swift
protocol Nameable {
    var name: String { get set }
}

struct Customer: Nameable {
    let name: String
}
```

**Fix:** Use a writable property when the protocol requires both `get` and `set`.

```swift
protocol Nameable {
    var name: String { get set }
}

struct Customer: Nameable {
    var name: String
}
```

The corrected version works because the stored property now matches the protocol’s required access level.

### Mistake 3: Assuming a protocol provides implementation automatically

A plain protocol only declares requirements. It does not automatically supply behavior unless a protocol extension adds a default implementation.

**Problem:** This code expects the protocol declaration itself to create working behavior, but no implementation exists, so the conforming type still fails to meet the requirement.

```swift
protocol Loggable {
    func log()
}

struct ServerEvent: Loggable {
    let message: String
}
```

**Fix:** Either implement the requirement in the type itself, or provide a default implementation in a protocol extension.

```swift
protocol Loggable {
    var message: String { get }
    func log()
}

extension Loggable {
    func log() {
        print("Log: \(message)")
    }
}

struct ServerEvent: Loggable {
    let message: String
}
```

The corrected version works because the protocol extension now supplies the missing method implementation.

## 7. Best Practices

Protocols are most helpful when they describe meaningful capabilities clearly and narrowly.

### Practice 1: Define behavior, not concrete data models

A protocol should usually describe what a type can do. If it becomes a copy of one specific model, it loses flexibility.

Less flexible:

```swift
protocol UserData {
    var firstName: String { get }
    var lastName: String { get }
    var streetAddress: String { get }
}
```

Preferred when the real need is display behavior:

```swift
protocol DisplayNameProviding {
    var displayName: String { get }
}
```

This is better because the protocol describes the capability your code actually needs.

### Practice 2: Keep protocols small and focused

Large protocols are harder to adopt and often force types to implement unrelated requirements.

Less preferred:

```swift
protocol ManagerTool {
    func save()
    func load()
    func delete()
    func share()
    func archive()
}
```

Preferred:

```swift
protocol Savable {
    func save()
}

protocol Loadable {
    func load()
}
```

Smaller protocols are easier to mix together only where needed.

### Practice 3: Use protocol types when behavior matters more than implementation

If a function only needs a capability, accept the protocol rather than a specific concrete type.

Less reusable:

```swift
struct EmailNotifier {
    func send() {
        print("Email sent")
    }
}

func runNotification(_ notifier: EmailNotifier) {
    notifier.send()
}
```

Preferred:

```swift
protocol Notifying {
    func send()
}

struct EmailNotifier: Notifying {
    func send() {
        print("Email sent")
    }
}

func runNotification(_ notifier: any Notifying) {
    notifier.send()
}
```

This makes the function work with any future notifier type that conforms to the protocol.

## 8. Limitations and Edge Cases

Protocols are powerful, but they do have rules and tradeoffs that can surprise beginners.

- A protocol only defines requirements. It does not automatically store data.
- Not every protocol can be used in every context exactly like a concrete type.
- Protocol requirements must match correctly in name, type, and mutability expectations.
- Using `any` with protocol types makes existential usage explicit in modern Swift.
- Some protocol-based designs become harder to understand if you create too many tiny protocols without a clear purpose.
- If you need shared stored properties, a protocol alone cannot provide them. That often requires concrete types or other patterns.
- When a method changes a struct or enum, protocol requirements may need `mutating`, which beginners often forget.

> A common “not working” situation is declaring a method in a protocol that should modify a struct, then forgetting to mark the requirement and implementation as `mutating`. Swift then reports errors because value types cannot change themselves through non-mutating methods.

## 9. Practical Mini Project

Let’s build a small but complete example that uses protocols to handle different kinds of notifications. The goal is to write one function that can send any notification type as long as it conforms to the same protocol.

```swift
protocol NotificationSender {
    var recipient: String { get }
    func send()
}

struct EmailSender: NotificationSender {
    let recipient: String

    func send() {
        print("Sending email to \(recipient)")
    }
}

struct SMSSender: NotificationSender {
    let recipient: String

    func send() {
        print("Sending SMS to \(recipient)")
    }
}

func sendWelcomeMessage(using sender: any NotificationSender) {
    print("Preparing message for \(sender.recipient)")
    sender.send()
}

let email = EmailSender(recipient: "alex@example.com")
let sms = SMSSender(recipient: "555-1234")

sendWelcomeMessage(using: email)
sendWelcomeMessage(using: sms)
```

Output (tested with Swift 6.4):

```text
Preparing message for alex@example.com
Sending email to alex@example.com
Preparing message for 555-1234
Sending SMS to 555-1234
```

This example shows the core advantage of protocols. The `sendWelcomeMessage` function does not care whether the sender is an email sender or an SMS sender. It only cares that the sender matches the `NotificationSender` contract.

## 10. Key Points

- A Swift protocol is a blueprint of required properties, methods, or initializers.
- Structs, classes, and enums can all conform to protocols.
- Protocols define what a type must do, not how it must do it.
- Protocols help you write flexible code that depends on behavior instead of one exact type.
- A type must implement every required member to conform successfully.
- Protocols are often a better fit than inheritance when unrelated types share the same capability.
- Protocol extensions can provide default implementations, but the protocol itself does not automatically do that.

## 11. Practice Exercise

Create a protocol named `Discountable` with one method named `discountedPrice()` that returns a `Double`. Then make two structs conform to it:

- `Book` with a `price` and a 10% discount
- `Course` with a `price` and a 20% discount
- Write a function that accepts any `Discountable` value and prints the discounted price

**Expected output:** The program should print the discounted price for both a book and a course.

**Hint:** Define the protocol first, then make each struct implement the method in its own way.

```swift
protocol Discountable {
    func discountedPrice() -> Double
}

struct Book: Discountable {
    let price: Double

    func discountedPrice() -> Double {
        price * 0.9
    }
}

struct Course: Discountable {
    let price: Double

    func discountedPrice() -> Double {
        price * 0.8
    }
}

func printDiscount(for item: any Discountable) {
    print("Discounted price: \(item.discountedPrice())")
}

let book = Book(price: 30.0)
let course = Course(price: 100.0)

printDiscount(for: book)
printDiscount(for: course)
```

Output (tested with Swift 6.4):

```text
Discounted price: 27.0
Discounted price: 80.0
```

## 12. Final Summary

Protocols are a core part of Swift because they let you define shared behavior without forcing different types into the same inheritance structure. Instead of asking, “What class is this?”, protocols let you ask, “What can this type do?” That shift leads to more flexible and reusable code.

In this article, you learned what protocols are, how to declare them, how types conform to them, where they are useful, and what common mistakes to avoid. Once you are comfortable with the basics, the best next step is to learn protocol extensions and see how default implementations make protocol-oriented programming even more powerful in Swift.
