1. Precedence and evaluation order
Operator precedence is about the order in which Go evaluates an expression when you wrote several operations in a row. At first it feels like, “Well, it’s obvious.” And then suddenly a discount is computed “wrong,” percentages turn into magic, and the final amount “drifts” — and it’s not the computer’s fault, it’s the expression without parentheses.
In programming (and in life), the most unpleasant mistakes are the ones that don’t crash with a panic but instead give a wrong, yet “plausible-looking” result. These are exactly the kinds of bugs that often come from precedence and integer division. So now we’ll do two things: understand the evaluation order and learn to predict the result.
Arithmetic operators
You’ve already encountered arithmetic: + - * / %. Now it’s useful to “wipe the glass a bit” and notice an important detail: the same symbol can behave predictably, but the context decides what exactly is happening. For example, -5 is not “subtraction,” it’s unary minus (an operator with one operand). And 10 - 5 is subtraction.
And even more importantly: in Go, arithmetic is almost always strictly typed. That is, expressions on int produce results of type int. This is especially noticeable with division.
A small “just in case” diagnostic:
package main
import "fmt"
func main() {
a := 10
b := 3
fmt.Printf("a=%d, b=%d\n", a, b) // a=10, b=3
}
Operator precedence
When we write an expression like 2 + 3*4, Go doesn’t read it left to right “like a tired person.” It reads it by the rules: multiplication first, then addition.
It’s convenient to keep a very short “school” version in mind, which is fully sufficient for us right now: parentheses are strongest, then * / %, then + -. This alone is enough to avoid 90% of common traps.
Mini precedence table (what you need right now):
| Group | Operators | How to remember |
|---|---|---|
| 1 (strongest) | |
“Parentheses are the boss” |
| 2 | |
“Multiply-divide-remainder are one team” |
| 3 (weaker) | |
“Addition/subtraction come later” |
Important: the % operator (remainder) lives at the same level as * and /. That means a + b%10 is a + (b%10), not (a+b)%10.
Left associativity
When operators have the same precedence (for example, * and /), a second question appears: “OK, but which one goes first?” Answer: in typical cases, Go evaluates left to right.
This is called (don’t worry) associativity. For us, it’s enough to remember: 8 / 2 * 2 is computed as ((8 / 2) * 2), not as 8 / (2 * 2).
Let’s look at concrete numbers:
package main
import "fmt"
func main() {
fmt.Println(8/2*2) // 8
fmt.Println(8/(2*2)) // 2
}
And this is not a “Go quirk”; it’s normal expression math in most languages. That’s why, if you want a different order explicitly — use parentheses, even if it’s “obvious.”
Parentheses as a way to make code more obvious
In reality, precedence is not something you should keep in your head like a multiplication table. In reality, precedence is something you should be able to check, and you should write code so the precedence is obvious.
Parentheses are not a sign of weakness (even if coworkers sometimes pretend they are). Parentheses are like labels on wires: “red is plus, don’t touch.”
Let’s compare two versions:
package main
import "fmt"
func main() {
price := 200
qty := 3
total1 := price*qty + 50
total2 := (price * qty) + 50
fmt.Println(total1, total2) // 650 650
}
Both work the same, but total2 is faster to read. Your future you will thank your current you. (A rare moment in life when this actually happens.)
2. Integer division and remainder
Integer division
Now — about division. And here comes that moment where many people say: “Wait, what?!”
If both operands are integers (int, int64, etc.), then / performs integer division, meaning the fractional part is simply discarded.
It does not round “by math rules,” it does not automatically turn into float64, it does not try to guess what you “meant.” It just takes the whole part.
package main
import "fmt"
func main() {
fmt.Println(7 / 2) // 3
fmt.Println(9 / 3) // 3
fmt.Println(1 / 2) // 0
}
1/2 == 0 is normal for integer arithmetic. That’s exactly why it’s very convenient for counters, indexes, “how many full boxes,” and money amounts in kopecks/cents.
Division of negative numbers
A separate nuance: what happens with negative numbers. In Go, integer division goes toward zero. This means -7/2 will be -3, not -4.
package main
import "fmt"
func main() {
fmt.Println(-7 / 2) // -3
fmt.Println(-1 / 2) // 0
}
Why is this important? Because the % remainder is tied to division. And if you do logic with negatives (for example, balance, debt, difference), it’s better to know in advance that the language behaves this way, not “the way you wish it did on a Friday evening.”
Remainder
The % operator is the remainder of division. It works only with integer types and is great for “splitting a number” into parts: seconds and minutes, rubles and kopecks, pages and items.
A very useful formula worth remembering as a fact of life:
If a and b are integers, then: a == (a/b)*b + (a%b)
Let’s verify with an example:
package main
import "fmt"
func main() {
a := 17
b := 5
fmt.Println(a/b, a%b) // 3 2
fmt.Println((a/b)*b + (a%b)) // 17
}
Practice: euros and cents
The most “real-life” example for integer division is money. With float64, money often produces “tails” like 10.0000000002, but with integer kopecks/cents everything is strict.
Suppose we have an amount in kopecks (or cents), and we want to print “euros and cents.”
package main
import "fmt"
func main() {
totalCents := 12345
euros := totalCents / 100
cents := totalCents % 100
fmt.Printf("%d euros %02d cents\n", euros, cents) // 123 euros 45 cents
}
Notice %02d: it’s just an output format so cents are always printed with two digits (for example, 05 instead of 5). We’re not diving into formatting as a separate topic right now, but this small trick makes the output “human-friendly.”
Percentages and operation order
Percentages are a classic source of bugs. The reason is simple: percentages almost always look like a “simple formula,” but with integers you suddenly run into division and operation order.
Imagine: the price price = 199 euros, the discount is 10%. How do we compute the discount amount in euros?
A naive attempt often looks like this: discount := price * 10 / 100. This is already pretty good: multiplication happens before division, all good.
But the mistake appears when someone writes: discount := price * (10 / 100). Here (10/100) is integer division and yields 0. So the discount becomes price*0, i.e. 0.
Let’s show them side by side:
package main
import "fmt"
func main() {
price := 199
d1 := price * 10 / 100
d2 := price * (10 / 100)
fmt.Println(d1) // 19
fmt.Println(d2) // 0
}
This is a great example of why parentheses are a powerful tool, but you can also “make it worse” with them if you don’t understand division.
3. Practice: an order, a discount, and splitting the total
Order calculation scheme
Let’s continue our mini app. Let’s call it условно “Coffee Shop”: we calculate the order cost, the discount, and the final total. We’ll store everything in kopecks (or cents) as int64 so we don’t argue with floating point.
Below is the computation scheme so you don’t drown in a single 200-character line.
flowchart TD
A[Unit price, kopecks] --> C[Compute subtotal]
B[Quantity] --> C
C --> D[Discount, kopecks]
D --> E[Total, kopecks]
E --> F[Rubles and kopecks]
The scheme is simple: first subtotal, then discount, then total, then “split” into rubles/kopecks for printing.
Code: subtotal, discount, and total
Let’s make a small but coherent fragment: we input the price (in kopecks), the quantity, and the discount percent. We compute carefully, without mixing everything into one line.
package main
import (
"fmt"
)
func main() {
var priceK int64
var qty int64
var discountPercent int64
fmt.Scan(&priceK, &qty, &discountPercent)
subtotal := priceK * qty
discount := subtotal * discountPercent / 100
total := subtotal - discount
fmt.Println(total)
}
The most important part here is the line discount := subtotal * discountPercent / 100. Because of precedence, it’s computed as (subtotal * discountPercent) / 100, i.e. “multiply first, then divide.” This is usually what you want for percentages in integer arithmetic.
Why it’s better to multiply before dividing
This is not only about “precedence.” It’s also about the fact that integer division discards the fractional part, which means you lose precision.
If you divide too early, you can lose information even before multiplying. The total loss can be surprising.
Let’s compare two discount formulas in kopecks:
1) subtotal * percent / 100
2) subtotal / 100 * percent
They are not equal because of truncation.
package main
import "fmt"
func main() {
subtotal := int64(199) // cents for simplicity
percent := int64(10)
a := subtotal * percent / 100
b := subtotal / 100 * percent
fmt.Println(a) // 19
fmt.Println(b) // 10
}
In the second variant, subtotal/100 first turned into 1, because 199/100 == 1. And only then it was multiplied by 10.
Where the “extra cents” go with a discount
If you compute a discount in kopecks/cents, you almost always face the fact that “by math” it comes out fractional, but there is no fraction of a cent in integer cents. For example, a 15% discount on 99 cents is 14.85 cents. What do you do with the 0.85?
In this lecture we don’t introduce separate rounding functions (that’s a separate topic), but it’s important to understand the basic effect: in integers, the fraction is simply lost. That means you always round toward zero on division.
Let’s see:
package main
import "fmt"
func main() {
subtotal := int64(99)
percent := int64(15)
discount := subtotal * percent / 100
fmt.Println(discount) // 14
}
This is not a “Go error.” It’s just the chosen data type: integers.
Splitting a total among people and computing the remainder
A very typical situation: you need to split a bill evenly among N people. In integer cents you’ll get a “remainder” that someone has to cover (or you decide the coffee shop gifts those cents to the world).
package main
import "fmt"
func main() {
totalC := 1000 // 10 euros 00 cents
people := 3
perPerson := totalC / people
remainder := totalC % people
fmt.Println(perPerson, remainder) // 333 1
}
Here we got: everyone pays 333 cents, and 1 cent remains as the “tail.”
Why one line doesn’t make code smarter
Once you’ve learned precedence, there’s a temptation to write like this:
total := priceC*qty - priceC*qty*discountPercent/100
The compiler will accept it. But a human (including you a week later) will slowly become sad.
It’s better to be a bit longer but clear. We already did it this way, and it’s genuinely good style for a beginner:
package main
import "fmt"
func main() {
priceC := int64(15000)
qty := int64(2)
discountPercent := int64(10)
subtotal := priceC * qty
discount := subtotal * discountPercent / 100
total := subtotal - discount
fmt.Println(total) // 27000
}
Numeric literals and dividing 7/2
Sometimes the confusion is amplified by the fact that literals like 7 and 2 look like “just numbers.” In Go, numeric constants first exist as “ideal numbers (untyped),” but when you use them in a regular expression, they get a default type. For integer constants it’s int, for floating-point it’s float64.
That’s why 7/2 is int divided by int, i.e. integer division. But 7.0/2.0 is already floating-point.
Let’s show the difference without type conversions (just by using different literals):
package main
import "fmt"
func main() {
fmt.Println(7 / 2) // 3
fmt.Println(7.0 / 2.0) // 3.5
}
4. Common mistakes
Mistake #1: expecting a “fraction” from int / int.
When you divide int by int, you get int, and the fractional part disappears. The most painful case is when you divide small numbers and get 0, and then multiply by something serious and wonder “why is everything zero.” This isn’t fixed by “magic,” it’s fixed by understanding which arithmetic is being performed: integer or floating-point.
Mistake #2: adding parentheses that change the meaning of percentages.
The expression subtotal * percent / 100 is usually correct. But subtotal * (percent / 100) is almost always wrong with integers, because (percent/100) often becomes 0. Parentheses aren’t just decoration; they change evaluation order, so you must use them deliberately.
Mistake #3: forgetting that % has high precedence.
Beginners sometimes read a + b%10 as “add first, then take the remainder.” But in reality the remainder b%10 is computed first, and only then it’s added to a. If you want something else, you need parentheses: (a + b) % 10.
Mistake #4: trying to “round” via int(x/100*percent) in integer arithmetic.
If x is an integer, then x/100 has already lost its fractional part, and no later multiplication will bring it back. It’s often more correct to multiply first and divide later, not the other way around. This is not an “always do this” rule, but it’s a good default for percentages and proportions in integer arithmetic.
Mistake #5: turning the calculation into one line and losing control of the meaning.
Technically, Go allows long expressions, and you really can “keep precedence in your head.” But in real code, it’s more important that the expression is readable: intermediate variables (subtotal, discount, total) are not “extra code,” they make the formula visually checkable and therefore more reliable.
GO TO FULL VERSION