When building apps in Swift, you quickly discover that organizing and protecting your code is just as important as making it work. That’s where access control comes in. Swift’s access modifiers help you manage which parts of your code can be seen or used outside of their defining context—whether that’s a file, a module, or a specific scope.

Swift provides five levels of access: open, public, internal, fileprivate, and private. In this post, we’ll break down each one, show when to use them, and give practical examples to make everything click.


Why Access Control Matters

Access control helps you:

  • Encapsulate implementation details
  • Prevent accidental misuse of APIs
  • Keep your codebase clean and maintainable
  • Design clear, intentional interfaces

Think of it as giving your code a set of permissions—like deciding which rooms in your digital “house” guests are allowed to enter.


1. open — The Most Permissive (For Frameworks)

open is the highest access level in Swift. It allows code to be accessed outside the module and also allows subclassing and overriding from other modules.

Use it when you’re building frameworks or libraries meant to be extended.

Example:

open class Animal {
    open func speak() {
        print("Some sound")
    }
}

2. public — Accessible Outside the Module, But Not Override-Friendly

public also allows entities to be used outside your module, but you cannot subclass or override them from outside the module.

It’s ideal for frameworks when you want to expose functionality but not allow external customization.

Example:

public class MathUtils {
    public static func square(_ x: Int) -> Int {
        return x * x
    }
}

3. internal — The Default Access Level

internal is Swift’s default. It allows code to be accessed anywhere within the same module, but not by external modules.

Use it for most app code. If you don’t specify a modifier, you’re using internal.

Example:

internal struct User {
    var name: String
}

4. fileprivate — Visible Only in the Same File

fileprivate restricts access to the current source file. This is useful when you need helper functions or extensions that should stay hidden from the rest of the module.

Example:

fileprivate func generateSecretKey() -> String {
    return "abc123"
}

5. private — The Most Restrictive (Within a Declaration)

private makes declarations accessible only within the same scope (e.g., a class or struct). It’s your go-to choice for encapsulating implementation details.

Example:

class BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }
}

Quick Comparison Table

Modifier Accessible Outside Module? Subclassable Outside Module? Typical Use
open Yes Yes Extensible frameworks
public Yes No Exposed APIs
internal No Default app code
fileprivate No (file only) File-level helpers
private No (scope only) Encapsulation

Choosing the Right Modifier

Here are some quick guidelines:

  • Use private for implementation details you don’t want exposed.
  • Use fileprivate when multiple declarations in a file need to cooperate.
  • Use internal (default) for all normal application logic.
  • Use public to expose APIs without allowing external subclassing.
  • Use open only when you explicitly intend extensibility across modules.