Swift has one non-negotiable rule about initialization: every stored property must have a value before an instance can be used. There’s no “uninitialized” memory to stumble into later, no half-built object reachable from your code. Everything else about initializers, from free memberwise initializers to the two-phase process for classes, exists to enforce that rule while keeping initialization flexible.
Initialization in Swift
Default values and custom initializers
The simplest way to satisfy the rule is to give every property a default value:
struct Fahrenheit {
var temperature = 32.0
}
let freezing = Fahrenheit()
Or write an initializer that sets every property:
struct Celsius {
var temperature: Double
init(fromFahrenheit fahrenheit: Double) {
temperature = (fahrenheit - 32) / 1.8
}
init(fromKelvin kelvin: Double) {
temperature = kelvin - 273.15
}
}
let boiling = Celsius(fromFahrenheit: 212)
let absolute = Celsius(fromKelvin: 0)
Initializers have no names of their own, so their argument labels are how callers tell them apart. Choose labels that describe what the argument means, like fromFahrenheit: here.
let properties can be assigned exactly once during initialization, and never again afterwards.
Memberwise initializers for structs
Structs get an automatic initializer with one parameter per stored property, in declaration order. Since Swift 5.1, properties with default values become parameters with defaults:
struct Size {
var width = 0.0
var height = 0.0
}
let a = Size()
let b = Size(width: 2)
let c = Size(width: 2, height: 3)
Classes don’t get memberwise initializers, because the compiler can’t know how you want inheritance to work.
Two things that make the memberwise initializer disappear
1. Writing your own initializer in the struct body. Once you define an init, Swift assumes you want full control. To keep both, put your custom initializer in an extension:
struct Point {
var x: Double
var y: Double
}
extension Point {
init(both value: Double) {
self.init(x: value, y: value) // delegate to the memberwise initializer
}
}
let origin = Point(x: 0, y: 0)
let diagonal = Point(both: 5)
2. A private property without a default value. The memberwise initializer would have to take it as a parameter, which would leak it, so the initializer itself becomes private:
struct Token {
private var secret: String
var name: String
}
// let token = Token(secret: "abc", name: "api") // ❌ 'Token' initializer is inaccessible due to 'private' protection level
Give the private property a default value, or write a public initializer, to fix this.
Initializer delegation in value types
An initializer can call another initializer of the same type with self.init(…). It’s the cleanest way to avoid duplicating validation and setup:
struct Rect {
var originX = 0.0, originY = 0.0
var width = 0.0, height = 0.0
init(originX: Double, originY: Double, width: Double, height: Double) {
self.originX = originX
self.originY = originY
self.width = width
self.height = height
}
init(centerX: Double, centerY: Double, width: Double, height: Double) {
self.init(originX: centerX - width / 2, originY: centerY - height / 2,
width: width, height: height)
}
}
let centered = Rect(centerX: 5, centerY: 5, width: 4, height: 2)
print(centered.originX, centered.originY) // 3.0 4.0
Failable initializers
Some inputs simply can’t produce a valid instance. init? returns an optional, and can return nil:
struct Email {
let address: String
init?(_ address: String) {
guard address.contains("@"), !address.hasPrefix("@") else { return nil }
self.address = address
}
}
if let email = Email("[email protected]") {
print(email.address)
}
let invalid = Email("not-an-email") // nil
This pattern is a great way to make invalid states unrepresentable: if an Email exists, it passed validation. The rest of your code never has to check again.
Enums with raw values get a failable initializer for free, init?(rawValue:), and you can write your own:
enum Weekday: Int {
case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
init?(name: String) {
switch name.lowercased() {
case "mon", "monday": self = .monday
case "sun", "sunday": self = .sunday
default: return nil
}
}
}
print(Weekday(rawValue: 3) == .wednesday) // true
print(Weekday(name: "Sun") == .sunday) // true
print(Weekday(name: "Funday") == nil) // true
When callers need to know why creation failed, use a throwing initializer instead:
enum PasswordError: Error {
case tooShort, missingDigit
}
struct Password {
let value: String
init(_ value: String) throws {
guard value.count >= 8 else { throw PasswordError.tooShort }
guard value.contains(where: { $0.isNumber }) else { throw PasswordError.missingDigit }
self.value = value
}
}
do {
_ = try Password("short")
} catch {
print(error) // tooShort
}
Classes: designated and convenience initializers
Classes have two kinds of initializers:
- Designated initializers are the primary ones. They fully initialize the class’s own properties and then call a superclass designated initializer.
- Convenience initializers are secondary shortcuts. They must call another initializer of the same class, eventually reaching a designated one.
The rule of thumb: designated initializers delegate up, convenience initializers delegate across.
class Food {
let name: String
init(name: String) { // designated
self.name = name
}
convenience init() { // convenience
self.init(name: "[Unnamed]")
}
}
let mystery = Food()
print(mystery.name) // [Unnamed]
Two-phase initialization
Class initialization happens in two phases. This is how Swift guarantees that no method ever sees a half-built object, even across an inheritance chain:
- Phase 1, initializing storage. Starting at the subclass and moving up, each class sets its own stored properties, then hands control to its superclass. Until the top of the chain is reached, you can’t call methods, read properties, or use
selfas a value. - Phase 2, customizing. Once every stored property in the whole chain has a value, control comes back down, and each initializer may use
selffreely: call methods, set inherited properties, and so on.
class RecipeIngredient: Food {
let quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity // phase 1: own properties first
super.init(name: name) // then up the chain
print("Created \(self.name)") // phase 2: self is fully usable
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
The compiler enforces the order with errors that are worth recognizing:
- Setting
super.initbefore your own properties: “property ‘self.quantity’ not initialized at super.init call”. - Calling a method before
super.init: “‘self’ used in method call ‘setup’ before ‘super.init’ call”.
Why the strictness? In other languages, a superclass initializer can call an overridden method that reads a subclass property before the subclass set it, a classic source of bugs. Two-phase initialization makes that impossible.
Initializer inheritance
Subclasses don’t inherit their superclass’s initializers by default. If they did, a subclass with new stored properties could be created through an inherited initializer that knows nothing about them, leaving them uninitialized. Two rules bring initializers back automatically:
- If a subclass defines no designated initializers of its own (and all its new properties have defaults), it inherits all of its superclass’s designated initializers.
- If a subclass provides all of its superclass’s designated initializers, either through rule 1 or by overriding them, it also inherits all of the superclass’s convenience initializers.
class ShoppingItem: RecipeIngredient {
var purchased = false // has a default, and no designated init of its own
}
let eggs = ShoppingItem(name: "Eggs", quantity: 6) // inherited designated init
let milk = ShoppingItem(name: "Milk") // inherited convenience-style init
let unknown = ShoppingItem() // inherited from Food, via rule 2
print(milk.quantity, unknown.name) // 1 [Unnamed]
Required initializers
required init forces every subclass to implement that initializer. Frameworks use it when they need to create instances of subclasses they’ve never seen. The most famous one is in UIKit:
class Shape {
let sides: Int
required init(sides: Int) { self.sides = sides }
}
class Polygon: Shape {
let label: String
init(label: String, sides: Int) {
self.label = label
super.init(sides: sides)
}
required init(sides: Int) { // must be implemented, and marked `required` again
label = "Polygon"
super.init(sides: sides)
}
}
That’s why every UIView subclass with a custom initializer must also implement required init?(coder:), the initializer storyboards use. If you never load the view from a storyboard, the usual implementation is fatalError("init(coder:) has not been implemented").
Setting a property with a closure
For setup that needs a few lines, initialize a property with a closure that runs immediately. Note the () at the end:
struct Chessboard {
let squares: [Bool] = {
var board: [Bool] = []
var isBlack = false
for _ in 1...8 {
for _ in 1...8 {
board.append(isBlack)
isBlack.toggle()
}
isBlack.toggle()
}
return board
}()
func squareIsBlack(row: Int, column: Int) -> Bool {
squares[row * 8 + column]
}
}
print(Chessboard().squareIsBlack(row: 7, column: 7)) // false
The closure runs before the instance exists, so it can’t use self or other properties. If you need them, use a lazy var instead.
Initializers or factory methods?
Static factory methods are sometimes clearer than another initializer, because they have a name:
extension Rect {
static func square(side: Double) -> Rect {
Rect(originX: 0, originY: 0, width: side, height: side)
}
}
let tile = Rect.square(side: 32)
Use initializers for the main ways to construct a value, and named factory methods when a construction has a specific meaning (“square”, “placeholder”, “fromCache”) that labels alone don’t capture.
Common pitfalls
- Losing the memberwise initializer by adding an
initto the struct body. Move the custom one to an extension. - Private properties without defaults that make the memberwise initializer private.
- Doing heavy work in initializers, like network calls. Initializers should set up state, quickly and predictably. Start work in a method.
- Forgetting
required init?(coder:)in UIKit subclasses. init!(implicitly unwrapped failable initializers): they exist mostly for Objective-C interop. In Swift code, preferinit?orthrows.
Interview questions
What is two-phase initialization and why does Swift use it? Phase 1 initializes every stored property, from subclass up to the root class; phase 2 lets initializers customize the instance. It prevents methods from running on partially initialized objects.
What’s the difference between designated and convenience initializers? Designated initializers fully initialize the class and call a superclass designated initializer (delegate up). Convenience initializers must call another initializer of the same class (delegate across).
Why don’t subclasses inherit initializers by default? An inherited initializer wouldn’t know about the subclass’s new stored properties, which could leave them uninitialized. Swift only inherits initializers when that’s provably safe.
How do you keep a struct’s memberwise initializer while adding a custom one? Define the custom initializer in an extension.
Summary
- Every stored property needs a value before an instance is used;
letproperties are set exactly once. - Structs get memberwise initializers (with default arguments), unless you define your own
initin the body or have private properties without defaults. init?andthrowshandle invalid input, and make invalid states unrepresentable.- Classes use designated (up) and convenience (across) initializers, two-phase initialization, and explicit inheritance rules;
requiredforces subclasses to implement an initializer.