Swift Protocol Explained

If you’ve been learning Swift for a little while, you’ve probably heard the word “protocol” It’s actually one of the most useful tools in the Swift language
Written by

Chris C

Updated on

Apr 09 2026

Table of contents

    A Swift protocol is a way of defining a set of requirements that a type must fulfill. Think of it like a job description. The protocol says “anyone who wants this role must be able to do these things” — but it doesn’t say exactly how they do it. That part is left to the type that signs up.

    In practice, you’ll run into protocols constantly in iOS development. View in SwiftUI is a protocol. Identifiable, Hashable, Equatable, Codable — all protocols. They’re how the Swift programming language lets you write flexible, reusable code without forcing everything into a rigid class hierarchy.

    In this guide, you’ll learn what a Swift protocol is, how to define one, how to conform to one, and how protocols show up in real iOS app code. By the end, this concept should feel like a natural part of your Swift toolkit.

    The Basic Syntax

    Here’s the simplest possible protocol you can write in Swift. You can paste this into an Xcode Playground and it will run immediately:

    // Define the protocol
    protocol Greetable {
        var name: String { get }
        func greet()
    }
    
    // Conform a struct to the protocol
    struct Person: Greetable {
        var name: String
        func greet() {
            print("Hi, I'm \(name)!")
        }
    }
    
    // Use it
    let chris = Person(name: "Chris")
    chris.greet()  // Hi, I'm Chris!

    Let’s walk through each part so you know exactly what’s happening:

    LineWhat it does
    protocol Greetable { }Defines a new protocol called Greetable. The name is entirely up to you. By convention, Swift protocol names tend to be adjectives that describe a capability — like “Greetable”, “Identifiable”, or “Comparable”.
    var name: String { get }This says any type conforming to Greetable must have a name property that can be read. The { get } means it needs to be at minimum readable — more on this in a moment.
    func greet()This says any conforming type must have a greet() method. Notice there’s no body here — just the signature. The actual implementation is left to whoever conforms to the protocol.
    struct Person: GreetableThe colon after Person followed by the protocol name means “Person promises to conform to Greetable”. If Person doesn’t fulfill every requirement, Swift will refuse to compile the code.
    var name: String (in Person)This is the actual implementation of the name requirement. Person provides a stored property that satisfies the protocol’s demand.
    func greet() { ... } (in Person)This is Person’s actual implementation of the greet() method. Every type that conforms to Greetable can have a completely different implementation — the protocol just ensures the method exists.

    Breaking It Down

    Now that you’ve seen a basic protocol, let’s clear up a few things that trip up almost every beginner.

    What does { get } and { get set } mean?

    When you declare a property inside a protocol, you have to tell Swift whether that property needs to be readable, or both readable and writable. You do this with the { get } and { get set } syntax.

    • { get } means the conforming type just needs to be able to return a value for this property. It can be a stored property, a computed property, a constant — anything that can be read.
    • { get set } means the conforming type must provide a property that can be both read and written. This rules out constants (let) — you’d need a var.
    Common beginner mistake: Forgetting to mark a property as { get set } in your protocol when you actually need it to be mutable. If the protocol only says { get }, conforming types are free to implement it as a constant — which means you won’t be able to change it later.

    The protocol body vs. the conforming type’s body

    A protocol never contains actual code. It only lists what must exist. The real code — the implementation — lives in the struct, class, or enum that conforms to the protocol. This separation is intentional and powerful. Different types can implement the same protocol in completely different ways.

    What can conform to a protocol?

    In Swift, protocols can be adopted by structs, classes, and enums. This is actually a big advantage over class-based inheritance, which only works with classes. Because most types in Swift (including SwiftUI Views) are structs, protocols are the primary way to share capabilities across them.

    You can conform to more than one protocol

    A type can conform to as many protocols as you want. Just separate them with commas after the colon:

    struct Employee: Greetable, Identifiable {
        var id: UUID
        var name: String
        func greet() {
            print("Hey, I'm \(name)")
        }
    }

    Variations and Syntax Patterns

    Protocols in the Swift language appear in several forms. Here are the most important patterns you’ll encounter as a beginner.

    Protocol with { get set } property When conforming types need to be able to change the property
    protocol Nameable {
        var name: String { get set }  // must be readable AND writable
    }
    
    struct User: Nameable {
        var name: String  // var satisfies { get set }
    }
    
    var user = User(name: "Jordan")
    user.name = "Taylor"  // allowed because name is { get set }
    Use { get set } when the property needs to be updated after creation. If you try to satisfy a { get set } requirement with a let constant, Swift will give you a compile error.
    Protocol used as a type Writing functions that accept any conforming type
    protocol Describable {
        func describe() -> String
    }
    
    // This function works with ANY type that conforms to Describable
    func printDescription(_ item: Describable) {
        print(item.describe())
    }
    
    struct Book: Describable {
        var title: String
        func describe() -> String { "Book: \(title)" }
    }
    
    struct Movie: Describable {
        var title: String
        func describe() -> String { "Movie: \(title)" }
    }
    
    printDescription(Book(title: "Swift Programming"))
    printDescription(Movie(title: "The Matrix"))
    This is where protocols get really powerful. You can use a protocol as the type of a function parameter instead of a specific type. The function doesn’t care whether it gets a Book or a Movie — it just knows it can call describe() on whatever comes in.
    Protocol inheritance One protocol building on top of another
    protocol Animal {
        var name: String { get }
        func makeSound()
    }
    
    // Pet inherits all requirements from Animal, plus adds its own
    protocol Pet: Animal {
        var owner: String { get }
    }
    
    // Dog must satisfy BOTH Animal and Pet requirements
    struct Dog: Pet {
        var name: String
        var owner: String
        func makeSound() { print("Woof!") }
    }
    Protocols can inherit from other protocols, just like classes can inherit from other classes. When Pet inherits from Animal, any type conforming to Pet must satisfy the requirements of both Pet and Animal.
    Protocol extension (default implementations) Giving protocol methods a default body
    protocol Greetable {
        var name: String { get }
        func greet()
    }
    
    // Extend the protocol to provide a default greet() implementation
    extension Greetable {
        func greet() {
            print("Hello, I'm \(name).")
        }
    }
    
    struct Robot: Greetable {
        var name: String
        // No greet() needed — uses the default from the extension
    }
    
    let r2 = Robot(name: "R2-D2")
    r2.greet()  // Hello, I'm R2-D2.
    Protocol extensions let you provide a default implementation for a method. Conforming types automatically get the default, but they can override it by providing their own version. This is one of the things that makes Swift protocols more powerful than interfaces in many other languages.
    mutating func in a protocol When a protocol method needs to modify the conforming type’s properties
    protocol Resettable {
        mutating func reset()
    }
    
    struct Counter: Resettable {
        var count = 0
    
        mutating func reset() {
            count = 0  // mutating lets us change count on a struct
        }
    }
    
    var c = Counter()
    c.count = 10
    c.reset()
    print(c.count)  // 0
    Because structs are value types, any method that changes their properties must be marked mutating. If your protocol defines a method that will change a property, mark it mutating func in the protocol. Classes don’t need this keyword, but if you add it to the protocol, it doesn’t hurt class conformance.
    Protocol composition with & Requiring multiple protocols at once without creating a new protocol
    protocol Named {
        var name: String { get }
    }
    
    protocol Aged {
        var age: Int { get }
    }
    
    // This function requires a type that is BOTH Named AND Aged
    func introduce(_ person: Named & Aged) {
        print("I'm \(person.name) and I'm \(person.age) years old.")
    }
    
    struct Student: Named, Aged {
        var name: String
        var age: Int
    }
    
    introduce(Student(name: "Sam", age: 22))
    // I'm Sam and I'm 22 years old.
    The & operator combines protocols inline. Instead of creating a brand new protocol that inherits from both, you can write Named & Aged directly as a type. This is common in SwiftUI where you’ll see things like Identifiable & Hashable.

    How Swift Protocols Appear in Real iOS Code

    Here’s where it gets satisfying. Once you understand protocols in theory, you start recognizing them everywhere in SwiftUI and iOS development.

    Consider one of the most common patterns in a beginner iOS app: displaying a list of items. You’ll want each item to be Identifiable (so SwiftUI knows which row is which), and you might want them to be comparable or hashable. All of those are built-in Swift protocols.

    import SwiftUI
    
    // Conforming to Identifiable is required for ForEach in SwiftUI
    struct Task: Identifiable {
        let id: UUID = UUID()   // Identifiable requires an 'id' property
        var title: String
        var isComplete: Bool
    }
    
    // A custom protocol for anything that can be marked as done
    protocol Completable {
        var isComplete: Bool { get set }
        mutating func markComplete()
    }
    
    // Extend Task to also conform to our custom Completable protocol
    extension Task: Completable {
        mutating func markComplete() {
            isComplete = true
        }
    }
    
    // A SwiftUI view that works with any Completable & Identifiable type
    struct TaskRow: View {
        var task: Task
    
        var body: some View {
            HStack {
                Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
                    .foregroundColor(task.isComplete ? .green : .gray)
                Text(task.title)
            }
        }
    }

    Notice a few things here. First, Task conforms to the built-in Identifiable protocol by providing an id property. That’s all SwiftUI needs to use Task inside a ForEach list.

    Second, you can add protocol conformance after the fact using an extension. That’s what extension Task: Completable does. This is a very common pattern in real iOS code — you define your type, then you add conformances in separate extensions to keep things organized.

    Third, notice that View in SwiftUI is itself a protocol. When you write struct TaskRow: View, you’re conforming your struct to the View protocol by implementing a body property. You’ve been using protocols from day one.

    Common Mistakes to Avoid

    These are the mistakes I see most often from students learning Swift protocols for the first time. Each one has a clear fix.

    Mistake 1: Missing a required method or property

    If your type claims to conform to a protocol but doesn’t implement every requirement, Swift will refuse to build your project. You’ll see an error like “Type does not conform to protocol”. This is actually a feature — Swift is catching the problem for you at compile time, not at runtime. Fix it by checking the protocol definition and making sure you’ve implemented everything it asks for.

    Tip: If you click on the error in Xcode, it sometimes offers to auto-generate the missing stubs for you. This is a great way to see exactly what the protocol needs.

    Mistake 2: Using { get } when you need { get set }

    If you declare a property as { get } in your protocol, conforming types are allowed to make that property a constant (let). If you later try to update that value, Swift won’t let you. Think carefully about whether the value needs to change after it’s set, and use { get set } accordingly.

    Mistake 3: Forgetting mutating on a struct’s protocol method

    If your protocol defines a mutating func and you’re conforming with a struct, you must also mark your implementation mutating. If you miss it, Swift will give you an error saying you can’t assign to a property. The fix is simple: add mutating to the method in both the protocol and the struct.

    Mistake 4: Expecting the protocol to “do” something

    A protocol is a contract, not an implementation. It can’t be instantiated on its own. You can’t write let g = Greetable() — that will fail because Greetable is just a description. You always need a concrete type (a struct, class, or enum) that actually implements the protocol’s requirements. The protocol just ensures those requirements exist.

    Quick Reference

    Here’s a cheat sheet of the syntax patterns covered in this article. Bookmark it for when you need a quick reminder while building.

    SyntaxWhat It Does
    protocol MyProtocol { }Defines a new protocol called MyProtocol
    var name: String { get }Requires a readable property — can be a let or var
    var name: String { get set }Requires a readable and writable property — must be a var
    func greet()Requires a method with no implementation in the protocol body
    mutating func reset()Requires a method that can modify struct properties
    struct Foo: MyProtocol { }Conforms Foo to MyProtocol
    struct Foo: A, B, C { }Conforms to multiple protocols at once
    protocol Child: Parent { }Protocol inheritance — Child includes all of Parent‘s requirements
    extension MyProtocol { func greet() { } }Provides a default implementation — conforming types can use or override it
    func foo(_ x: A & B)Protocol composition — parameter must conform to both A and B
    extension Foo: MyProtocol { }Adds protocol conformance to an existing type in a separate extension

    Using AI to Go Deeper with Swift Protocols

    AI tools like Claude, GitHub Copilot, and Cursor are now a real part of how developers work. Knowing the right questions to ask is itself a skill — and protocols are a topic where AI tutoring can be genuinely helpful. Here are three ways to use these tools to strengthen your understanding.

    Deepen Your Understanding Use AI as a tutor — no code generation required
    Explain Swift protocols to me like I’m a beginner. Use a real-world analogy first, then show me the simplest possible code example and walk through it line by line. Don’t write more than 10 lines of code.
    I think I understand Swift protocols but I’m not 100% sure I get the difference between { get } and { get set }. Can you quiz me on it? Ask me one question at a time and tell me when I get something wrong.
    Build a Practice Example Get a commented, learning-focused code example to study
    Write a short Swift example that uses a protocol in a realistic iOS context — something like a recipe app or a to-do list. Add a comment on every single line explaining what it does and why. Write the comments for someone who is still learning Swift.
    Give me 3 different Swift protocol examples, each slightly more complex than the last. Start with a basic protocol with one method, then show protocol inheritance, then show a protocol extension with a default implementation. Add inline comments throughout.
    Audit Your Own Code Have AI review your code — you stay in the driver’s seat
    Here’s some Swift code I wrote: [paste your code]. Can you tell me if there are places where I should be using a protocol but I’m not? Explain why for each suggestion — don’t just rewrite my code.
    Review this Swift code and look specifically at how I defined my protocol: [paste your code]. Are the property requirements correct — am I using { get } vs { get set } appropriately? Explain before suggesting any changes.
    Remember: If AI generates code for you, make sure you can explain every single line before you move on. If any line is confusing, ask the AI to explain it. The goal is your understanding — not just getting code that compiles.

    When To Use Swift Protocols

    Here are some real scenarios where a protocol is the right tool for the job in an iOS app:

    • 📋

      A to-do app with multiple task types

      You might have simple tasks, recurring tasks, and collaborative tasks. They all need a title, a dueDate, and a markComplete() method. Define a Task protocol and write your list UI to work with any conforming type — regardless of the specifics underneath.

    • 🏋️

      A fitness tracker with different workout types

      Running, cycling, and swimming all track duration and calories, but each has unique data too. A Workout protocol with shared requirements lets you write one summary view and one history list that works for every workout type you add later.

    • 🧪

      Making your code testable

      This is one of the most practical uses of protocols that beginners often don’t hear about. If you define a protocol for your data service (e.g. DataService), you can swap in a fake implementation during testing that returns predictable data — no real network calls required. This makes your app much easier to test.

    Summary

    Here’s a quick recap of what you learned about Swift protocols:

    • A protocol defines requirements — properties and methods — that conforming types must implement
    • Use { get } for readable properties and { get set } for properties that need to be writable
    • Structs, classes, and enums can all conform to protocols
    • A type can conform to multiple protocols by listing them separated by commas
    • Protocol extensions let you provide default implementations that conforming types can use or override
    • Mark protocol methods mutating func if they need to change a struct’s properties
    • Use & for protocol composition when a function needs a type that satisfies multiple protocols
    • Built-in protocols like Identifiable, Codable, and View are already part of your daily SwiftUI code

    The best way to get comfortable with protocols is to start noticing them in code you’re already writing. Look at your SwiftUI views — every single one is conforming to the View protocol. Look at the data types you pass into ForEach — they’re conforming to Identifiable. Protocols are everywhere once you know what to look for.

    Once you’re comfortable with protocols, a natural next step is learning about generics in Swift — the two topics are closely related and generics become much easier to understand once protocols click.



    Get started for free

    Join over 2,000+ students actively learning with CodeWithChris