Methods and Subscripts in Swift: Instance, Type, and Mutating
On the Scare Floor at Monsters, Inc., every monster has their own signature move — Sulley roars, Mike cracks jokes, Randall turns invisible. But some rules apply to the whole company: the daily scare quota, the all-hands meeting protocol. In Swift, methods are functions that belong to a type, and they follow the same split — some act on individual instances, others operate at the type level.
This guide covers instance methods, mutating methods, type methods, and subscripts. We won’t get into protocol method requirements or extensions — those each have their own posts.
What You’ll Learn
- What Are Methods?
- Instance Methods
- Mutating Methods
- Type Methods
- Subscripts
- Common Mistakes
- What’s Next?
What Are Methods?
A method is simply a function that’s defined inside a type — a struct, class, or
enum. The key difference between a method and a regular function is that methods have access to self, a reference to
the instance they belong to.
Think of it this way: a regular function is a freelancer — it can work anywhere. A method is an employee — it’s tied to a specific company (type) and has access to the company’s internal data (properties).
Apple Docs:
Methods— The Swift Programming Language
Instance Methods
An instance method is a function that operates on a specific instance of a type. It can read and (sometimes) modify
the instance’s properties through self.
Let’s build a Monster struct for the Scare Floor:
struct Monster {
let name: String
var scareCount: Int
func roar() {
print("\(name) lets out a terrifying roar!")
}
func report() {
print("\(name) has \(scareCount) scares.")
}
}
let sulley = Monster(name: "Sulley", scareCount: 42)
sulley.roar()
sulley.report()
Sulley lets out a terrifying roar!
Sulley has 42 scares.
Both roar() and report() are instance methods. They access the instance’s name and scareCount properties through
self. Swift lets you omit self when the meaning is clear, so name is shorthand for self.name.
Tip: You only need to write
selfexplicitly when a parameter name conflicts with a property name, or inside closures that captureself.
Mutating Methods
Structs are value types, which means their properties are immutable by default inside methods. If you want a method
to modify a property, you must mark it as mutating.
Think of it as asking permission to change the scoreboard — regular employees can read it, but only a manager (a
mutating method) can update the numbers:
struct Monster {
let name: String
var scareCount: Int
mutating func performScare() {
scareCount += 1
print("\(name) scared a kid! Total: \(scareCount)")
}
}
var sulley = Monster(name: "Sulley", scareCount: 42)
sulley.performScare()
sulley.performScare()
Sulley scared a kid! Total: 43
Sulley scared a kid! Total: 44
The mutating keyword tells Swift that this method will modify the struct’s properties. Without it, the compiler would
reject the scareCount += 1 line.
Note: Classes don’t need
mutatingbecause they’re reference types — their methods can always modify properties. Themutatingkeyword is specific to structs and enums.
Assigning to self
A mutating method can even replace the entire instance by assigning a new value to self:
struct ScareRecord {
var topScore: Int
mutating func reset() {
self = ScareRecord(topScore: 0)
}
}
var record = ScareRecord(topScore: 99)
record.reset()
print(record.topScore)
0
This creates an entirely new ScareRecord and replaces the current one. It’s useful when you want a clean-slate reset.
Type Methods
A type method belongs to the type itself, not to any particular instance. You call it on the type name, not on an
instance. Declare one with static:
struct Monster {
static var totalMonsters = 0
let name: String
init(name: String) {
self.name = name
Monster.totalMonsters += 1
}
static func headCount() {
print("Monsters on staff: \(totalMonsters)")
}
}
let sulley = Monster(name: "Sulley")
let mike = Monster(name: "Mike")
Monster.headCount()
Monsters on staff: 2
headCount() is called on Monster itself — not on sulley or mike. It accesses the type property totalMonsters
directly because type methods can access other type properties.
static vs class
In classes, you have two options for type methods:
static func— cannot be overridden by subclasses.class func— can be overridden by subclasses.
class Employee {
class func companyMotto() -> String {
"We scare because we care."
}
}
class TopScarer: Employee {
override class func companyMotto() -> String {
"We scare because we're the best."
}
}
print(Employee.companyMotto())
print(TopScarer.companyMotto())
We scare because we care.
We scare because we're the best.
If companyMotto() were declared with static, the override in TopScarer would cause a compiler error. Use
class func when you want subclasses to customize behavior.
Tip: Structs and enums only support
static— they can’t be subclassed, soclass funcdoesn’t apply to them.
Subscripts
A subscript lets you access elements of a type using bracket syntax — just like you access array elements with
array[0]. You can define custom subscripts to give your types the same convenient bracket access.
Imagine a Scare Floor scoreboard where you look up a monster’s score by their position:
struct ScoreBoard {
let scores: [String: Int]
subscript(monster: String) -> Int {
scores[monster] ?? 0
}
}
let board = ScoreBoard(scores: [
"Sulley": 42,
"Mike": 15,
"Randall": 39
])
print(board["Sulley"])
print(board["Roz"])
42
0
The subscript takes a String key and returns the matching score, or 0 if the monster isn’t on the board. Callers use
the familiar bracket syntax board["Sulley"] instead of calling a method.
Subscripts with integer indices
You can also define subscripts that take integers, making your custom type behave like an array:
struct ScareLine {
let monsters: [String]
subscript(index: Int) -> String {
monsters[index]
}
}
let line = ScareLine(monsters: ["Sulley", "Mike", "Randall"])
print(line[0])
print(line[2])
Sulley
Randall
Note: Subscripts can be read-write (with
getandsetblocks) or read-only (just a single return expression), just like computed properties.
Common Mistakes
Forgetting mutating on a struct method
If a struct method modifies a property without the mutating keyword, the compiler rejects it:
struct Monster {
var scareCount: Int
// ❌ Error: cannot assign to property: 'self' is immutable
func performScare() {
scareCount += 1
}
}
The fix is simple — add mutating:
struct Monster {
var scareCount: Int
// ✅ Mark the method as mutating
mutating func performScare() {
scareCount += 1
}
}
Confusing static func vs class func
Beginners sometimes use static func in a class and then wonder why they can’t override it in a subclass:
class Employee {
// ❌ Using static means subclasses cannot override this
static func motto() -> String {
"We scare because we care."
}
}
class TopScarer: Employee {
// Error: cannot override static method
override static func motto() -> String {
"We're the best."
}
}
If you need subclasses to override the method, use class func instead:
class Employee {
// ✅ Use class func to allow overrides
class func motto() -> String {
"We scare because we care."
}
}
The rule is straightforward: static = final (no overrides), class = overridable.
What’s Next?
- Instance methods are functions bound to a type that can access
self. - Mutating methods let struct methods modify properties — required for value types.
- Type methods (
static func/class func) belong to the type, not an instance. - Subscripts let you define bracket-access syntax for your custom types.
You’ve learned how to attach both data (properties) and behavior (methods) to your types. Next, let’s look at how those
types come to life in the first place. Head over to init in Swift to learn about
initializers, convenience inits, and deinit.