Python In-Place Operator Functions: iadd, isub, imul and More
Python's operator module exposes iadd, isub, imul and 7 more in-place functions. Traced examples show what changes between mutable lists and immutable ints.
Python’s operator module exposes iadd, isub, imul, and nine more in-place functions that turn += and -= into callable objects you can pass directly to reduce, map, and functools.partial.
The standard + operator always returns a new object. The += augmented-assignment statement works in-place for mutable types, but it is a statement. You cannot pass a statement to reduce. In-place operator functions bridge that gap. They are objects, not statements. You store them, pass them, and call them like any other function.
What In-Place Operator Functions Are
The Python operator module provides functional equivalents of every Python operator. For every standard function like operator.add(a, b), there is an in-place counterpart operator.iadd(a, b).
The difference is in what happens internally. operator.add(a, b) calls a.__add__(b) and returns a new object. operator.iadd(a, b) calls a.__iadd__(b) first. If __iadd__ exists on the type, it performs the operation in place and returns a. If __iadd__ does not exist (as with immutable types), Python falls back to calling a.__add__(b) and returns a new object.
That fallback behaviour is the source of the most common confusion about these functions, covered in detail in the section on mutable vs immutable types below.
The i prefix stands for “in-place.” The naming is consistent across the whole family: iadd, isub, imul, itruediv, ifloordiv, imod, ipow, iconcat, iand, ior.
The 10 In-Place Functions: Reference Table
| Function | Equivalent to | Dunder called | Type notes |
|---|---|---|---|
iadd(a, b) | a += b | __iadd__ | Works on numbers and sequences |
isub(a, b) | a -= b | __isub__ | Numbers and sets (set difference) |
imul(a, b) | a *= b | __imul__ | Numbers; for list, repeats elements |
itruediv(a, b) | a /= b | __itruediv__ | Always returns float |
ifloordiv(a, b) | a //= b | __ifloordiv__ | Integer floor division |
imod(a, b) | a %= b | __imod__ | Remainder after division |
ipow(a, b) | a **= b | __ipow__ | Raises a to the power b |
iconcat(a, b) | a += b (sequences) | __iadd__ | Requires both args to be sequences |
iand(a, b) | a &= b | __iand__ | Bitwise AND; set intersection |
ior(a, b) | a |= b | __ior__ | Bitwise OR; set union |
iconcat is a narrower version of iadd that explicitly requires both arguments to be sequences (list, tuple, str). It raises TypeError if either argument is not a sequence. Use iadd when your type can vary; use iconcat when you want the type assertion enforced.
Mutable vs Immutable: How the Dunder Dispatch Works
Understanding this section makes the rest of the module predictable. The Python data model for in-place operators states that if a type does not define __iadd__, the interpreter falls back to __add__. The result is always returned; but whether the original object is modified depends entirely on whether __iadd__ existed.
iadd with a list (mutable)
Lists define __iadd__. Calling it extends the list in place and returns the same list object.
from operator import iadd
a = [1, 2, 3]
result = iadd(a, [4, 5])
print(result) # [1, 2, 3, 4, 5]
print(a) # [1, 2, 3, 4, 5] — a was modified in place
print(result is a) # True — result and a are the same object
iadd(a, [4, 5])callsa.__iadd__([4, 5])list.__iadd__extends the list and returnsselfresultandapoint to the same object
iadd with an int (immutable)
Integers do not define __iadd__. Python falls back to __add__, which returns a new integer. The original variable is untouched.
from operator import iadd
x = 10
result = iadd(x, 5)
print(result) # 15
print(x) # 10 — x was NOT modified
print(result is x) # False — different objects
iadd(x, 5)triesx.__iadd__(5)— not defined onint- Python falls back to
x.__add__(5)= 15 (newintobject) xstill refers to10;resultrefers to15
iadd with a tuple (immutable)
Tuples behave the same as integers: no __iadd__, so a new tuple is returned.
from operator import iadd
t = (1, 2)
result = iadd(t, (3, 4))
print(result) # (1, 2, 3, 4)
print(t) # (1, 2) — t was NOT modified
The gotcha is subtle. Writing iadd(t, (3, 4)) reads like an in-place operation, but with a tuple you get a new object back. The name “in-place” refers to the intent, not to a guarantee. Whether in-place mutation actually happens depends on the type.
imul with a list
imul on a list repeats the elements, not concatenates. This differs from what iadd does.
from operator import imul, iadd
lst = [0]
print(imul(lst, 3)) # [0, 0, 0] — repeated 3 times
print(iadd(lst, [1])) # [0, 0, 0, 1] — extended with [1]
imul([0], 3)=[0] * 3=[0, 0, 0]iadd([0, 0, 0], [1])=[0, 0, 0, 1]- Both modify in place since list defines
__imul__and__iadd__
Using In-Place Functions in reduce and partial
Assignment operators like += are statements in Python. You cannot pass a statement to a function. In-place operator functions solve this for places where you need += behaviour as a callable.
Flattening a list of lists with reduce
The sum of array elements article covers functools.reduce with a lambda. operator.iadd replaces that lambda with a named function.
import functools
from operator import iadd
nested = [[1, 2], [3, 4], [5, 6]]
flat = functools.reduce(iadd, nested, [])
print(flat) # [1, 2, 3, 4, 5, 6]
Trace of how reduce applies iadd step by step:
- Start: accumulator =
[](the initialiser) - Step 1:
iadd([], [1, 2])extends accumulator to[1, 2]; returns same object - Step 2:
iadd([1, 2], [3, 4])extends to[1, 2, 3, 4]; returns same object - Step 3:
iadd([1, 2, 3, 4], [5, 6])extends to[1, 2, 3, 4, 5, 6]; returns same object - Final result:
[1, 2, 3, 4, 5, 6]✓
Each step mutates the accumulator list in place because list.__iadd__ exists. The return value of each iadd call becomes the accumulator for the next step, which is why the function must return the object even when modifying it in place.
Summing numbers with reduce
import functools
from operator import iadd
nums = [1, 2, 3, 4, 5]
total = functools.reduce(iadd, nums, 0)
print(total) # 15
Trace:
- Start: accumulator =
0 - Step 1:
iadd(0, 1)= 1 (new int, 0 unchanged) - Step 2:
iadd(1, 2)= 3 - Step 3:
iadd(3, 3)= 6 - Step 4:
iadd(6, 4)= 10 - Step 5:
iadd(10, 5)= 15 ✓
For integers iadd and add produce the same result because integers are immutable and both fall through to __add__. The functional gain here is clarity: iadd communicates the accumulator intent; a lambda does not.
functools.partial with iadd
functools.partial fixes one or more arguments of a function, returning a new callable. Since iadd is a regular function, partial works on it the same way it works on any callable.
from functools import partial
from operator import iadd
add_to_100 = partial(iadd, 100)
print(add_to_100(5)) # 105
print(add_to_100(25)) # 125
partial(iadd, 100)creates a function equivalent tolambda x: iadd(100, x)iadd(100, 5)returns105(new int, since int is immutable)- Useful for building specialised increment/offset functions in data-processing pipelines
For the broader collection of arithmetic programs including the simple calculator in Python, the arithmetic operators (+, -, *, /) are used directly. The operator module becomes worth reaching for when those operators need to be passed around as values.
Four Gotchas That Trip Up Students
1. Immutable types: capture the return value
For any immutable type, iadd does not rebind the caller’s variable. The result only exists if you assign it.
from operator import iadd
x = 10
iadd(x, 5) # result is discarded — x is still 10
x = iadd(x, 5) # now x is 15
2. iconcat requires sequences on both sides
iconcat asserts that both arguments are sequences before proceeding. Passing an integer raises TypeError immediately.
from operator import iconcat
iconcat([1, 2], [3, 4]) # OK: [1, 2, 3, 4]
iconcat(10, 5) # TypeError: 'int' object is not a sequence
iadd(10, 5) returns 15 without raising an error. iconcat(10, 5) fails. Know which you need.
3. imul repeats, iadd extends
On lists, these do different things. iadd extends (appends elements from the second list). imul repeats (duplicates the list’s own elements).
from operator import iadd, imul
lst = [1, 2]
print(iadd(lst, [3, 4])) # [1, 2, 3, 4]
lst = [1, 2]
print(imul(lst, 3)) # [1, 2, 1, 2, 1, 2]
4. The return value is always the result
Whether the type is mutable or immutable, the return value is the result of the operation. Do not rely on side effects alone. Always store the return value when the result matters.
For a broader set of Python programs to practise these patterns, the Python basic programs collection covers the operator fundamentals that these in-place functions extend.
The reduce(iadd, nested, []) flattening pattern is the same computational model behind map-reduce document pipelines used in LLM applications. If you want to move from Python practice problems to building something that runs against real AI APIs, TinkerLLM is a Python-accessible environment where you can extend that reduce pattern into live document processing for ₹299.
Primary sources
Frequently asked questions
Does operator.iadd actually modify the original variable?
Only if the first argument is a mutable type. For a list, iadd(a, b) calls a.__iadd__(b), which extends a in place and returns a. For an int or tuple, Python falls back to __add__ and returns a new object — the original is unchanged.
What is the difference between operator.iadd and operator.add?
operator.add(a, b) always returns a + b as a new object without touching either operand. operator.iadd(a, b) attempts in-place modification via __iadd__ first, then falls back to __add__. For mutable types, iadd modifies the object; for immutable types, both functions produce the same result.
Why do in-place functions return a value if they modify in place?
In-place functions return the result so they can be used in functional pipelines like functools.reduce, where the return value of each step becomes the accumulator for the next step. For mutable types, the returned object is the same object that was modified.
Can I use operator.iadd with functools.reduce?
Yes. reduce(iadd, [[1,2],[3,4]], []) flattens a list of lists by extending the accumulator in place at each step. For integers, reduce(iadd, [1,2,3], 0) sums the list the same way sum() does.
What is operator.iconcat and how does it differ from iadd?
iconcat(a, b) is a sequence-specific concatenation function. Both a and b must be sequences (list, tuple, str). It raises TypeError for non-sequence arguments. iadd is more general: it works on numeric types and sequences alike, falling back to __add__ when __iadd__ is absent.
A self-paced playground for building with LLMs.
TinkerLLM is FACE Prep's sister property. A guided environment for shipping real LLM applications, the kind of project that earns a paragraph on your resume, not a line.
Try TinkerLLM (₹299 launch)