Methods & Initializers

Reviewed & published by Brayan K

Functions that live inside your types become methods. In this lesson you'll write instance and type methods, use mutating on structs, master self, and set up new values with designated and convenience initializers.

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️⃣ Instance Methods

An instance method is a function written inside a type. It can read the instance's properties directly. Because a struct is a value type, any method that changes a property must be marked mutating — Swift's way of making mutation explicit.

// An instance method is a function that lives inside a type.
struct Counter {
    var count = 0

    // A normal method can READ properties freely.
    func describe() -> String {
        return "count is \(count)"
    }

    // Because Counter is a value type (a struct), a method that CHANGES a
    // property must be marked 'mutating'.
    mutating func increment() {
        count += 1
    }
}

var c = Counter()
print(c.describe())   // count is 0
c.increment()
c.increment()
print(c.describe())   // count is 2

2️⃣ The self Keyword

self refers to the current instance. You only need it when a local name (often an init parameter) would otherwise hide a property of the same name. Elsewhere it's optional.

// 'self' refers to the current instance. It's needed when a parameter name
// would otherwise shadow (hide) a property of the same name.
struct Player {
    var name: String

    // Without self, 'name = name' would assign the parameter to itself.
    init(name: String) {
        self.name = name
    }

    func greet() -> String {
        // Here 'name' is unambiguous, so self is optional but allowed.
        return "Hi, I'm \(self.name)"
    }
}

let p = Player(name: "Ada")
print(p.greet())   // Hi, I'm Ada

3️⃣ Type Methods

A type method belongs to the type itself, not an instance. Mark it static and call it on the type, like Temperature.fromFahrenheit(212). These are perfect for factory helpers and shared constants.

// A TYPE method belongs to the type, not to any single instance.
// Use 'static' for structs/enums (and for non-overridable class methods).
struct Temperature {
    var celsius: Double

    // Called on the TYPE: Temperature.freezing
    static let freezing = Temperature(celsius: 0)

    // A type method — note it's called on the type, not an instance.
    static func fromFahrenheit(_ f: Double) -> Temperature {
        return Temperature(celsius: (f - 32) * 5 / 9)
    }
}

let boiling = Temperature.fromFahrenheit(212)
print("boiling = \(boiling.celsius)°C")   // boiling = 100.0°C
print("freezing = \(Temperature.freezing.celsius)°C")

4️⃣ Initializers: Designated vs Convenience

An initializer prepares a new instance by giving every stored property a value. In a class a designated init fully sets up the object, while a convenience init is a helper that must delegate to another init in the same class with self.init(...).

// Classes use 'designated' and 'convenience' initializers.
class Vehicle {
    var wheels: Int
    var name: String

    // DESIGNATED init — fully sets every stored property.
    init(name: String, wheels: Int) {
        self.name = name
        self.wheels = wheels
    }

    // CONVENIENCE init — must call another init in the SAME class (self.init).
    convenience init(name: String) {
        self.init(name: name, wheels: 4)   // a sensible default
    }
}

let bike = Vehicle(name: "Bike", wheels: 2)
let car = Vehicle(name: "Car")            // uses the convenience init
print("\(bike.name): \(bike.wheels) wheels")   // Bike: 2 wheels
print("\(car.name): \(car.wheels) wheels")     // Car: 4 wheels

Your turn. Fill in the blank so the deposit method can change the balance.

// 🎯 YOUR TURN — fill in the blanks, then run it in SwiftFiddle.
struct BankAccount {
    var balance: Double

    // 1) This method changes 'balance', so it needs a keyword. Add it:
    ___ func deposit(_ amount: Double) {   // 👉 replace ___ with 'mutating'
        balance += amount
    }

    func describe() -> String {
        return "Balance: \(balance)"
    }
}

var acct = BankAccount(balance: 100)
acct.deposit(50)
print(acct.describe())   // Balance: 150.0

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCodeNote
Instance methodfunc go() { … }on an instance
Mutatingmutating func add() { … }struct changes self
Type methodstatic func make() { … }on the type
Designated initinit(x: Int) { … }sets all props
Convenience initconvenience init() { self.init(...) }delegates

Mini-Challenge: Thermostat

