Operators#
Operators are how you combine values, compare them, and control evaluation order. In interviews, operator behavior shows up constantly in edge cases — integer division, chained comparisons, short-circuit logic, bitwise tricks, and the subtle difference between value comparison and identity.
How to use this page
Read arithmetic and comparison first, then logical/identity, then bitwise. Revisit Interview traps before mocks. Continue with Control Flow and Strings.
At a glance
| Track | Python Basics |
| Sections | 9 major topics |
| Outline | Use the right-hand TOC to jump |
Topics: How Python evaluates expressions · Arithmetic operators · Comparison operators · Logical operators · Membership and identity operators · Bitwise operators · Assignment operators · Operator precedence (highest → lowest) · … (+1 more)
- How Python evaluates expressions
- Arithmetic operators
- Comparison operators
- Logical operators
- Membership and identity operators
- Bitwise operators
- Assignment operators
- Operator precedence (highest → lowest)
- Type-specific operator behavior
How Python evaluates expressions#
Before individual operators, understand the evaluation model:
- Operands are objects — operators invoke special methods (
__add__,__eq__, etc.) on those objects. - Most operators create or return objects — even
and/orreturn an operand, not necessarilybool. - Short-circuit operators (
and,or) skip evaluating the right side when the result is already determined. - Precedence decides grouping when parentheses are absent — when unsure, add parentheses.
class Box:
def __init__(self, value):
self.value = value
def __add__(self, other):
return Box(self.value + other.value)
Box(2) + Box(3) # Box(5) — + dispatches to __add__
You rarely define __add__ in interviews, but knowing operators are syntax sugar for method calls explains why "a" + 1 raises TypeError while 1 + 1.5 works (int defines __add__ accepting float).
Arithmetic operators#
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 3 + 2 |
5 |
- |
Subtraction | 5 - 2 |
3 |
* |
Multiplication | 4 * 3 |
12 |
/ |
True division | 7 / 2 |
3.5 (always float) |
// |
Floor division | 7 // 2 |
3 |
% |
Modulo (remainder) | 7 % 2 |
1 |
** |
Exponentiation | 2 ** 10 |
1024 |
+x, -x |
Unary plus/minus | -(-5) |
5 |
True division vs floor division vs modulo#
Python 3 made / always return a float. This differs from C, Java, and Python 2:
| Expression | Python 3 | C/Java (int/int) |
|---|---|---|
7 / 2 |
3.5 |
3 |
7 // 2 |
3 |
3 |
-7 / 2 |
-3.5 |
-3 |
-7 // 2 |
-4 |
-3 |
Floor division rounds toward negative infinity, not toward zero:
import math
7 // 2 # 3
-7 // 2 # -4 (math.floor(-3.5) == -4)
7 // -2 # -4
-7 // -2 # 3
# Toward-zero truncation (like C) — use int() on float division
int(-7 / 2) # -3
The divmod identity (always holds in Python 3):
a, b = 17, 5
q, r = divmod(a, b)
assert q == a // b
assert r == a % b
assert a == b * q + r
# divmod(17, 5) → (3, 2)
Modulo sign follows the divisor (b), not the dividend:
Code & explanation — clock arithmetic
- Double modulo handles negative dividends cleanly.
- Use this in rotating array / cyclic string problems instead of manual
if n < 0.
Exponentiation nuances#
2 ** 10 # 1024
2 ** 0.5 # 1.414... — float result
pow(2, 10) # same as 2 ** 10
pow(2, 10, 1000) # 24 — modular exponent (2^10 % 1000), efficient for large exponents
# Right-associative: 2 ** 3 ** 2 == 2 ** (3 ** 2) == 2 ** 9
2 ** 3 ** 2 # 512, not 64
math.pow always returns float; ** preserves int when both operands are int and result is integral.
Augmented assignment (+=, -=, …)#
| Operator | Example |
|---|---|
+= |
x += 1 |
-= |
x -= 1 |
*= |
x *= 2 |
/= |
x /= 2 |
//= |
x //= 2 |
%= |
x %= 2 |
**= |
x **= 2 |
&=, \|=, ^=, <<=, >>= |
Bitwise compound |
Critical nuance: augmented assignment may mutate in place or rebind depending on type:
# Immutable — creates new object, rebinds name
s = "hi"
id_before = id(s)
s += "!"
id(s) != id_before # True — new str object
# Mutable list — often mutates in place
nums = [1, 2]
id_before = id(nums)
nums += [3] # calls __iadd__ → extend-like
id(nums) == id_before # True — same list object
# BUT: rebind vs mutate depends on object
t = (1, 2)
# t += (3,) # creates NEW tuple, rebinds t — tuples are immutable
| Type | += behavior |
|---|---|
int, float, str, tuple |
New object, rebind |
list, dict, set |
In-place mutation when __iadd__/update applies |
list with + |
Always new list: a = a + [x] differs from a += [x] for some custom classes |
a = [1, 2]
b = a
a = a + [3] # new list; b still [1, 2]
a = [1, 2]
b = a
a += [3] # mutates; b is [1, 2, 3]
Comparison operators#
| Operator | Meaning | Calls |
|---|---|---|
== |
Equal value | __eq__ |
!= |
Not equal | __ne__ |
<, >, <=, >= |
Ordering | __lt__, __gt__, etc. |
3 == 3.0 # True — numeric equality across types
"a" < "b" # True — lexicographic (Unicode code point order)
[1, 2] < [1, 3] # True — lexicographic on sequences
(1, 2) < (1, 3) # True
None == None # True — but prefer `is None` for identity
Chained comparisons#
Python evaluates chained comparisons as and of pairwise comparisons — without re-evaluating middle operands:
lo <= x <= hi # lo <= x and x <= hi
1 < a < b < 10
# Middle operand evaluated once — matters with side effects
# x is evaluated once in: lo <= x <= hi
This is idiomatic for range checks and avoids repeating variables.
Comparing different types#
Python 3 does not compare arbitrary incompatible types (unlike Python 2):
# "3" < 3 # TypeError in Python 3
[] < () # False — compares by type name order (implementation detail, avoid relying on)
For interviews: ensure comparable types before sorting or using <.
Float and NaN comparisons#
float("nan") == float("nan") # False — NaN is not equal to itself
float("nan") != float("nan") # True
import math
math.isnan(x) # correct NaN test
Never use == to compare floats for equality in production — use math.isclose:
Identity vs equality (with operators)#
| Operator | Compares | Example |
|---|---|---|
== |
Values | [1,2] == [1,2] → True |
is |
Identity (same object) | [1,2] is [1,2] → False |
See Python Language Fundamentals for the full is/== guide.
Logical operators#
| Operator | Behavior |
|---|---|
and |
Short-circuit: first falsy operand, or last if all truthy |
or |
Short-circuit: first truthy operand, or last if all falsy |
not |
Always returns True or False |
0 or 42 # 42 — not True/False!
"" or "default" # "default"
1 and 2 and 3 # 3
0 and expensive() # expensive() never called
[] or [1] # [1]
Return value is the operand itself, not coerced to bool — this enables patterns:
# Default fallback
name = user_input or "anonymous"
# Conditional execution
callback and callback()
# Guard chaining
if user and user.is_active and user.has_permission("read"):
...
De Morgan's laws (refactoring conditions)#
Useful when simplifying negated compound conditions.
Boolean operators vs bitwise operators#
True & False # False — bitwise AND on bool (both evaluated!)
True and False # False — short-circuit
# Never use & / | for logical conditions on booleans — no short-circuit
&, |, ^ on bools evaluate both sides — use and/or for logic.
Membership and identity operators#
| Operator | Tests | Typical complexity |
|---|---|---|
in / not in |
Value in container | See table below |
is / is not |
Same object | O(1) |
in complexity by container type#
| Container | x in container |
|---|---|
list, tuple |
O(n) linear scan |
dict |
O(1) avg — tests keys, not values |
set |
O(1) avg |
str |
O(n·m) substring search (CPython uses optimized algorithms) |
3 in [1, 2, 3] # True
"py" in "python" # True — substring
"a" in {"a": 1} # True — key membership
1 in {"a": 1} # False — 1 is not a key
Three ways to test dict membership#
d = {"name": "Alice", "age": 30}
# Key exists?
"name" in d
d.keys() # same for `in`
# Value exists? (O(n))
30 in d.values()
# Key-value pair?
("name", "Alice") in d.items()
For value lookup, invert the map or maintain a secondary index if needed frequently.
Bitwise operators#
Essential for Bit Manipulation:
| Operator | Meaning | Example |
|---|---|---|
& |
AND | 5 & 3 → 1 (101 & 011 = 001) |
\| |
OR | 5 \| 3 → 7 |
^ |
XOR | 5 ^ 3 → 6 |
~ |
NOT (invert bits) | ~0 → -1 (two's complement) |
<< |
Left shift | 1 << 3 → 8 |
>> |
Right shift | 8 >> 2 → 2 |
Negative numbers use two's complement — bitwise on negatives is error-prone in interviews. Stick to non-negative inputs unless the problem requires otherwise.
Core bitwise identities#
x & 1 # LSB — 1 if odd, 0 if even
x >> 1 # floor divide by 2
x << 1 # multiply by 2
x ^ x # 0
x ^ 0 # x
x & (x - 1) # clear lowest set bit
x & -x # isolate lowest set bit
Interview patterns (multiple approaches)#
Check odd/even:
Count set bits:
def popcount(n: int) -> int:
count = 0
while n:
n &= n - 1
count += 1
return count
# Or: bin(n).count("1") — readable, slightly slower
Power of two:
Swap without temp (XOR — know conceptually, rarely use in Python):
a, b = b, a # Pythonic — use this in interviews
# XOR swap: a ^= b; b ^= a; a ^= b — don't use in real Python code
Subset enumeration via bitmask:
def all_subsets(nums: list[int]) -> list[list[int]]:
n = len(nums)
result = []
for mask in range(1 << n):
result.append([nums[i] for i in range(n) if mask & (1 << i)])
return result
Set algebra: operators vs methods#
a = {1, 2, 3}
b = {3, 4, 5}
a | b # union: {1, 2, 3, 4, 5}
a & b # intersection: {3}
a - b # difference: {1, 2}
a ^ b # symmetric difference: {1, 2, 4, 5}
# Method form — accepts any iterable
a.union([6, 7])
a.update([6]) # in-place union
| In-place method | Operator equivalent |
|---|---|
s \|= t |
s.update(t) |
s &= t |
s.intersection_update(t) |
s -= t |
s.difference_update(t) |
s ^= t |
s.symmetric_difference_update(t) |
Assignment operators#
| Form | Meaning |
|---|---|
= |
Bind name to object |
:= (walrus, 3.8+) |
Assign and return value in expression |
Walrus operator — when and when not#
# Good: avoid double call / double lookup
while (line := input()) != "quit":
process(line)
if (match := pattern.search(text)):
print(match.group())
# Good: reuse expensive computation in comprehension filter
results = [y for x in data if (y := transform(x)) > threshold]
# Avoid: walrus where simple assignment before block is clearer
if (n := len(nums)) > 0: # fine
...
Operator precedence (highest → lowest)#
| Level | Operators | Notes |
|---|---|---|
| 1 | ( ), [ ], { } |
Grouping, subscription, display |
| 2 | ** |
Right-associative: 2**3**2 = 2**(3**2) |
| 3 | +x, -x, ~x |
Unary |
| 4 | *, /, //, % |
|
| 5 | +, - |
Binary |
| 6 | <<, >> |
|
| 7 | & |
|
| 8 | ^ |
|
| 9 | \| |
|
| 10 | Comparisons (==, <, in, is, …) |
Chained |
| 11 | not |
|
| 12 | and |
|
| 13 | or |
|
| 14 | Conditional (x if c else y) |
Ternary |
| 15 | Lambda | |
| 16 | =, +=, …, := |
Assignment |
When in doubt, use parentheses.
Type-specific operator behavior#
Strings and sequences#
"hello" + " world" # concatenation — O(n+m) new string
"ha" * 3 # "hahaha" — repetition
[1, 2] + [3] # [1, 2, 3] — new list
[0] * 5 # five references to same 0 (immutable, OK)
[[0]] * 3 # BUG if inner mutable — three refs to same list
Interview trap — nested multiplication
Lists: + vs += vs extend#
a = [1, 2]
b = a + [3] # new list [1,2,3]; a unchanged
a += [3] # mutates a in place
a.extend([3]) # same effect as += for lists
Interview traps (quick reference)#
| Trap | What goes wrong | Safe approach |
|---|---|---|
/ vs // |
Expecting integer from / |
Use // for floor division |
Negative // and % |
Differs from C/Java | Test edge cases; use divmod |
is for values |
Wrong identity check | Use == for content |
[x] * n nested |
Shared inner references | List comprehension |
| Short-circuit side effects | Right side skipped | Don't rely on side effects in and/or |
& vs and |
Both sides evaluated with & |
Use and/or for logic |
Float == |
Precision errors | math.isclose |
| NaN comparison | Always False for == |
math.isnan |
in on dict |
Tests keys only | Use .values() or invert map |
2 ** 3 ** 2 |
Right-associative | Parenthesize explicitly |
Mental model checklist#
- What is the difference between
/and//for negative operands? - How does
divmod(a, b)relate to//and%? - What does
and/orreturn — always bool? - Why is
x is Nonepreferred overx == None? - What is the O(.) complexity of
x in my_listvsx in my_set? - How do you test whether a number is a power of two with one bitwise expression?
What's next#
| Topic | Page |
|---|---|
| Branching and loops | Control Flow |
| String operations | Strings |
| Collections | Data Structures |
| Bit manipulation patterns | Bit Manipulation |