`init` in Swift: Initializers, Convenience Inits, and Deinit


Picture the toy factory in Toy Story 2 — before any toy rolls off the assembly line, every single piece must be in place. Arms attached, eyes painted, voice box installed. If even one part is missing, the toy doesn’t ship. Swift’s initialization system works the same way: the compiler guarantees that every stored property has a value before anyone can use the instance.

This guide covers designated initializers, memberwise initializers, convenience initializers, failable initializers, and deinit. We won’t get into protocol initializer requirements or codable init — those topics have their own dedicated posts.

What You’ll Learn

What Is Initialization?

Initialization is the process of preparing an instance of a type for use. It ensures that every stored property gets an initial value before the instance is accessed for the first time.

Swift enforces this at compile time — if you forget to set a property, your code won’t even build. There’s no risk of accidentally reading garbage data from an uninitialized field.

Think of it like the quality control line at a Pixar toy factory: the toy cannot leave the factory until every part is accounted for. The compiler is the inspector who refuses to let anything through until everything checks out.

Apple Docs: Initialization — The Swift Programming Language

Designated Initializers

A designated initializer is the primary initializer for a type. It must set all stored properties that don’t have default values, making the instance fully ready to use.

Let’s build a toy on the factory line:

struct Toy {
    let name: String
    let material: String
    var batteryLevel: Int

    init(name: String, material: String, batteryLevel: Int) {
        self.name = name
        self.material = material
        self.batteryLevel = batteryLevel
    }
}

let woody = Toy(name: "Woody", material: "cloth", batteryLevel: 100)
print("\(woody.name) is made of \(woody.material)")
Woody is made of cloth

The init sets all three stored properties. After initialization, woody is complete — every property has a value.

Classes and super.init

When a class inherits from another class, the subclass’s designated initializer must call super.init to ensure the parent’s properties are also initialized:

class FactoryProduct {
    let serialNumber: Int

    init(serialNumber: Int) {
        self.serialNumber = serialNumber
    }
}

class ActionFigure: FactoryProduct {
    let characterName: String

    init(characterName: String, serialNumber: Int) {
        self.characterName = characterName
        super.init(serialNumber: serialNumber)
    }
}

let buzz = ActionFigure(characterName: "Buzz", serialNumber: 1995)
print("\(buzz.characterName) — Serial #\(buzz.serialNumber)")
Buzz — Serial #1995

Notice the order: the subclass sets its own properties first (characterName), then calls super.init to let the parent class handle its properties. Swift requires this order — all your properties must be set before calling super.init.

Warning: Forgetting to call super.init in a subclass designated initializer is a compiler error. Swift won’t let you skip it.

Memberwise Initializer (Structs)

Here’s a nice shortcut: Swift automatically generates a memberwise initializer for structs. You don’t have to write any init code at all.

struct Toy {
    let name: String
    let material: String
    var batteryLevel: Int
}

// Swift generates this for free:
let rex = Toy(name: "Rex", material: "plastic", batteryLevel: 75)
print("\(rex.name) has \(rex.batteryLevel)% battery")
Rex has 75% battery

Swift looked at the struct’s properties and generated init(name:material:batteryLevel:) automatically. Each parameter matches a stored property by name and type.

Note: The memberwise initializer is only generated for structs, not classes. And if you define your own custom init, the automatic memberwise initializer goes away. To keep both, define your custom init in an extension instead.

Default values reduce parameters

If a stored property has a default value, the memberwise initializer makes that parameter optional:

struct Toy {
    let name: String
    let material: String
    var batteryLevel: Int = 100  // default value
}

// Both of these work:
let slinky = Toy(name: "Slinky", material: "metal")
let hamm = Toy(name: "Hamm", material: "plastic", batteryLevel: 50)
print(slinky.batteryLevel)
print(hamm.batteryLevel)
100
50

slinky uses the default battery level of 100, while hamm overrides it with 50.

Convenience Initializers

A convenience initializer is a secondary initializer that provides a shortcut for common setup patterns. It must call a designated initializer from the same class using self.init.

Convenience initializers are class-only — structs don’t use the convenience keyword.

class Toy {
    let name: String
    let material: String
    var batteryLevel: Int

    // Designated initializer — sets all properties
    init(name: String, material: String, batteryLevel: Int) {
        self.name = name
        self.material = material
        self.batteryLevel = batteryLevel
    }

