swift
readonly property
readwrite property
swift programming
access control

Swift readonly external, readwrite internal property

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Swift lets you expose a property as readable to other modules while still allowing code inside the module to update it. This is a common API design pattern for framework authors because it preserves encapsulation without forcing awkward getter methods.

The Core Syntax

The feature you want is a more restrictive setter than getter. In Swift, that is written with a setter access modifier such as private(set), fileprivate(set), or internal(set).

For a property that is readable outside the module but writable only inside the module, use public internal(set).

swift
1public final class DownloadTask {
2    public internal(set) var state: String = "pending"
3
4    public func start() {
5        state = "running"
6    }
7
8    public func finish() {
9        state = "completed"
10    }
11}

Code inside the same module can assign to state, but code in another module can only read it.

Why This Pattern Is Useful

A public mutable property is easy to misuse. If external code can change internal state freely, your type can become inconsistent. Restricting the setter lets the type remain authoritative about its own lifecycle while still making the current value observable.

Imagine a framework that reports a connection status. Consumers should be able to inspect the status, but only the framework itself should decide when that status changes.

swift
1public struct ConnectionInfo {
2    public internal(set) var isConnected: Bool = false
3    public internal(set) var retryCount: Int = 0
4
5    mutating func markConnected() {
6        isConnected = true
7        retryCount = 0
8    }
9
10    mutating func markFailure() {
11        isConnected = false
12        retryCount += 1
13    }
14}

This makes the API safer because state changes stay attached to meaningful domain methods instead of arbitrary assignments.

Choosing the Right Setter Scope

Swift gives you several options:

  • 'private(set) means only the enclosing declaration can write the property.'
  • 'fileprivate(set) means anything in the same file can write it.'
  • 'internal(set) means anything in the same module can write it.'

For a framework or package API, public internal(set) is usually the correct answer when you want module-wide writes and public reads.

For a single type that should control all mutations itself, public private(set) is stricter and often better:

swift
1public final class Counter {
2    public private(set) var value: Int = 0
3
4    public func increment() {
5        value += 1
6    }
7
8    public func reset() {
9        value = 0
10    }
11}

This version prevents even other types in the same module from mutating value directly.

Behavior Across Modules

Suppose Counter lives in a framework module named MetricsKit. Code inside MetricsKit can call methods like increment(). If the property is marked public private(set), outside code can read counter.value but cannot assign to it. The compiler enforces that rule, so consumers see the property as effectively read-only.

That is the important distinction: the property is not truly immutable. It has a setter, but the setter is not visible at the same access level as the getter.

If you only write public var value: Int, both getter and setter become public. Swift does not infer a restricted setter automatically. You must declare the setter visibility explicitly.

Common Pitfalls

The most common mistake is choosing private(set) when the requirement is actually "writable anywhere inside the module." private(set) is much stricter than internal(set), so helper types in the same module will not be able to update the property.

Another issue is misunderstanding internal. It is the default access level for declarations in Swift, but once you mark the property public, the setter becomes public too unless you override it with internal(set) or another restricted scope.

Developers also sometimes expose a mutable collection with a restricted setter and assume the contents are protected. That is not always true. If the property exposes a reference type or a mutable collection, consumers may still mutate the underlying object through the getter. In those cases, you may need a value type, a copied view, or a read-only abstraction.

Finally, keep API intent clear. If outside callers should not think of the property as something to manage, pair the restricted setter with domain methods such as start(), finish(), or reset(). That makes the mutation model obvious.

Summary

  • Use public internal(set) for public reads and module-only writes.
  • Use public private(set) when only the type itself should mutate the property.
  • Swift does not restrict the setter automatically once a property is public.
  • Restricted setters improve encapsulation and keep state transitions under control.
  • Be careful with mutable reference types and collections, because a restricted setter does not always guarantee deep immutability.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.