Placement Prep

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.

By FACE Prep Team 6 min read
python operator-module in-place-operators functional-python placement-prep python-programming

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

FunctionEquivalent toDunder calledType 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]) calls a.__iadd__([4, 5])
  • list.__iadd__ extends the list and returns self
  • result and a point 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) tries x.__iadd__(5) — not defined on int
  • Python falls back to x.__add__(5) = 15 (new int object)
  • x still refers to 10; result refers to 15

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 to lambda x: iadd(100, x)
  • iadd(100, 5) returns 105 (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.

Build AI projects

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)
Free AI Roadmap PDF