No blanks this time — just a brief and an outline. Build it, run it, and check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: a Thermostat
// 1. Make a struct Thermostat with a 'var temperature: Double'.
// 2. Add an initializer that takes a starting temperature.
// 3. Add a 'mutating' method warmUp() that adds 1.0 to temperature.
// 4. Add a type method 'comfortable()' (static) that returns a
//    Thermostat set to 21.0.
// 5. Create one with comfortable(), call warmUp() twice, and print it.
//
// ✅ Expected output:
//    Temperature: 23.0

// your code here

🎉 Lesson Complete!

Practice quiz

What is a method in Swift?

  • A stored property
  • A global constant
  • A function that belongs to a type
  • A protocol

Answer: A function that belongs to a type. A method is simply a function defined inside a type such as a struct, class, or enum.

What does the 'self' keyword refer to inside an instance method?

  • The current instance the method is called on
  • The type itself
  • The superclass
  • A new copy of the instance

Answer: The current instance the method is called on. Inside an instance method, 'self' refers to the specific instance the method was called on.

What keyword marks a method that belongs to the TYPE rather than an instance?

  • instance
  • class only
  • shared
  • static

Answer: static. The 'static' keyword (or 'class' in a class, to allow overriding) defines a type method.

What is the purpose of an initializer (init)?

  • To deinitialize an object
  • To set up a new instance's stored properties
  • To copy an instance
  • To compare two instances

Answer: To set up a new instance's stored properties. An initializer prepares a new instance by giving every stored property an initial value.

Why must an instance method on a STRUCT be marked 'mutating' to change a property?

  • Structs are value types, so methods can't change them by default
  • Structs are reference types
  • Mutating is required on every struct method
  • It improves performance

Answer: Structs are value types, so methods can't change them by default. Structs are value types; a method must be marked 'mutating' to modify the instance's properties.

What is a convenience initializer in a class?

  • An init that fully initializes all properties directly
  • An init with no parameters
  • A secondary init that must call another init in the same class
  • An init that can't be overridden

Answer: A secondary init that must call another init in the same class. A convenience initializer must call another initializer (designated or convenience) in the same class.

What must a designated initializer of a class do?

  • Call a convenience init
  • Fully initialize all stored properties introduced by its class
  • Always have parameters
  • Return self

Answer: Fully initialize all stored properties introduced by its class. A designated initializer is responsible for fully initializing all stored properties of its class.

When is 'self' required (not optional) inside an initializer?

  • Never
  • Only in classes
  • Only for computed properties
  • When a parameter name shadows a property name

Answer: When a parameter name shadows a property name. When a parameter has the same name as a property, you write self.name = name to disambiguate.

How do you call a type method named 'create' on a type named Robot?

  • create()
  • Robot.create()
  • self.create()
  • Robot().create()

Answer: Robot.create(). Type methods are called on the type itself: Robot.create().

What keyword (instead of 'static') lets a class type method be overridden by subclasses?

  • override
  • virtual
  • class
  • open

Answer: class. Using 'class func' instead of 'static func' allows subclasses to override the type method.

Continue this course

Frequently asked questions

What is the difference between a function and a method in Swift?

There is no syntactic difference — a method is just a function that is defined inside a type (struct, class, or enum). The only practical difference is scope: a method can access the instance's properties and other methods through self, whereas a free function cannot.

Why do struct methods sometimes need the 'mutating' keyword?

Structs are value types. By default Swift treats the instance inside a method as a constant, so it cannot be changed. Marking the method 'mutating' tells Swift the method may modify the instance's stored properties. Classes are reference types and never need 'mutating'.

What is the difference between a designated and a convenience initializer?

A designated initializer is the primary init: it must fully initialize every stored property the class introduces. A convenience initializer is a secondary helper that must call another initializer in the same class (with self.init) — it can supply defaults or transform inputs, but it can't initialize properties directly first.

When do I have to write self in Swift?

You only need self when a local name shadows a property — most commonly when an initializer parameter has the same name as a property (self.name = name). Otherwise self is optional inside instance methods, though some teams write it for clarity.

What is the difference between static func and class func?

Both define type methods called on the type rather than an instance. 'static func' cannot be overridden by subclasses. 'class func' (only available in classes) can be overridden, which is occasionally useful for polymorphic factory methods.