Swift Operator Precedence and Custom Precedence Groups
Swift expressions can contain several operators in one line, and Swift needs rules to decide which part runs first. Those rules are called operator precedence and associativity. In this article, you will learn how Swift evaluates mixed expressions, how built-in precedence works, and how to create custom precedence groups for your own operators without making code confusing or unsafe.
Quick answer: In Swift, operator precedence decides which operator is evaluated first, while associativity decides how operators of the same precedence are grouped. If you create a custom operator, you should usually assign it to a custom precedencegroup so Swift knows how it interacts with other operators.
Difficulty: Intermediate
Helpful to know first: You will understand this better if you already know basic Swift syntax, variables and constants, functions, and common operators like +, ==, and &&.
1. What Is Operator Precedence and Custom Precedence Groups?
Operator precedence is the set of rules Swift uses to evaluate expressions that contain more than one operator. Without precedence rules, Swift would not know whether to add before multiplying, compare before assigning, or apply logical operators in the correct order.
Custom precedence groups let you define those rules for your own operators. This matters when you create a custom infix operator and want it to behave predictably in larger expressions.
- Precedence decides which operator binds more tightly.
- Associativity decides how operators of the same precedence group are evaluated from left to right or right to left.
- Precedence groups are named levels that operators can belong to.
- Custom precedence groups are mainly used with custom infix operators.
- Parentheses always let you override precedence when clarity matters.
A common confusion is thinking precedence and associativity are the same thing. They are related, but different. Precedence answers which operator first. Associativity answers how operators at the same level group together.
2. Why Operator Precedence Matters
Precedence matters because code can produce the wrong result even when it compiles successfully. If you misunderstand the order of evaluation, you may write expressions that look correct but behave differently from what you intended.
Real-world reasons this matters include:
- Calculating totals, discounts, or tax values correctly.
- Combining comparison and logical operators in conditions.
- Using the nil-coalescing operator ?? with other expressions safely.
- Designing domain-specific operators for math, pipelines, or data transformation.
- Avoiding ambiguous or hard-to-read custom operator code.
You should care especially when an expression mixes arithmetic, comparison, logical, range, assignment, or custom operators. Even if Swift knows the answer, human readers may not.
3. Basic Syntax or Core Idea
Understanding built-in precedence
Swift gives built-in operators predefined precedence groups. For example, multiplication has higher precedence than addition, so multiplication happens first in a mixed arithmetic expression.
let result = 2 + 3 * 4This evaluates as 2 + (3 * 4), so the result is 14, not 20.
Understanding associativity
When operators have the same precedence, associativity matters. For many arithmetic operators, evaluation is left-associative.
let value = 20 - 5 - 3This is grouped as (20 - 5) - 3, giving 12.
Declaring a custom precedence group
To control a custom infix operator, declare a precedence group and then assign the operator to it.
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedenceThis tells Swift that ** should bind more tightly than multiplication and that repeated uses of the same operator should group from right to left.
Implementing the operator
After declaring the operator, you define how it works with a function.
func ** (lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}Now Swift knows both the behavior of the operator and where it fits in expression parsing.
4. Step-by-Step Examples
Example 1: Arithmetic precedence
This example shows the most familiar precedence rule: multiplication happens before addition.
let total = 10 + 2 * 5
print(total)The printed result is 20 because Swift evaluates 2 * 5 first, then adds 10.
Example 2: Using parentheses to override precedence
Parentheses let you force a different evaluation order when that matches your intention better.
let total = (10 + 2) * 5
print(total)The printed result is 60 because the grouped addition happens first.
Example 3: Comparison and logical operators
Logical expressions often mix comparison operators like > with logical operators like && and ||. Swift evaluates the comparison expressions before the logical combination.
let age = 20
let hasID = true
let canEnter = age >= 18 && hasID
print(canEnter)Swift evaluates age >= 18 first, then combines that Boolean result with hasID using &&.
Example 4: Nil-coalescing with arithmetic
The nil-coalescing operator can be tricky in larger expressions, so this is a good place to rely on precedence awareness and parentheses.
let storedBonus: Int? = nil
let finalScore = 50 + (storedBonus ?? 10)
print(finalScore)This prints 60. The parentheses make it obvious that the optional bonus is resolved first, then added to the base score.
Example 5: A custom operator with a precedence group
Here is a complete custom operator example. This operator raises one number to the power of another.
import Foundation
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func ** (lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
let result = 2.0 * 3.0 ** 2.0
print(result)This prints 18.0 because 3.0 ** 2.0 is evaluated before multiplication.
5. Practical Use Cases
- Building formulas for finance, scoring, or statistics where arithmetic precedence affects the result.
- Writing complex if conditions that mix comparisons and logical operators.
- Using ?? to supply fallback values in calculations or string building.
- Creating custom operators for mathematical libraries or domain-specific APIs.
- Designing expressive internal DSLs where operator readability depends on a sensible precedence group.
- Refactoring long expressions safely by knowing when parentheses are required for clarity.
6. Common Mistakes
Mistake 1: Assuming expressions are evaluated strictly left to right
Beginners often read an expression from left to right and expect Swift to do the same. That is not how mixed operators work.
Problem: This code assumes addition happens before multiplication, so the expected result is wrong even though the code compiles.
let result = 2 + 3 * 4
print(result) // expecting 20Fix: Use parentheses when you want a non-default order of evaluation.
let result = (2 + 3) * 4
print(result)The corrected version works because the parentheses explicitly group the addition first.
Mistake 2: Creating a custom operator without assigning meaningful precedence
A custom operator needs a clear place in Swift's parsing rules. If you do not think carefully about its precedence, expressions that use it may be surprising or hard to read.
Problem: This custom operator is declared, but its relationship to other operators is unclear to readers, and the resulting expressions may not behave as intended for your API design.
infix operator +-
func +- (lhs: Int, rhs: Int) -> Int {
return lhs + rhs - 1
}Fix: Define a precedence group so the operator has predictable behavior in mixed expressions.
precedencegroup AdjustedAdditionPrecedence {
higherThan: AdditionPrecedence
associativity: left
}
infix operator +-: AdjustedAdditionPrecedence
func +- (lhs: Int, rhs: Int) -> Int {
return lhs + rhs - 1
}The corrected version works because Swift and other developers can now understand how the operator should combine with surrounding operators.
Mistake 3: Forgetting that associativity affects repeated custom operators
If the same custom operator appears multiple times in one expression, associativity decides how Swift groups the expression.
Problem: This operator models exponentiation, but left associativity gives mathematically surprising results for repeated exponentiation.
import Foundation
precedencegroup BadExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: left
}
infix operator **: BadExponentiationPrecedence
func ** (lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}Fix: Use right associativity for exponent-style operators so repeated expressions group in the expected way.
import Foundation
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func ** (lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}The corrected version works because repeated exponentiation groups from right to left, which better matches mathematical expectations.
Mistake 4: Relying on precedence when parentheses would make intent clearer
Even when Swift parses an expression correctly, readers may still struggle to understand it. Readability is part of correctness in production code.
Problem: This condition may be technically valid, but it is easy to misread and maintain incorrectly later.
let isAdmin = false
let isOwner = true
let isActive = true
if isAdmin || isOwner && isActive {
print("Allowed")
}Fix: Add parentheses to show the intended logic clearly, even if Swift does not require them.
let isAdmin = false
let isOwner = true
let isActive = true
if isAdmin || (isOwner && isActive) {
print("Allowed")
}The corrected version works because the grouping is now obvious to anyone reading the code.
7. Best Practices
Practice 1: Prefer parentheses when an expression mixes several operator categories
Swift has well-defined precedence rules, but that does not mean every reader remembers them. Parentheses are often the best documentation for your intention.
Less-preferred code:
let message = name ?? "Guest" + "!"Preferred code:
let message = (name ?? "Guest") + "!"This is better because the fallback value is clearly resolved before the string concatenation.
Practice 2: Give custom precedence groups descriptive names
A good precedence group name helps explain the operator’s role. Avoid vague names that reveal nothing about purpose or relative strength.
Less-preferred code:
precedencegroup MyGroup {
higherThan: AdditionPrecedence
associativity: left
}Preferred code:
precedencegroup PipelinePrecedence {
higherThan: AdditionPrecedence
associativity: left
}This is better because the name communicates the operator’s intended semantic role.
Practice 3: Keep custom operators rare and purposeful
Just because Swift allows custom operators does not mean every repeated pattern should become one. Operators work best when they model something concise, conventional, and easy to read.
Less-preferred code:
infix operator =>>Preferred code:
func applyDiscount(price: Double, rate: Double) -> Double {
return price * (1 - rate)
}This is better when the intent is business logic rather than mathematical notation, because a named function is often clearer than a symbolic operator.
Practice 4: Match associativity to the mental model of the operator
Associativity should fit how developers naturally expect the operator to group. Arithmetic-like chaining often uses left associativity, while exponent-style operations often use right associativity.
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}This matters because associativity affects results whenever the same operator appears multiple times in one expression.
8. Limitations and Edge Cases
- Custom precedence groups only affect parsing of operators, not the underlying semantics of your function implementation.
- Even valid precedence rules can create code that is hard for teammates to read. Readability is often a stronger constraint than syntax.
- Custom operators are most useful in specialized libraries, mathematical code, or DSL-style APIs. In general app code, named functions are often clearer.
- If you mix optional-related operators such as ?? with arithmetic or string concatenation, parentheses are often the safest choice for readability.
- Associativity only matters when operators of the same precedence are chained together.
- A precedence group can declare relationships like higherThan or lowerThan, but poor choices can still make expressions confusing.
- If code seems to be “not working,” the issue is often not Swift’s precedence rules themselves but a mismatch between the reader’s expectation and the actual grouping.
A practical rule is simple: if a reader has to stop and remember precedence tables, add parentheses or use a named helper function instead.
9. Practical Mini Project
This mini project creates a small exponentiation operator and uses it in a score formula. It demonstrates built-in precedence, custom precedence, and explicit grouping in one complete example.
import Foundation
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func ** (lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
let baseScore = 4.0
let multiplier = 3.0
let bonus: Double? = nil
let finalScore = (baseScore ** 2.0) + (bonus ?? multiplier * 2.0)
print("Final score:", finalScore)This project defines ** with a custom precedence group that is higher than multiplication. It then calculates a final score by squaring the base score and adding either an optional bonus or a fallback expression. The parentheses make the intended grouping obvious, which is exactly what you want in production code.
10. Key Points
- Operator precedence decides which operator Swift evaluates first in a mixed expression.
- Associativity decides how operators of the same precedence group are chained.
- Built-in Swift operators already belong to predefined precedence groups.
- Custom infix operators should usually be assigned to a custom precedencegroup.
- higherThan and lowerThan describe how a custom group relates to existing groups.
- For repeated exponent-style operators, right associativity is often the most natural choice.
- Parentheses are the best tool when you want to remove doubt and improve readability.
- Readable expressions are usually more valuable than clever operator-heavy syntax.
11. Practice Exercise
Create a custom operator %% that returns the average of two Double values. Put it in a custom precedence group that is lower than multiplication but higher than addition. Then use it in an expression with both + and * to confirm the grouping works the way you expect.
- Declare a precedence group named AveragePrecedence.
- Set it lower than MultiplicationPrecedence.
- Set it higher than AdditionPrecedence.
- Declare the infix operator %%.
- Implement it for two Double values.
- Print the result of an expression like 2.0 + 4.0 %% 6.0 * 2.0.
Expected output: A numeric result that shows * happens before %%, and %% happens before +.
Hint: The average of two values can be calculated with (lhs + rhs) / 2.
precedencegroup AveragePrecedence {
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
associativity: left
}
infix operator %%: AveragePrecedence
func %% (lhs: Double, rhs: Double) -> Double {
return (lhs + rhs) / 2.0
}
let result = 2.0 + 4.0 %% 6.0 * 2.0
print(result)In this solution, 6.0 * 2.0 is evaluated first, then the average operator is applied, and finally the addition happens last.
12. Final Summary
Swift operator precedence is the rule system that lets the compiler evaluate expressions consistently. It works together with associativity to determine how mixed operators and repeated operators are grouped. Once you understand those two ideas, many expressions become much easier to read and debug.
Custom precedence groups matter when you create your own infix operators. They let you define how a new operator should interact with Swift’s built-in operators, but they should be used carefully. A technically correct precedence rule can still produce code that is difficult for humans to understand.
The safest habit is to learn the core rules, use custom operators sparingly, and add parentheses whenever intent could be unclear. A good next step is to study Swift’s standard operator categories such as arithmetic, comparison, logical, range, and nil-coalescing operators so you can read complex expressions with confidence.