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
- • The five levels: open, public, internal, fileprivate, private
- • Why internal is the default
- • The difference between private and fileprivate
- • What open adds over public (subclassing across modules)
- • Read-only-from-outside properties with private(set)
- • How to encapsulate internals behind a safe public surface
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 private2️⃣ 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()) // 42Your 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
// falsePro Tips
- 💡 Start private, widen as needed. The less you expose, the easier code is to change safely.
- 💡 Use private(set) for state that's safe to read but should only be mutated internally.
- 💡 Reserve open for framework classes you intend others to subclass.
- 💡 A public API can't leak an internal type — keep its signature types at least as accessible.
Common Errors (and the fix)
- "'x' is inaccessible due to 'private' protection level": you touched a private member from outside. Add a method, or relax the level.
- "... cannot be declared public because its ... uses an internal type": a public API leaked a less-accessible type. Make that type public too.
- "Cannot assign to property: setter is inaccessible": the property uses private(set). Mutate it through a method instead.
- "Cannot inherit from non-open class outside of its module": the class is public, not open. Mark it open to allow subclassing.
📋 Quick Reference
| Level | Visible | Subclass outside module? |
|---|---|---|
| open | other modules | yes |
| public | other modules | no |
| internal | same module (default) | n/a |
| fileprivate | same file | n/a |
| private | enclosing declaration | n/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
- Previous: Type Casting (is, as?)
- Next: Memory Management & ARC — How ARC works, retain cycles, and weak/unowned references
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).