Methods: instance, static, and mutating
Learning intentions
In this lesson, you will learn how methods belong to a type and how that changes the way you design reusable code.
- Explain what a method is and why it is attached to a type,
- compare methods with open functions that are written outside any type,
- and choose the right approach based on what data the code needs to access.
You will also practise two important method keywords in Swift.
- Explain
staticmethods for type-level behaviour, - and explain
mutatingmethods for safely changing struct state.
What is a method?
A method is a function written inside a type such as a struct, class, or enum, so it can work directly with that type’s data and purpose.
- Methods live inside the model definition instead of being separate global code,
- so behaviour stays close to related properties and intent is easier to read.
struct Player {
var name: String
var score: Int
func summary() -> String {
return "\(name) has \(score) points."
}
}
What is an open function?
An open function is a standalone function written outside a type, and it can still work with model data when that data is passed in as parameters.
- Open functions are called directly by name,
- and they receive all context through parameters such as arrays and struct instances.
struct Player {
var name: String
var score: Int
}
func averageScore(from scores: [Int]) -> Double {
guard !scores.isEmpty else { return 0 }
return Double(scores.reduce(0, +)) / Double(scores.count)
}
func playerSummary(for player: Player) -> String {
return "\(player.name) has \(player.score) points."
}
let scores = [8, 12, 15, 9]
let p = Player(name: "Ari", score: 12)
print(averageScore(from: scores))
print(playerSummary(for: p))
Open functions are useful when behaviour is generic and not clearly owned by one type or when you want lightweight utility logic.
How methods can help
The same behaviour can often be moved into a type as methods, which makes usage cleaner because the value provides its own context.
- Methods reduce repeated parameter passing,
- and make behaviour easier to discover next to the data it belongs to.
struct ScoreBoard {
var scores: [Int]
func averageScore() -> Double {
guard !scores.isEmpty else { return 0 }
return Double(scores.reduce(0, +)) / Double(scores.count)
}
}
struct Player {
var name: String
var score: Int
func summary() -> String {
return "\(name) has \(score) points."
}
}
let board = ScoreBoard(scores: [8, 12, 15, 9])
let p = Player(name: "Ari", score: 12)
print(board.averageScore())
print(p.summary())
With methods, each model owns behaviour that belongs to it, so your API reads more naturally and scales better as projects grow.
Instance methods
An instance method is the most common method type, and it runs on one specific instance of a type.
- Call instance methods with dot syntax like
player.summary(), - and use them to read or compute from that instance’s properties.
Instance methods are ideal when behaviour depends on one object’s current state such as score, health, balance, or progress.
Static methods
A static method belongs to the type itself, not to any single instance, so you call it using the type name.
- Use
TypeName.methodName()syntax for shared logic, - and choose
staticwhen behaviour does not need per-instance state.
struct ScoreRules {
static func bonus(for streak: Int) -> Int {
return streak >= 3 ? 10 : 0
}
}
let extra = ScoreRules.bonus(for: 4)
Static methods are good for helpers, validation, and factory-style setup logic where instance data is not required.
Mutating methods
Structs are value types, so methods that change stored properties must be marked with mutating.
mutatingtells Swift the method will change instance state,- so assignments like
score += 1are allowed inside that method.
struct BankAccount {
var owner: String
var balance: Double
mutating func deposit(_ amount: Double) {
balance += amount
}
}
Without mutating, Swift blocks property changes inside struct methods to protect value semantics and avoid accidental mutation.
Computed properties
Computed properties return a value calculated from other data, rather than storing that value directly in memory.
- Use them when a value should always stay derived from current state,
- and avoid duplicate stored data that can go out of sync.
struct Player {
var name: String
var score: Int
var description: String {
"\(name) has \(score) points."
}
}
A computed property getter is closure-like because it is a block of code that runs each time the property is accessed.
- It can read
selfand build a result on demand, - but it is not literally a closure value being passed around like
{ value in value * 2 }.
You can think of it as “method-like syntax with property-style access” which keeps call sites clean, for example print(player.description), skipping the () brackets.
Choosing between open functions and methods
Choose a method when behaviour clearly belongs to a model, and choose an open function when behaviour is generic and not owned by one type.
- Ask: “Does this action belong to this model?” if yes, prefer a method,
- ask: “Could this work for many unrelated types?” if yes, an open function may be better.
This decision improves readability and architecture, because related behaviour stays discoverable where developers expect to find it.
Task A: convert open functions to methods
- Create a struct called
Bookwith propertiestitle,author, andpages. - Start by writing an open function
bookSummary(title:author:pages:) -> String. - Then move that behaviour into an instance method
summary() -> StringinsideBook. - Create two
Bookinstances and print both summaries using the method version. - Write one sentence explaining why the method version is clearer for this model.
Task B: static method challenge
- Create a struct called
Temperature. - Add two static methods:
toFahrenheit(celsius: Double) -> DoubletoCelsius(fahrenheit: Double) -> Double
- Call both methods using type syntax (for example
Temperature.toFahrenheit(celsius: 22)). - Print clear output lines for at least three conversions.
- Explain why these are better as
staticmethods than instance methods.
Task C: mutating method challenge
- Create a struct called
Timerwith propertiessecondsandisRunning. - Add a
mutatingmethodstart()that setsisRunningtotrue. - Add a
mutatingmethodtick()that increasessecondsby1only whenisRunningistrue. - Add a
mutatingmethodreset()that setssecondsto0andisRunningtofalse. - Test your struct by calling the methods in sequence and printing values after each step.
Task D: mixed design decision
- Build a struct
Cartwith propertyitemsCountstarting at0. - Add a type-level rule with
static let freeShippingThreshold = 5. - Add a
mutatingmethodaddItem()that incrementsitemsCountby1. - Add an instance method
shippingMessage() -> Stringthat usesitemsCountand returns:"Free shipping"whenitemsCount >= Cart.freeShippingThreshold"Shipping applies"otherwise
- Add a static helper
qualifiesForFreeShipping(count: Int) -> Boolso the shipping rule can be reused in other places (for example previews, reports, or tests without aCartinstance). - Create one
Cartinstance and calladdItem()in a loop. - After each add, print both
cart.itemsCountandcart.shippingMessage(). - In comments, explain why
addItem()andshippingMessage()are instance behaviour, whilefreeShippingThresholdandqualifiesForFreeShippingare type-level behaviour.
Task E: method to computed property
- Create a struct called
Badgewith propertiesnameandlevel. - Add an instance method
label() -> Stringthat returns text like"Ranger - Level 4". - Create at least one
Badgeinstance and print the method result. - Convert
label()into a computed propertylabel: String. - Print the new computed property and confirm the output is the same.
Extension for Super Players!
Take one extra step to strengthen your design choices with computed properties.
- Add at least one computed property to each of Tasks A through D,
- or convert one existing method from any task into a computed property and explain why that change improves readability.
Summary
Methods help you keep behaviour close to the data it belongs to, while open functions remain useful for shared logic outside specific models.
- Instance methods work with one value’s state,
- static methods provide type-level behaviour without needing an instance,
- and mutating methods allow safe state changes in structs when mutation is intentional.
Designing with these tools makes code easier to understand, test, and maintain especially as projects grow.
Review
Use these prompts to check your understanding.
- Explain the difference between an open function and a method in your own words,
- describe when a
staticmethod is the best choice, - describe why structs need the
mutatingkeyword for state changes, - and write one example where you would prefer an open function over a method.