PythonMastery
reference 3 min read · lesson 1 of 45 in Errors

TypeError: unsupported operand type(s) for +: 'int' and 'str'

1 · The lesson

read

What this error means

Python refuses to glue a number and a string together with +. The + operator means "add" for numbers and "concatenate" for strings, and Python won't guess which one you meant. Unlike JavaScript or PHP, Python is strongly typed: it will not silently convert 25 into "25" (or vice versa) just because you asked it to.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print("I am " + age + " years old")
TypeError: can only concatenate str (not "int") to str

Or the reverse direction:

python
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    total = 5 + "10"
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Why it happens

+ is overloaded. For int + int it adds. For str + str it concatenates. For int + str there is no sensible default — should 5 + "10" be 15 or "510"? Python refuses to choose and raises TypeError instead. Any value coming from input(), a CSV cell, or a JSON field is almost always a str, even when it looks like a number.

How to fix it

Cleanest fix — use an f-string:

python
age = 25
print(f"I am {age} years old")

Convert explicitly with str() or int():

python
age = 25
print("I am " + str(age) + " years old")     # int -> str

total = 5 + int("10")                         # str -> int

Or use .format:

python
print("I am {} years old".format(age))
+ setup added so this can run · defines age
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

age = _AutoMock('age')

Pick the conversion direction based on what the value means. If you need to do arithmetic, convert to int or float. If you just need to display it, convert to str (or use an f-string and skip the manual conversion entirely).

When you'd actually see this in real code

  • Reading numeric input from input() — it always returns a str, so input("Age: ") + 1 blows up.
  • Concatenating user IDs into a URL: "/users/" + user_id when user_id is an int from the database.
  • Building log lines: "Processed " + count + " rows" when count is a counter.
  • CSV columns are strings; trying to sum a column without int() will fail at the first row.

See Also