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.

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:

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 * 4

This 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 - 3

This 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 **: ExponentiationPrecedence

This 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

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 20

Fix: 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

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

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.

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.