    // Convenience initializer — shorthand for common case
    convenience init(name: String) {
        self.init(name: name, material: "plastic", batteryLevel: 100)
    }
}

let alien = Toy(name: "Little Green Alien")
print("\(alien.name)\(alien.material), \(alien.batteryLevel)%")
Little Green Alien — plastic, 100%

The convenience init provides sensible defaults for material and batteryLevel, making it easy to create a toy with just a name. Under the hood, it delegates to the designated initializer, which does the actual property assignment.

Tip: Think of convenience initializers as shortcuts. They always end up calling a designated initializer — they can never set properties directly.

Failable Initializers

Sometimes initialization should fail if the input data is invalid. A failable initializer is declared with init? and returns an optionalnil if initialization fails.

Imagine the factory quality check rejects any toy with an empty name:

struct Toy {
    let name: String
    let material: String

    init?(name: String, material: String) {
        guard !name.isEmpty else {
            return nil  // Factory rejects this toy
        }
        self.name = name
        self.material = material
    }
}

let woody = Toy(name: "Woody", material: "cloth")
let defective = Toy(name: "", material: "plastic")

print(woody?.name ?? "No toy")
print(defective?.name ?? "No toy")
Woody
No toy

Toy(name: "", material: "plastic") returns nil because the name is empty. The result type is Toy? (an optional), so callers are forced to handle the possibility that creation might fail.

Note: Failable initializers are great for parsing user input, decoding data from a network response, or any scenario where you can’t guarantee the input is valid.

deinit

A deinitializer is the opposite of an initializer — it’s called automatically just before a class instance is deallocated from memory. You use it for cleanup: closing files, canceling timers, or releasing resources.

Think of it as recycling a toy at the factory. Before the toy is melted down, you remove the batteries and log the retirement:

class Toy {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) assembled on the factory line!")
    }

    deinit {
        print("\(name) has been recycled. Goodbye!")
    }
}

func buildAndRetireToy() {
    let toy = Toy(name: "Wheezy")
    print("\(toy.name) is playing...")
    // toy goes out of scope here and gets deallocated
}

buildAndRetireToy()
Wheezy assembled on the factory line!
Wheezy is playing...
Wheezy has been recycled. Goodbye!

When toy goes out of scope at the end of buildAndRetireToy(), Swift automatically calls its deinit. This is where you handle any last-minute cleanup.

Warning: Only classes have deinitializers. Structs and enums are value types and don’t use deinit. Also, you never call deinit yourself — Swift handles it automatically through memory management.

Common Mistakes

Forgetting to initialize a stored property

If you forget to give a stored property a value in your initializer, Swift catches it at compile time:

class Toy {
    let name: String
    let material: String

    // ❌ Error: 'self.material' not initialized
    init(name: String) {
        self.name = name
        // Forgot to set self.material!
    }
}

The fix is to set every stored property, or give it a default value:

class Toy {
    let name: String
    let material: String

    // ✅ All properties initialized
    init(name: String, material: String) {
        self.name = name
        self.material = material
    }
}

Not calling super.init in a subclass

Every subclass designated initializer must call super.init to initialize the parent class’s properties:

class FactoryProduct {
    let id: Int
    init(id: Int) { self.id = id }
}

class ActionFigure: FactoryProduct {
    let heroName: String

    // ❌ Error: 'super.init' isn't called on all paths
    init(heroName: String) {
        self.heroName = heroName
        // Forgot super.init!
    }
}

The fix is to call super.init after setting your own properties:

class ActionFigure: FactoryProduct {
    let heroName: String

    // ✅ Call super.init after setting own properties
    init(heroName: String, id: Int) {
        self.heroName = heroName
        super.init(id: id)
    }
}

Remember the order: set your own properties first, then call super.init.

What’s Next?

  • Initialization ensures all stored properties have values before first use.
  • Designated initializers are the primary init, responsible for setting all properties.
  • Memberwise initializers are auto-generated for structs — no code needed.
  • Convenience initializers are class-only shortcuts that delegate to a designated init.
  • Failable initializers (init?) return nil when input is invalid.
  • deinit handles cleanup before a class instance is deallocated.

You now understand how Swift types are born (and retired). Next, let’s explore how types can make promises about their capabilities. Head over to Protocols in Swift to learn how to define shared contracts that any type can adopt.