ProgrammingiOS Developer

What are protocol extensions in Swift and why are they needed? How do they integrate with protocol-oriented programming and what pitfalls can arise when using default implementations?

Pass interviews with Hintsage AI assistant

Answer.

Protocol extensions were introduced in Swift to support the ideology of protocol-oriented programming: a programmer can implement default method implementations directly at the protocol level rather than through a base class ancestor or global functions. This reduces code duplication and allows for flexible adaptation of type behavior.

The problem arises when a default implementation masks the need for a custom (override), or when the line of responsibility becomes blurred — especially if a type implements multiple protocols with overlaps. Additionally, it's important to remember: if a method is implemented in the type itself, it will always override the implementation from the extension.

The solution is to use protocol extensions only for universal behavior, and in specific cases explicitly implement the method within the type. It's advisable to avoid overloading the same methods in two extensions for the same protocol.

Example code:

protocol Flyer { func fly() } extension Flyer { func fly() { print("Default flying") } } struct Bird: Flyer {} let sparrow = Bird() sparrow.fly() // Will output: Default flying

Key features:

  • Allow providing shared functionality and reduce duplication without inheritance.
  • The default implementation only works if the type has not explicitly defined its method.
  • Allow using generics and where constraints to refine behavior for specific types.

Trick questions.

Can a protocol extension add a stored property to a protocol?

No, only computed properties and methods can be added in protocol extensions. Stored properties are not allowed.

What will happen if a protocol and a type have different implementations of a method with the same name? Which one will be called?

When directly calling the type, the type's implementation will be called; when accessing an instance through a protocol type reference, the protocol extension implementation will be called.

protocol Greeter { func greet() } extension Greeter { func greet() { print("Hi from extension") } } struct Person: Greeter { func greet() { print("Hi from type") } } let person = Person() person.greet() // Hi from type let greeter: Greeter = person greeter.greet() // Hi from extension

Can you use where constraints in protocol extensions for restrictions?

Yes. This is one of the powerful capabilities — a protocol can be extended only for specific types.

extension Collection where Element: Equatable { func allEqual() -> Bool { guard let first = self.first else { return true } return allSatisfy { $0 == first } } }

Typical errors and anti-patterns

  • Implementing behavior using protocol extension, expecting override in specific types: protocol extension does not support override, it is not a class.
  • Overriding a type method and an extension method (different signatures or business logic) can lead to unexpected polymorphic behavior.

Real-life example

**Negative case

The team decided to implement error logging through a protocol extension, not considering that each service might want to add its specific format. As a result, different services call the function through protocol references, and the behavior logic differs from expectations.

Pros:

  • Minimal code, easy to maintain the basic implementation.

Cons:

  • Surprises with runtime polymorphism, divergence of intent and execution, bugs in production.

**Positive case

Protocols are extended only for those cases where behavior is always universal. For special cases — explicit implementation of methods in the type, and there is a code review on conflict areas.

Pros:

  • Clear logic, minimal errors with calls, universality works only where it should.

Cons:

  • Requires knowledge of nuances, cannot completely avoid copy-pasting in single cases.