Swift Enumerations Explained: Cases, Raw Values, and Associated Values
Swift enumerations, usually written as enum, let you define a fixed set of related values under one type. They are one of Swift’s most useful custom types because they make code clearer, safer, and easier to reason about than passing around loosely related strings, numbers, or Boolean combinations.
Quick answer: A Swift enumeration is a custom type that groups a known set of possible cases, such as directions, network states, or user roles. Enums help you model data more safely, and Swift can often check at compile time that you handled every possible case.
Difficulty: Beginner
Helpful to know first: basic Swift syntax, how constants and variables work, and simple types like String, Int, and functions.
1. What Is Enumerations?
An enumeration is a custom Swift type that represents a small, defined set of possible values. Each possible value is called a case. Instead of using random strings like "success" or numbers like 0 and 1, you can create a meaningful type with named cases.
For example, a traffic light has a limited set of states. That makes it a good fit for an enum.
- An enum defines a fixed list of allowed cases.
- Each case belongs to the enum’s type.
- Enums improve readability because case names describe intent.
- Enums improve safety because invalid values are harder to create.
- Swift enums are powerful: they can also store raw values, associated values, properties, methods, and conform to protocols.
Beginners often compare enums to strings or integers. The key difference is that strings and integers can represent many unrelated values, while an enum only allows the cases you defined. That makes bugs easier to prevent.
enum Direction { case north }
- enum
- declaration keyword
- Direction
- type name
- case
- case keyword
- north
- case name
A Swift enum declares a type and the cases that belong to it.
Here is a simple enum declaration:
enum Direction {
case north
case south
case east
case west
}
This code creates a new type named Direction. A value of that type can only be one of four cases.
In Swift, enum cases are values in their own right. They are not just labels attached to numbers like they often are in some other languages.
2. Why Enumerations Matters
Enums matter because many programming problems involve a value that must be one of a small number of known options. When you model those options as an enum, your code becomes more explicit and more reliable.
Here are common reasons to use enums:
- To represent status values such as loading, success, and failure.
- To model categories like user roles, directions, payment methods, or file types.
- To make switch statements safer, because Swift can require exhaustive handling.
- To avoid stringly typed code, where spelling mistakes can create bugs.
- To attach related data using associated values.
Imagine you store order status as a string:
let status = "shippd"
This compiles, but the value is wrong because of a typo. With an enum, that kind of mistake is much harder to make.
enum OrderStatus {
case pending
case processing
case shipped
case delivered
}
Now your program works with a dedicated type instead of arbitrary text.
Enums are especially valuable in Swift because the language uses them heavily in its own design. Optional is an enum behind the scenes, and many APIs use enums to model states and choices safely.
3. Basic Syntax or Core Idea
The basic syntax starts with the enum keyword, followed by a type name and one or more case declarations.
Declaring an enum
This is the smallest useful pattern:
enum Season {
case spring
case summer
case autumn
case winter
}
Season is the enum type, and the four case names are the only valid values of that type.
Creating enum values
You can assign a case to a variable or constant using dot syntax:
let favoriteSeason: Season = .autumn
Because the type is already known on the left side, Swift lets you write .autumn instead of Season.autumn.
You can also write the full form:
let currentSeason = Season.summer
Both forms create a value of type Season.
Multiple cases on one line
Swift also allows multiple cases in one declaration:
enum CompassPoint {
case north, south, east, west
}
This is equivalent to writing each case on its own line.
Using enums with switch
One of the most important ideas in Swift enums is that they work naturally with switch. Swift checks that all cases are handled unless you use a default branch.
let direction = Direction.east
switch direction {
case .north:
print("Going up")
case .south:
print("Going down")
case .east:
print("Going right")
case .west:
print("Going left")
}
This shows why enums are so useful: the language knows exactly which cases exist.
A common beginner question is whether enums are like structs or classes. An enum is its own custom type category in Swift. Use it when a value should be one of several known cases, not when you need many stored properties like a typical struct or reference semantics like a class.
4. Step-by-Step Examples
The examples below build from simple cases to more powerful enum features you will use in real Swift code.
Example 1: Representing a simple choice
This example uses an enum to store a player difficulty setting. It is clearer than using strings like "easy" or "hard".
enum Difficulty {
case easy
case medium
case hard
}
let selectedDifficulty = Difficulty.medium
print(selectedDifficulty)
This creates a value that can only be one of the three defined difficulty levels.
Example 2: Using a switch with an enum
Here, the enum controls program behavior. This is one of the most common enum patterns in Swift.
enum ConnectionState {
case disconnected
case connecting
case connected
}
let state = ConnectionState.connecting
switch state {
case .disconnected:
print("No network connection")
case .connecting:
print("Connecting...")
case .connected:
print("Connected successfully")
}
The enum gives the switch a controlled set of possibilities, and Swift checks that none are forgotten.
Example 3: Enum with raw values
Enums can assign each case a raw value. A raw value is a built-in value such as a string or integer that is associated with each case.
enum HTTPStatus: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
let status = HTTPStatus.notFound
print(status.rawValue)
This prints 404. Raw values are useful when your enum cases map to external data, database values, or protocol codes.
You can also create an enum from a matching raw value:
let responseCode = 200
let httpStatus = HTTPStatus(rawValue: responseCode)
print(httpStatus as Any)
This initializer returns an optional because the integer might not match any case.
Example 4: Enum with associated values
Associated values are different from raw values. Raw values are fixed and shared by a case definition. Associated values store extra data for each specific enum value.
enum LoginResult {
case success(String)
case failure(String)
}
let result = LoginResult.failure("Wrong password")
switch result {
case .success(let username):
print("Welcome, \(username)!")
case .failure(let message):
print("Login failed: \(message)")
}
This is a powerful pattern because the enum does not just say what happened. It can also carry the related data for that case.
Raw values and associated values are often confused. A raw value is predefined for every case, like 404 for notFound. An associated value is extra data stored with an individual enum value at runtime, like a specific error message.
5. Practical Use Cases
Enums are most useful when your data naturally falls into a small set of well-defined options. Here are practical situations where they improve Swift code:
- App state management, such as idle, loading, loaded, and failed.
- Network result types, especially when success and failure need different associated data.
- User roles like guest, member, and admin.
- Input modes such as card payment, bank transfer, or cash on delivery.
- Direction, navigation, or movement logic in games and command-line tools.
- Parsing external values such as HTTP status codes, file formats, or configuration options.
- Feature flags where only certain named modes are valid.
As a rule, use an enum when a value should be one of several known choices. If you instead need a type that stores many independent pieces of data together, a struct is often the better fit.
For example, this is a strong enum use case:
enum PaymentMethod {
case creditCard
case bankTransfer
case cash
}
But this kind of data usually belongs in a struct instead:
struct Customer {
let name: String
let email: String
let age: Int
}
The enum models one choice from a limited set. The struct models an object with multiple stored properties.
6. Common Mistakes
Swift enums are very readable once you understand them, but beginners often run into the same few problems. Most of these issues come from forgetting that enum values must match a defined case exactly, or from not handling every possible case in a switch.
Mistake 1: Using a case that does not exist
An enum can only use the cases declared inside its definition. You cannot invent new values later.
Problem: This code tries to assign a case that was never declared in the enum, so Swift reports that the member does not exist.
enum TrafficLight {
case red
case yellow
case green
}
let light = TrafficLight.blue
Fix: Use only one of the declared cases, or add the missing case to the enum if it is a real valid state.
enum TrafficLight {
case red
case yellow
case green
}
let light = TrafficLight.green
The corrected version works because green is a real case defined by the enum.
Mistake 2: Forgetting that switch must be exhaustive
When you switch over an enum, Swift usually requires you to handle every possible case. This makes your code safer because no state is silently ignored.
Problem: This switch does not cover all enum cases, so Swift shows an error such as Switch must be exhaustive.
enum Direction {
case north
case south
case east
case west
}
let direction = Direction.east
switch direction {
case .north:
print("Up")
case .south:
print("Down")
}
Fix: Cover every case explicitly, or use default when that is truly appropriate.
switch direction {
case .north:
print("Up")
case .south:
print("Down")
case .east:
print("Right")
case .west:
print("Left")
}
The corrected version works because every possible enum value is handled.
Mistake 3: Confusing raw values with associated values
Raw values are fixed values attached to each case definition. Associated values are extra data stored with a specific enum instance.
Problem: This code tries to pass data into a raw-value enum case, but raw values do not work that way.
enum Status: String {
case success = "success"
case error = "error"
}
let result = Status.error("Network failed")
Fix: Use an enum with associated values when each case may carry extra information.
enum Status {
case success
case error(String)
}
let result = Status.error("Network failed")
The corrected version works because the error case is defined to store a String message.
Mistake 4: Treating raw values as automatic case lookups
If an enum has raw values, creating an enum from a raw value returns an optional. The raw value might not match any case.
Problem: This code assumes Swift will always find a matching case, but the initializer can return nil.
enum Role: String {
case admin
case editor
case viewer
}
let role: Role = Role(rawValue: "owner")
Fix: Unwrap the optional result safely with if let or provide fallback logic.
if let role = Role(rawValue: "owner") {
print(role)
} else {
print("Unknown role")
}
The corrected version works because it handles the possibility that no matching enum case exists.
7. Best Practices
Enums become especially powerful when you use them to model clear, intentional states. These practices help keep your enums easy to read and easy to maintain.
Practice 1: Use enums for closed sets of valid choices
If a value should come from a fixed list, an enum is usually safer than a plain string. Strings are easy to mistype and harder to validate.
A less-preferred approach uses strings directly:
let userRole = "admim"
A better approach uses an enum:
enum UserRole {
case admin
case editor
case viewer
}
let userRole = UserRole.admin
This is better because Swift now prevents invalid role values at compile time.
Practice 2: Prefer meaningful associated values over separate loose variables
When a state needs extra data, store that data inside the enum case instead of spreading it across multiple variables.
A less-preferred approach keeps related state disconnected:
let isError = true
let errorMessage = "File not found"
A better approach groups the state and its data together:
enum FileResult {
case success(String)
case failure(String)
}
let result = FileResult.failure("File not found")
This is better because the data is attached directly to the state it belongs to.
Practice 3: Keep switch statements explicit when clarity matters
Although default can be convenient, explicitly handling each case is often clearer, especially when the enum has only a few cases.
A less-preferred approach hides behavior inside a catch-all branch:
switch userRole {
case .admin:
print("Full access")
default:
print("Limited access")
}
A better approach spells out the cases:
switch userRole {
case .admin:
print("Full access")
case .editor:
print("Edit access")
case .viewer:
print("Read-only access")
}
This is better because future readers can see exactly how each enum case is treated.
Practice 4: Add raw values only when they serve a real purpose
Raw values are useful for serialization, display mapping, or interoperability. If you do not need those things, a simple enum is often cleaner.
A less-preferred approach adds raw values without any real need:
enum Theme: String {
case light
case dark
}
If you only need to represent a choice in code, this may be enough:
enum Theme {
case light
case dark
}
This keeps the enum simpler unless raw values are needed for storage, APIs, or user-facing text.
8. Limitations and Edge Cases
Enums are powerful, but they are not the right solution for every situation. These real-world limitations help you decide when to use them and when to choose another type.
- Enums model one choice or one state at a time. If you need many independent stored properties, a struct or class is often a better fit.
- Raw values must all be of the same raw type, such as String or Int. You cannot mix raw value types in one enum.
- Associated values are flexible, but they can make switching more verbose because you often need pattern matching to extract data.
- Creating an enum from a raw value with EnumName(rawValue:) returns an optional, so code may seem like it is “not working” if you forget to unwrap the result.
- If you add a new case to an enum later, existing switch statements may stop compiling until they handle the new case. This is usually helpful, but it can affect many files in a large project.
- Enums with associated values do not automatically behave like simple labels. Two values of the same case can still be different if their associated data is different.
- Some APIs and persistence layers work more easily with raw-value enums than with associated-value enums, because associated values need custom encoding or decoding logic in more advanced scenarios.
Tip: A good rule is: use an enum when you want to say “this value must be one of these known cases.” Use a struct when you want to say “this value has these properties.”
9. Practical Mini Project
Let’s build a small but complete example that uses several enum features together. This program models an order status system for a shopping app.
It uses:
- a simple enum for payment method
- an enum with associated values for order status
- a switch to display useful messages
Here is the full code:
enum PaymentMethod {
case card
case cash
case bankTransfer
}
enum OrderStatus {
case pending
case paid(PaymentMethod)
case shipped(String)
case delivered
case cancelled(String)
}
func printOrderUpdate(status: OrderStatus) {
switch status {
case .pending:
print("Your order has been received.")
case .paid(let method):
switch method {
case .card:
print("Payment confirmed by card.")
case .cash:
print("Payment will be collected in cash.")
case .bankTransfer:
print("Payment confirmed by bank transfer.")
}
case .shipped(let trackingNumber):
print("Your order was shipped. Tracking number: \(trackingNumber)")
case .delivered:
print("Your order has been delivered.")
case .cancelled(let reason):
print("Order cancelled: \(reason)")
}
}
let firstOrder = OrderStatus.paid(.card)
let secondOrder = OrderStatus.shipped("TRK-2048")
let thirdOrder = OrderStatus.cancelled("Item out of stock")
printOrderUpdate(status: firstOrder)
printOrderUpdate(status: secondOrder)
printOrderUpdate(status: thirdOrder)
This mini project shows why enums are so useful in Swift. The order can only be in one valid state at a time, and cases like shipped or cancelled can carry the exact extra data they need.
It also shows how nested switching can keep your logic very explicit. Instead of checking many unrelated booleans or strings, the enum itself describes the possible states of the order.
10. Key Points
- An enum defines a type with a fixed set of possible cases.
- Enums are ideal when a value should be one of several known choices.
- Swift enums can be much more powerful than simple labels because they support methods, computed properties, raw values, and associated values.
- Raw values are fixed values assigned to cases, while associated values store extra data in a specific enum instance.
- switch statements over enums are usually exhaustive, which helps catch missing logic at compile time.
- Enums are often better than strings for roles, states, modes, and categories because they reduce invalid values.
- If you need a type with many independent stored properties, a struct is often a better choice than an enum.
11. Practice Exercise
Try building your own enum-based weather status system.
- Create an enum named Weather.
- Add these cases: sunny, cloudy, rainy(Int), and snowy(Int).
- Write a function named describeWeather that takes a Weather value.
- Use a switch to print a different message for each case.
- For rainy and snowy, print the amount using the associated value.
Expected output: Messages such as Sunny day, Rainfall: 12 mm, or Snowfall: 5 cm.
Hint: Use pattern matching with let inside the switch cases to extract the associated values.
enum Weather {
case sunny
case cloudy
case rainy(Int)
case snowy(Int)
}
func describeWeather(weather: Weather) {
switch weather {
case .sunny:
print("Sunny day")
case .cloudy:
print("Cloudy sky")
case .rainy(let amount):
print("Rainfall: \(amount) mm")
case .snowy(let amount):
print("Snowfall: \(amount) cm")
}
}
let today = Weather.rainy(12)
describeWeather(weather: today)
12. Final Summary
Swift enumerations let you model values that must belong to a limited set of valid cases. That makes your code clearer, safer, and easier to reason about than using loose strings, numbers, or flag variables. In this article, you saw how enums work, how to declare cases, how to use raw values and associated values, and how to process enums with exhaustive switch statements.
You also saw when enums are a great fit and when a struct may be better. If your data represents one choice, one mode, or one state from a known list, an enum is often the right tool. If your data needs several stored properties at the same time, a struct is usually more natural.
A strong next step is to learn more about Swift pattern matching and switch statements, because those features make enums even more powerful. After that, explore structs, methods on custom types, and protocol conformance to build richer Swift models.