Access Control

Reviewed & published by Brayan K

Decide who can see and use each part of your code with Swift's five access levels — open, public, internal, fileprivate, and private — to hide implementation details and present a clean, deliberate interface.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ The Five Levels in Action

From widest to narrowest: open, public, internal, fileprivate, private. The classic pattern is a public type with private internals, exposing only safe methods.

// Swift has FIVE access levels, from most open to most restrictive:
// open > public > internal > fileprivate > private

public struct BankAccount {       // visible to other modules
    public let owner: String       // anyone can read
    private var balance: Double     // only this type can touch it

    public init(owner: String, opening: Double) {
        self.owner = owner
        self.balance = opening
    }

    // A public method is the SAFE doorway to the private balance.
    public func deposit(_ amount: Double) {
        balance += amount
    }

    public func currentBalance() -> Double {
        return balance              // reading the private value from inside is fine
    }
}

var acct = BankAccount(owner: "Ada", opening: 100)
acct.deposit(50)
print("\(acct.owner): \(acct.currentBalance())")   // Ada: 150.0
// acct.balance += 1000   // ❌ error: 'balance' is private

2️⃣ internal — The Default

Write no modifier and you get internal: usable anywhere in the same module. This is what the vast majority of app code wants, so you only annotate when you need to go tighter or wider.

// If you write NO access modifier, the default is 'internal' —
// usable anywhere in the SAME module, but not from another module.

struct Counter {            // internal by default
    var count = 0           // internal by default
    mutating func bump() {  // internal by default
        count += 1
    }
}

var c = Counter()
c.bump()
c.bump()
print(c.count)              // 2

// 'internal' is exactly what most app code wants, which is why it's
// the default — you only annotate when you need something tighter or wider.

3️⃣ private(set), private vs fileprivate

private(set) gives you a property others can read but only your own code can write — perfect for protected state. And remember: private is scoped to the enclosing declaration (plus same-file extensions), while fileprivate opens up to the whole file.

// 'private(set)' makes a readable-but-not-externally-writable property.
struct Score {
    // Readable everywhere, but only Score's own code can change it.
    private(set) var points = 0

    mutating func add(_ n: Int) {
        points += n
    }
}

var s = Score()
s.add(10)
s.add(5)
print(s.points)      // 15  (reading is fine)
// s.points = 999    // ❌ error: setter is private

// fileprivate vs private: fileprivate is visible to the whole FILE,
// private is limited to the enclosing declaration (and same-file extensions).
struct Widget {
    private var secret = 42
}
extension Widget {            // same file -> may see 'private' members
    func reveal() -> Int { secret }
}
print(Widget().reveal())      // 42

Your turn. Fill in the setter modifier and the most restrictive level.

// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.

struct Vault {
    // 1) Make 'code' readable from anywhere but settable only inside Vault.
    ___(set) var code = "0000"     // 👉 the access modifier for the setter

    // 2) Make 'attempts' fully hidden from outside this type.
    ___ var attempts = 0           // 👉 the most restrictive access level

    mutating func tryOpen(_ guess: String) -> Bool {
        attempts += 1
        return guess == code
    }
}

var v = Vault()
print(v.code)                      // 0000  (reading allowed)
print(v.tryOpen("1234"))           // false
// print(v.attempts)               // would be an error: attempts is private

// ✅ Expected output:
//    0000
//    false

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

LevelVisibleSubclass outside module?
openother modulesyes
publicother modulesno
internalsame module (default)n/a
fileprivatesame filen/a
privateenclosing declarationn/a

Mini-Challenge: Temperature Sensor

No blanks this time — just a brief and an outline. Encapsulate the calibration and expose only a read-only reading.

// 🎯 MINI-CHALLENGE: a temperature sensor
// 1. Make a struct  TemperatureSensor.
// 2. Give it  private(set) var reading: Double = 0  (read anywhere, set inside).
// 3. Give it  private var calibration: Double = 1.0  (fully hidden).
// 4. Add a mutating  func record(_ raw: Double)  that sets
//    reading = raw * calibration.
// 5. Create one, call record(20), and print the reading.
//
// ✅ Expected output:
//    20.0

// your code here

Practice quiz

What is the default access level in Swift when you write none?

  • private
  • public
  • internal
  • open

