Operators — Arithmetic, Comparison, Logical
Every operator Python has, what it does under the hood, and the precedence rules that cause real bugs.
The Seven Arithmetic Operators
Python has seven arithmetic operators. Two of them — floor division and modulo — do not exist in most beginners' prior experience and are worth learning properly the first time.
10 + 3 # 13 addition
10 - 3 # 7 subtraction
10 * 3 # 30 multiplication
10 / 3 # 3.3333333333333335 true division — ALWAYS returns a float
10 // 3 # 3 floor division — divides, then rounds DOWN to the nearest integer
10 % 3 # 1 modulo — the REMAINDER after floor division
10 ** 3 # 1000 exponentiation — 10 to the power of 310 / 2 is 5.0, not 5. This trips up anyone coming from a language where integer division on two integers returns an integer. If you need an integer result, use floor division (//) or wrap the result in int() — but understand that int() truncates toward zero while // always rounds toward negative infinity, and these give different answers for negative numbers.Floor division and modulo with negative numbers
This is where floor division surprises people who assume it behaves like truncation:
7 // 2 # 3 (straightforward)
-7 // 2 # -4 NOT -3 — floor division rounds DOWN (toward -infinity)
7 % 2 # 1
-7 % 2 # 1 NOT -1 — Python's modulo result always matches the SIGN of the divisorThe identity a == (a // b) * b + (a % b) always holds true in Python — that consistency is exactly why the modulo of a negative number behaves this way. This detail matters more than it seems: modulo is used constantly for tasks like determining if a number is even (n % 2 == 0), wrapping an index around an array (i % len(arr)), and bucketing values into a fixed number of groups.
Exponentiation and roots
** also accepts fractional exponents, giving you roots without a separate function — 16 ** 0.5 computes the square root. For anything beyond simple cases, the math module (covered briefly here, and used constantly throughout this track) provides dedicated, more precise functions.
16 ** 0.5 # 4.0 — square root, via exponentiation
27 ** (1/3) # 3.0 — cube root
import math
math.sqrt(16) # 4.0 — dedicated, slightly more precise for edge cases
math.pow(2, 10) # 1024.0 — like **, but always returns a floatComparing Values
5 == 5 # True — equal to
5 != 3 # True — not equal to
5 > 3 # True — greater than
5 < 3 # False — less than
5 >= 5 # True — greater than or equal to
5 <= 4 # False — less than or equal toChained comparisons — a genuine Python convenience
Python allows you to chain comparisons in a way that reads naturally and matches mathematical notation — something most other languages do not support directly.
age = 25
# Instead of writing this:
if age >= 18 and age <= 65:
print("Working age")
# Python lets you write this — and it means exactly the same thing:
if 18 <= age <= 65:
print("Working age")0 <= i < len(items) and similar patterns throughout real production codebases. Use them whenever they make a range check more readable.Comparing different types
Comparing objects of genuinely incompatible types with </> raises a TypeError in Python 3 (this is a real change from Python 2, which allowed nonsensical cross-type comparisons and silently produced arbitrary results). ==/!= are more forgiving — comparing a string to a number with == simply returns False rather than raising an error, since they can never be equal.
"5" == 5 # False — different types, simply not equal, no error
"5" > 5 # TypeError: '>' not supported between instances of 'str' and 'int'and, or, not — and Short-Circuit Evaluation
Python spells its logical operators as English words — and, or, and not — rather than symbols like && and ||.
age = 25
has_license = True
age >= 18 and has_license # True — both must be true
age >= 18 or has_license # True — at least one must be true
not has_license # False — flips the booleanShort-circuit evaluation
Python stops evaluating an and/or expression as soon as the overall result is already determined — it does not evaluate the right-hand side unless it actually needs to. This is not just an optimisation detail; it is a pattern used deliberately in real code.
user = None
# This would crash if user is None, because None has no .name attribute:
# if user.name == "Maria":
# This is safe — short-circuiting means user.name is never evaluated
# once "user is not None" has already determined the result is False:
if user is not None and user.name == "Maria":
print("Found Maria")and returns the first falsy value it finds, or the last value if all are truthy. or returns the first truthy value it finds, or the last value if all are falsy. This is exploited deliberately for default-value patterns: name = user_input or "Anonymous" assigns "Anonymous" only if user_input is falsy (empty, None, zero, etc.).0 or "default" # "default" — 0 is falsy, so "or" moves on and returns the next value
"" or "default" # "default" — same reasoning, empty string is falsy
5 and 10 # 10 — both truthy, "and" returns the LAST value it evaluated
0 and 10 # 0 — short-circuits immediately, never even looks at 10in, not in, is, is not
Python has two operators dedicated to checking membership in a collection, and two dedicated to checking object identity — both read almost like plain English.
fruits = ["apple", "banana", "cherry"]
"banana" in fruits # True
"grape" in fruits # False
"grape" not in fruits # True
# Also works on strings (substring check) and dicts (checks KEYS, not values):
"an" in "banana" # True
{"a": 1, "b": 2}
"a" in {"a": 1, "b": 2} # True — checks keysYou already met is and is not briefly in the previous module — the identity operators, checking whether two names point at the exact same object rather than merely equal values. They matter enough to restate here in the context of operators specifically: reserve them for None/True/False checks, and use ==/!= for ordinary value comparisons.
Compound Assignment Operators
count = 0
count += 1 # same as: count = count + 1 → 1
count -= 1 # same as: count = count - 1 → 0
count *= 5 # same as: count = count * 5 → 0
total = 10
total //= 3 # same as: total = total // 3 → 3
total **= 2 # same as: total = total ** 2 → 9Every arithmetic and bitwise operator in Python has a compound-assignment form. These are purely a convenience for the common pattern of "update this variable based on its current value" — they do not behave differently from the expanded form for immutable types like numbers, but for mutable types like lists, some compound operators do modify the object in place rather than creating a new one, which becomes relevant once you reach the Lists module.
Bitwise Operators — Working Directly With Bits
Bitwise operators manipulate the individual binary bits of integers directly. They come up far less often in everyday application code than arithmetic or logical operators, but they are genuinely used for flags, permission systems, low-level networking code, and performance-sensitive numeric work — and they appear often enough in technical interviews that skipping them entirely would leave a real gap.
5 & 3 # 1 AND — bits set in BOTH operands (0101 & 0011 = 0001)
5 | 3 # 7 OR — bits set in EITHER operand (0101 | 0011 = 0111)
5 ^ 3 # 6 XOR — bits set in EXACTLY ONE (0101 ^ 0011 = 0110)
~5 # -6 NOT — flips every bit (equivalent to -(x + 1))
5 << 1 # 10 Left shift — shift bits left, equivalent to multiplying by 2
5 >> 1 # 2 Right shift — shift bits right, equivalent to floor-dividing by 2755). For everyday application code, you are far more likely to reach for a plain boolean, a set, or an Enum (covered in the Object-Oriented Python phase of this track) than raw bitwise flags — but recognising this syntax immediately when you encounter it is a real, expected skill.Precedence — The Order Python Actually Evaluates In
Just like mathematics has an order of operations (PEMDAS), Python has a defined precedence for every operator. From highest to lowest priority among what you have learned so far:
1. ** (exponentiation)
2. ~ +x -x (bitwise NOT, unary plus/minus)
3. * / // % (multiplication, division, floor division, modulo)
4. + - (addition, subtraction)
5. << >> (bitwise shifts)
6. & (bitwise AND)
7. ^ (bitwise XOR)
8. | (bitwise OR)
9. == != < > <= <= in not in is is not (comparisons, membership, identity)
10. not (logical not)
11. and (logical and)
12. or (logical or)# Intention: "is the discount valid for either a member or someone spending over $100?"
is_valid = is_member or total_spent > 100 and has_coupon
# What this ACTUALLY evaluates as, because "and" binds tighter than "or":
is_valid = is_member or (total_spent > 100 and has_coupon)
# If that is not the intended logic, parentheses are required to force it:
is_valid = (is_member or total_spent > 100) and has_couponand/or, or arithmetic mixed with bitwise operators — add explicit parentheses even where they are not strictly required. It costs nothing, and it removes any ambiguity for the next person reading the code — who is very often you, six months later.:= — Assignment Inside an Expression
Introduced in Python 3.8, the walrus operator lets you assign a value to a name as part of a larger expression, instead of requiring a separate statement first. It is most useful for avoiding a value being computed twice.
import random
n = random.randint(1, 10)
if n > 5:
print(f"{n} is greater than 5")import random
if (n := random.randint(1, 10)) > 5:
print(f"{n} is greater than 5")It is also common inside while loops that read data in chunks — a pattern you will use for real in the Reading & Writing Files module:
with open("large_file.txt") as f:
while (chunk := f.read(1024)):
process(chunk)
# Without the walrus operator, this needs an extra line to assign
# "chunk" before the while condition can check it.An Austin Ride-Share Startup's Pricing Bug
An engineer writes a rule for surge pricing eligibility: apply surge pricing if demand is high AND it is a weekend, OR if it is a major holiday (holidays always get surge pricing regardless of demand).
apply_surge = is_high_demand and is_weekend or is_holidayWhat the reviewer catches
Because and binds tighter than or, this line is actually evaluated as (is_high_demand and is_weekend) or is_holiday — which happens to be exactly the intended logic in this specific case. The reviewer flags it anyway, not because it is wrong, but because nothing in the line itself tells the next reader that this was verified intentionally rather than accidentally correct.
apply_surge = (is_high_demand and is_weekend) or is_holidayThe parentheses do not change behaviour at all — Python would evaluate both versions identically. What changes is whether the next engineer reading this file (quite possibly months later, quite possibly during an incident at 2am) has to mentally re-derive Python's precedence rules to trust the logic, or can simply read it. This is precisely the kind of change that gets requested constantly in real code review, and precisely why Part 07's "always parenthesise mixed and/or" rule of thumb is not a stylistic nicety — it is a practice that prevents real, dangerous ambiguity in business-critical logic like pricing.
Four Misconceptions About Operators
5 Interview Questions — With Complete Answers
Operator Mistakes That Produce a Wrong Answer, Not an Error
Errors You Will Hit With Operators — And Exactly Why
🎯 Key Takeaways
- ✓/ always returns a float, even when the division is exact. Use // for floor division when you need an integer result — it rounds toward negative infinity, not toward zero.
- ✓Python 3 raises a TypeError for incompatible ordering comparisons (str > int) instead of silently guessing, unlike Python 2.
- ✓in/not in check membership; is/is not check identity. Membership checks are O(1) on sets and dicts but O(n) on lists — convert to a set for repeated membership checks on large collections.
- ✓and/or short-circuit and return one of their actual operands, not necessarily True/False — used deliberately for default-value patterns.
- ✓Bitwise operators (&, |, ^, ~, <<, >>) operate on the binary representation of integers — distinct from the logical and/or/not operators, and a common source of hard-to-spot bugs when confused with them.
- ✓and binds tighter than or. Always parenthesise mixed and/or expressions explicitly, even when not strictly required — it documents intent for the next reader.
- ✓The walrus operator (:=) assigns a value as part of a larger expression — most common inside while loops reading data in chunks, and in comprehensions.
What comes next
Module 04 is a deep dive into strings — indexing, slicing, the methods you will use constantly, and f-strings done properly.
Module 04 → StringsDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.