Swift Type Properties and Methods: static vs class Explained
Swift type properties and type methods let you attach data and behavior to the type itself instead of to individual instances. This matters whenever you need shared values, counters, factory-style helpers, configuration defaults, or functionality that belongs to a class, struct, or enum as a whole rather than to one object.
Quick answer: Use static to create type properties and type methods that belong to the type itself. In classes, use class for type methods and computed type properties when you want subclasses to be able to override them; static cannot be overridden.
Difficulty: Beginner
Helpful to know first: You'll understand this better if you know basic Swift syntax, how classes and structs are defined, and how instance properties and instance methods work.
1. What Is Swift Type Properties and Methods?
A type property or type method belongs to the type itself, not to any one instance created from that type.
- An instance property belongs to one specific object or value.
- A type property belongs to the class, struct, or enum itself.
- An instance method is called on an instance.
- A type method is called on the type name.
- Use static for type members on structs, enums, and classes.
- Use class only in classes, and only when you want an overridable type member.
For example, if every bank account should have its own balance, that is an instance property. But if the bank wants to track the total number of accounts created, that value belongs to the type, so a type property is a better fit.
A common confusion is static vs class. Both create type-level members in classes, but only class supports overriding in subclasses.
2. Why Type Properties and Methods Matters
Type-level members help you model information and behavior that should be shared across all instances.
They are useful when:
- You need a shared constant such as a default tax rate or app-wide configuration value.
- You want to count how many instances have been created.
- You want helper behavior that does not depend on instance data.
- You need a factory-style method that creates instances in a standard way.
- You want subclasses to customize shared behavior using an overridable type method.
They are not the best choice when the data should be different for each instance. If each object needs its own value, use an instance property instead.
3. Basic Syntax or Core Idea
Start with the smallest useful examples. First, here is a type property and type method on a struct using static.
Declaring a static type property
This property belongs to GameSettings itself, so you access it with the type name.
struct GameSettings {
static let maxPlayers = 4
}
print(GameSettings.maxPlayers)This prints 4. No instance of GameSettings is needed.
Declaring a static type method
This method also belongs to the type, not an instance.
struct MathHelper {
static func square(_ number: Int) -> Int {
return number * number
}
}
let result = MathHelper.square(6)
print(result)This prints 36. Again, no instance is required.
Using class for overridable type members
In classes, class can be used for type methods and computed type properties that subclasses may override.
class Vehicle {
class var description: String {
"Generic vehicle"
}
class func category() -> String {
"Transport"
}
}This syntax is useful when subclasses should be allowed to provide a different implementation.
4. Step-by-Step Examples
Example 1: Shared app configuration
Use a type property when a value should be shared everywhere.
struct AppConfig {
static let apiBaseURL = "https://api.example.com"
}
print(AppConfig.apiBaseURL)This keeps the configuration value in one place and avoids repeating the same string in many files.
Example 2: Counting instances
A type property can track information shared by all instances.
class User {
static var count = 0
let name: String
init(name: String) {
self.name = name
User.count += 1
}
}
let user1 = User(name: "Ava")
let user2 = User(name: "Noah")
print(User.count)This prints 2. The counter is shared by all User instances.
Example 3: A utility type method
Type methods are useful for logic that does not need instance data.
struct Temperature {
static func celsiusToFahrenheit(_ celsius: Double) -> Double {
return (celsius * 9 / 5) + 32
}
}
let fahrenheit = Temperature.celsiusToFahrenheit(25)
print(fahrenheit)This is a good fit because conversion does not depend on a stored instance.
Example 4: Overridable class method
Use class when subclasses should customize type-level behavior.
class Notification {
class func defaultSound() -> String {
"default.wav"
}
}
class UrgentNotification: Notification {
override class func defaultSound() -> String {
"alarm.wav"
}
}
print(Notification.defaultSound())
print(UrgentNotification.defaultSound())The base class and subclass can now provide different type-level behavior.
5. Practical Use Cases
- Defining shared constants such as API endpoints, app version strings, or default limits.
- Tracking global counters, such as the number of created objects or active sessions.
- Creating helper functions like formatters, converters, validators, or factory methods.
- Providing default behavior for a class hierarchy that subclasses can override with class methods.
- Grouping logic under a type name so related functionality is easier to discover and maintain.
- Storing cached values that should be shared by all instances of a type.
6. Common Mistakes
Mistake 1: Accessing a type property through an instance
Beginners often try to read a type property from an instance because instance properties are accessed that way. Type properties must be accessed with the type name.
Problem: This code tries to access a shared type property through an instance, which causes a compile-time error because the member belongs to the type, not the object.
struct Settings {
static let theme = "Dark"
}
let settings = Settings()
print(settings.theme)Fix: Access type members using the type name itself.
struct Settings {
static let theme = "Dark"
}
print(Settings.theme)The corrected version works because theme is owned by Settings, not by a specific instance.
Mistake 2: Using static when you need overriding
In classes, developers sometimes write a static method and later try to override it in a subclass. That is not allowed.
Problem: A static type method in a class is final-like for overriding purposes, so attempting to override it produces a compiler error such as Cannot override static method.
class Animal {
static func sound() -> String {
"Some sound"
}
}
class Dog: Animal {
override class func sound() -> String {
"Bark"
}
}Fix: Use class for the base type method if subclasses should override it.
class Animal {
class func sound() -> String {
"Some sound"
}
}
class Dog: Animal {
override class func sound() -> String {
"Bark"
}
}The corrected version works because class methods in classes are overridable.
Mistake 3: Expecting each instance to have its own static value
A static property is shared across all instances. If you change it, every instance sees the same updated value.
Problem: This code uses a type property for data that should be unique per object, so changing one value affects all instances and produces incorrect behavior.
class Player {
static var score = 0
}
Player.score = 10
print(Player.score)Fix: Use an instance property when each object needs its own value.
class Player {
var score = 0
}
let player1 = Player()
let player2 = Player()
player1.score = 10
print(player1.score)
print(player2.score)The corrected version works because each Player instance now stores its own score independently.
Mistake 4: Trying to make a stored class property
Swift allows stored type properties with static, but class is used for overridable computed type properties, not stored ones.
Problem: This code attempts to declare a stored type property with class, which Swift does not allow.
class Account {
class var minimumBalance = 100
}Fix: Use static for stored type properties, or use a computed class property when overriding is needed.
class Account {
static var minimumBalance = 100
}
class PremiumAccount {
class var minimumBalance: Int {
500
}
}The corrected version uses the right feature for each case: stored shared data with static, overridable computed behavior with class.
7. Best Practices
Use type members only for truly shared data
If a value should be the same for every instance, a type property is a clean choice. If different objects need different values, keep it as an instance property.
struct Theme {
static let primaryColor = "Blue"
}This works well because the primary color is shared application-wide.
Prefer let for shared constants that should not change
If a type-level value is meant to stay fixed, declare it with let. This makes intent clear and prevents accidental mutation.
struct Limits {
static let maxUploadSize = 10
}This is safer than using a mutable property when the value should remain constant.
Choose class only when subclasses must customize behavior
In classes, prefer static by default unless there is a real need for overriding. Use class only when the design explicitly requires subclass customization.
class Report {
class func fileExtension() -> String {
"txt"
}
}This choice communicates that subclasses may provide a different file extension.
Keep utility methods stateless when possible
A type method is easiest to understand when it does not rely on hidden shared mutable state.
struct Validator {
static func isAdult(age: Int) -> Bool {
age >= 18
}
}This makes the method easy to test and predictable to use.
8. Limitations and Edge Cases
- static members on classes cannot be overridden by subclasses.
- class is only available on classes, not structs or enums.
- Stored type properties use static; a stored class property is not allowed.
- Shared mutable type properties can make code harder to reason about because changes affect all instances.
- If a type property seems to be “not working,” check whether you actually needed an instance property instead.
- Type methods cannot directly use instance properties unless you first create or receive an instance.
- Type properties are lazily initialized in Swift, which is useful for setup but can surprise beginners who expect immediate initialization at program start.
9. Practical Mini Project
This mini project builds a simple employee system that uses type properties and methods in realistic ways. It tracks the total number of employees, stores a shared company name, and provides an overridable class method for employee role labels.
class Employee {
static let companyName = "Northwind Labs"
static var totalEmployees = 0
let name: String
init(name: String) {
self.name = name
Employee.totalEmployees += 1
}
class func roleLabel() -> String {
"Employee"
}
func summary() -> String {
"\(name) works at \(Employee.companyName) as a \(type(of: self).roleLabel())."
}
}
class Manager: Employee {
override class func roleLabel() -> String {
"Manager"
}
}
let employee1 = Employee(name: "Lena")
let employee2 = Manager(name: "Marcus")
print(employee1.summary())
print(employee2.summary())
print("Total employees: \(Employee.totalEmployees)")This example combines several important ideas: shared constants with static let, shared mutable counters with static var, and subclass customization with an overridable class method.
10. Key Points
- Type properties and methods belong to the type itself, not to individual instances.
- Use static for type-level members on structs, enums, and classes.
- Use class only in classes when subclasses should be able to override the member.
- Access type members with the type name, such as User.count.
- Use type properties for shared data and instance properties for per-object data.
- Stored type properties use static, while overridable type properties with class are computed.
11. Practice Exercise
Create a Book class with:
- A shared company-wide library name stored as a type property.
- A shared counter that tracks how many books have been created.
- An instance property for the book title.
- A type method that returns a category label.
- A subclass named ReferenceBook that overrides the category label.
Expected output: Two book summaries and the total number of books created.
Hint: Use static for shared stored values and class func for the overridable type method.
class Book {
static let libraryName = "City Library"
static var bookCount = 0
let title: String
init(title: String) {
self.title = title
Book.bookCount += 1
}
class func category() -> String {
"General"
}
func summary() -> String {
"\(title) is a \(type(of: self).category()) book in \(Book.libraryName)."
}
}
class ReferenceBook: Book {
override class func category() -> String {
"Reference"
}
}
let book1 = Book(title: "Swift Basics")
let book2 = ReferenceBook(title: "Swift Standard Library Guide")
print(book1.summary())
print(book2.summary())
print("Total books: \(Book.bookCount)")12. Final Summary
Swift type properties and type methods are the right tool when data or behavior belongs to the type as a whole instead of to a single instance. Use static for shared values and helper behavior, and remember that those members are accessed through the type name, not an instance.
When working with classes, the key decision is usually static vs class. Choose static when overriding is not needed, and choose class when subclasses should provide their own implementation of a type method or computed type property.
A good next step is to study Swift instance methods, inheritance, and computed properties together with this topic. Those concepts make it much easier to decide whether behavior belongs to an instance or to the type itself.