Answer: internal. If you omit an access modifier, the entity is 'internal' — visible anywhere within the same module.

Which access level restricts use to the same source file?

  • fileprivate
  • private
  • internal
  • public

Answer: fileprivate. 'fileprivate' makes an entity accessible only within the file it is declared in.

How does 'private' differ from 'fileprivate'?

  • They are identical
  • private is broader
  • private allows subclassing
  • private limits to the enclosing declaration (and its extensions in the same file); fileprivate limits to the whole file

Answer: private limits to the enclosing declaration (and its extensions in the same file); fileprivate limits to the whole file. private is tighter — scoped to the enclosing type/declaration, while fileprivate covers the entire file.

What does 'internal' mean?

  • Visible only in the type
  • Visible anywhere within the same module
  • Visible to any module
  • Visible only in subclasses

Answer: Visible anywhere within the same module. internal entities are accessible throughout the defining module but not from other modules.

What can 'open' do that 'public' cannot?

  • Allow subclassing and overriding from outside the defining module
  • Nothing, they are the same
  • Restrict access to a file
  • Make members private

Answer: Allow subclassing and overriding from outside the defining module. open lets other modules subclass a class and override its members; public exposes but does not permit external subclassing/overriding.

Which is the MOST restrictive access level?

  • fileprivate
  • internal
  • private
  • public

Answer: private. private is the most restrictive, limiting access to the enclosing declaration.

Order these from most open to most restrictive.

  • private, fileprivate, internal, public, open
  • open, public, internal, fileprivate, private
  • internal, public, open, private, fileprivate
  • public, open, internal, private, fileprivate

Answer: open, public, internal, fileprivate, private. From most to least open: open, public, internal, fileprivate, private.

Can a public function have a parameter of an internal type?

  • Yes, always
  • Only with open types
  • Only inside extensions
  • No — a public API cannot expose a less-accessible type in its signature

Answer: No — a public API cannot expose a less-accessible type in its signature. An entity cannot be more accessible than the types it uses in its public interface.

What is a common use of 'private(set)' on a property?

  • Make it fully public
  • Allow reading from anywhere but only setting within the declaring scope
  • Hide it completely
  • Make it open

Answer: Allow reading from anywhere but only setting within the declaring scope. private(set) gives a property a public (or internal) getter but a private setter — read freely, write internally.

Why use access control at all?

  • To make code run faster
  • It is required for every declaration
  • To hide implementation details and expose a clean, intentional interface
  • To enable optional chaining

Answer: To hide implementation details and expose a clean, intentional interface. Access control encapsulates internals, reducing coupling and protecting invariants behind a deliberate public surface.

Continue this course

Frequently asked questions

What are Swift's access levels, from most to least open?

There are five: open, public, internal, fileprivate, and private. 'open' and 'public' both expose an entity to other modules, but only 'open' permits subclassing and overriding from outside. 'internal' (the default) keeps things within the current module. 'fileprivate' limits use to the same source file, and 'private' is tightest, limiting use to the enclosing declaration and its same-file extensions.

What is the default access level if I don't write one?

internal. An entity with no access modifier is visible anywhere within the same module (your app or framework target) but invisible to other modules. This is the right default for most app code, which is why Swift chose it — you only add an explicit modifier when you need something narrower (private/fileprivate) or wider (public/open).

What is the difference between private and fileprivate?

private is the more restrictive of the two. A private member is accessible only within its enclosing declaration (the type or scope where it's defined) and extensions of that type written in the same file. fileprivate is looser: it makes the member accessible anywhere within the same source file, including other unrelated types in that file. Reach for private first and widen to fileprivate only when you actually need it.

When should I use open versus public?

Use public to expose an API that other modules can use but should not subclass or override — it locks down your class's behavior. Use open when you deliberately want clients in other modules to be able to subclass your class or override its methods, such as in a framework designed for extension. Within your own module the distinction doesn't matter; it only affects code in other modules.

Can a public function use an internal or private type in its signature?

No. Swift enforces that an entity cannot be more accessible than the types that make up its public interface. A public function whose parameter or return type is internal would leak something callers can't even name, so the compiler rejects it. To expose the function publicly, you must also make the types it uses at least as accessible (public).