
In Python, the + and += operators have two completely different jobs: they concatenate strings ("a" + "b" becomes "ab") and they add numbers (10 + 5 becomes 15). Unlike JavaScript or Java, Python is strongly typed and refuses to guess which one you meant. When the value on the left side of + (or +=) is a str and the value on the right side is an int, Python immediately halts with TypeError: can only concatenate str (not "int") to str.
This error happens in two opposite situations: either you tried to build a text message like "Order #" + 1042 without converting the number to a string, or you tried to do math like user_age + 1 (or total += 10) without realizing that the left-hand variable is actually a string ("25" or "0").
Reading the can only concatenate str Traceback
Read the traceback from the bottom up. Because this exact error is raised by str.__add__(), it always means the expression on the left side of + or += evaluated to a str, and the expression on the right side evaluated to the type named inside the double quotes ("int", "numpy.int64", "UUID", "bool", or "NoneType"):
Traceback (most recent call last):
File "/var/www/app/main.py", line 2, in <module>
print("Order #" + order_id)
TypeError: can only concatenate str (not "int") to str- Python 3.11+ Carets: Starting in Python 3.11+ (not run by me; noted from documentation), the traceback draws
~~~~~~~~~~^~~~~~~~~~carets under the binary+expression, pointing directly to the+operator that failed.
Tested Environment: Python 3.10.16, NumPy 1.26.3 & Pandas 2.0.0 (Linux) | Python 3.11+ carets, Pandas 3.0+, Polars, and linter/type-checker behaviors not run by me; noted from documentation.
Part of: The Ultimate Python Debugging Guide
⚡ The Quick Fix: Choose Text Formatting or Numeric Addition
Fix the error based on whether you intended to build a string or calculate a number:
1. When You Wanted Text (“Order #1042”): Use an f-String or str()
Modern Python f-strings (f"...", introduced in Python 3.6) automatically format integers, floats, dates, UUIDs, and booleans into strings without clumsy + str(...) chains:
order_id = 1042
# Fails: "Order #" is a str, order_id is an int
# print("Order #" + order_id)
# Best Fix: Use an f-string
print(f"Order #{order_id}") # Prints: Order #1042
# Alternative 1: Explicitly convert with str()
print("Order #" + str(order_id)) # Prints: Order #1042
# Alternative 2: Pass comma-separated arguments to print()
print("Order #", order_id) # Prints: Order # 10422. When You Wanted Math (“25” + 1 to 26): Convert the String to int()
If the left-hand variable is a numeric string (from input(), os.getenv(), or a file), convert it with int() before adding. (⚠️ Trap: Never “fix” a math line by wrapping the right-hand number in str(1)—writing "25" + str(1) concatenates the strings into "251" instead of calculating 26!)
raw_age = "25" # e.g., returned from input("Enter age: ")
# Optional input: Fall back to a sensible default if blank or invalid
def add_years_optional(val, years=1, default=0):
try:
return int(val) + years
except (ValueError, TypeError):
return default
# Required input: Validate and raise a meaningful error if invalid
def add_years_strict(val, years=1):
try:
return int(val) + years
except (ValueError, TypeError) as err:
raise ValueError(f"Expected integer age, got {val!r}") from err
print(add_years_strict(raw_age, 1)) # Prints: 26⏱️ The 10-Second Diagnostic: What Your TypeError: can only concatenate str (not “int”) to str Looks Like
Find the pattern that matches your crashing line to grab the exact fix in under 15 seconds:
| What the Code Looks Like | Root Cause | The Exact Fix |
|---|---|---|
| “Order #” + 1042 | Building text with +. | Use an f-string: f”Order #{1042}” |
| user_age + 1 (where user_age = “25”) | input() and APIs return strings. | Convert to number: int(user_age) + 1 |
| total += price (where total = “0”) | Accumulator initialized as str instead of int. | Initialize as integer: total = 0 |
| “Total: %d” % 10 + 5 | % has higher precedence than +. | Use an f-string: f”Total: {10 + 5}” |
| “Price: $” + 19.99 | + cannot combine str and float. | Format it: f”Price: ${19.99:.2f}” |
| “Hi ” + user.get(“name”) | dict.get() returned None. | Coalesce: “Hi ” + (name or “Guest”) |
| df[“prefix”] + df[“id”] | Pandas column is int64, not str. | Cast column: df[“id”].astype(str) |
Can You Fix This TypeError?
order_id = 1042 message = "Order #" + order_id print(message)
Choose the best fix:
Table of contents
- Reading the can only concatenate str Traceback
- ⚡ The Quick Fix: Choose Text Formatting or Numeric Addition
- Inspect Which Side of + Is a String and Which Is an Integer
- Common Causes of TypeError: can only concatenate str (not “int”) to str
- 1. Building Strings with + Instead of f-Strings (JavaScript / Java Habit)
- 2. Doing Math on input() , os.getenv() , or JSON Strings
- 3. The += Operator Traps (String Building & String Accumulators)
- 4. Concatenating len() , range() , and enumerate() Loop Counters
- 5. Operator Precedence Traps with % String Formatting ( “%d” % 10 + 5 )
- 6. NumPy Scalars ( not “numpy.int64” ) from Array or DataFrame Indexing
- 7. Concatenating datetime , UUID , and bool Objects to a String
- 8. Concatenating float , NoneType , list , or bytes to a String
- 9. Concatenating String and Integer Columns in Pandas ( df[“code”] + df[“id”] ) and Polars
- Why “10” + 5 and 5 + “10” Raise Different TypeError Messages
- Sibling String & Operator TypeError Variants
- Catch str + int Bugs Before Runtime with Type Checkers and Linters
- Worked Example: Fixing a String Price in an API Checkout Calculation
- Frequently Asked Questions
- Official Documentation & Related Fixes
Inspect Which Side of + Is a String and Which Is an Integer
When you have a line like result = a + b + c where all three variables look like numbers when printed, drop these two lines right above the crash to inspect type() and repr() (which wraps strings in single quotes '25' so they stand out immediately from integers 10):
left = "25"
right = 10
print(f"left: type={type(left).__name__:<6} | value={left!r}")
print(f"right: type={type(right).__name__:<6} | value={right!r}")Verified Output (Python 3.10.16):
left: type=str | value='25' right: type=int | value=10
Common Causes of TypeError: can only concatenate str (not "int") to str
Here are the nine most frequent ways str + int (and its sibling type mismatches) sneak into Python code.
1. Building Strings with + Instead of f-Strings (JavaScript / Java Habit)
In JavaScript ("Score: " + 10) and Java, the + operator automatically coerces numbers into strings whenever one side is a string. Python intentionally forbids implicit string coercion so that accidental string variables don’t silently turn math like "10" + 5 into "105".
- The Trigger (Verified 3.10.16):
score = 100 msg = "Your score is " + score + " points." # TypeError: can only concatenate str (not "int") to str
- The Fix: Replace
+concatenation with an f-string:
score = 100
msg = f"Your score is {score} points."
print(msg) # Your score is 100 points.- How to tell it’s this one: The left side of
+is a literal string in quotes ("Order #","Score: ",",").
2. Doing Math on input(), os.getenv(), or JSON Strings
Built-in input(), environment variable lookups (os.getenv()), command-line arguments (sys.argv), CSV readers (csv.reader), and unvalidated JSON fields always return strings, even if the value looks like digits. When that string variable sits on the left side of + (user_age + 1), Python sees str + int and raises can only concatenate str (not "int") to str—even though you were trying to do addition, not string concatenation!
- The Trigger (Verified 3.10.16):
user_age = "25" # Returned by input("Enter your age: ")
next_year = user_age + 1
# TypeError: can only concatenate str (not "int") to str- The Fix: Wrap
input(),os.getenv(), or the JSON field inint()(orfloat()) when reading the value:
user_age = int("25")
next_year = user_age + 1
print(next_year) # Prints: 26- How to tell it’s this one: Both variables represent numbers in your business logic, and the left variable came from
input(),os.getenv(),sys.argv, a CSV row, or an API payload.
3. The += Operator Traps (String Building & String Accumulators)
Augmented assignment (+=) on a string triggers this error in two common ways:
- Appending an Integer to a Log/Message String (
msg += 10): Unlikelist.__iadd__(wheremy_list += "42"works because strings are iterable),strhas no special+=coercion and raisesTypeError: can only concatenate str (not "int") to str. - The String Accumulator Trap (
total = "0"): If you accidentally initialize a counter or running total as the string"0"(or seed it from an uncast CSV/JSON stringtotal = row["start_balance"]) instead of the integer0, the very firsttotal += priceinside your loop crashes! (⚠️ Trap: Never fixtotal += priceby writingtotal += str(price)—for[10, 20, 30], that concatenates digits onto"0"and produces the string"0102030"instead of60!)
- The Trigger (Verified 3.10.16):
# Trap A: Appending an int to a string with +=
msg = "Score: "
msg += 10 # TypeError: can only concatenate str (not "int") to str
# Trap B: Initializing a numeric accumulator as a string "0"
total = "0"
for price in [10, 20, 30]:
total += price # TypeError: can only concatenate str (not "int") to str- The Fix: Use
msg += str(10)when appending text, and initialize numeric accumulators asintorfloat(total = 0):
msg = "Score: "
msg += str(10)
total = 0
for price in [10, 20, 30]:
total += price
print(msg, "| Total:", total) # Score: 10 | Total: 60- How to tell it’s this one: The traceback points to a line with
+=.
4. Concatenating len(), range(), and enumerate() Loop Counters
Built-in len() always returns an int, and loop helpers like range() and enumerate() yield int indices. Concatenating them directly into status messages or dictionary keys with + fails on the first iteration.
- The Trigger (Verified 3.10.16):
items = ["apple", "banana"]
# Fails: len(items) returns int 2
print("Total items: " + len(items))
# Fails: 'idx' from enumerate() or range() is an int
for idx, item in enumerate(items):
print("Row " + idx + ": " + item)- The Fix: Format
len(items)and loop counters inside an f-string:
items = ["apple", "banana"]
print(f"Total items: {len(items)}")
for idx, item in enumerate(items, start=1):
print(f"Row {idx}: {item}")- How to tell it’s this one: The right side of
+islen(...)or a loop index variable (i,idx,count) fromrange()orenumerate().
5. Operator Precedence Traps with % String Formatting ("%d" % 10 + 5)
In legacy Python % string formatting, the % operator has the exact same precedence as multiplication * and division /, which is higher than +. If you write "Total: %d" % 10 + 5 intending to format 10 + 5, Python actually evaluates ("Total: %d" % 10) + 5 first—turning the left side into "Total: 10" and crashing when it tries to add + 5!
- The Trigger (Verified 3.10.16):
# Python evaluates ("Total: %d" % 10) first -> "Total: 10" + 5
msg = "Total: %d" % 10 + 5
# TypeError: can only concatenate str (not "int") to str- The Fix: Switch to an f-string (
f"Total: {10 + 5}"), or wrap the math expression in parentheses ("Total: %d" % (10 + 5)):
msg = f"Total: {10 + 5}"
print(msg) # Prints: Total: 15How to tell it’s this one: The failing line combines % string formatting and + without parentheses around the math.
Can You Fix This TypeError?
user_age = "25" next_year = user_age + 1 print(next_year)
Choose the best fix:
6. NumPy Scalars (not "numpy.int64") from Array or DataFrame Indexing
When you extract a single integer from a NumPy array (arr[0]) or a Pandas Series (df["id"].iloc[0]), you get a NumPy scalar object (numpy.int64) rather than a standard Python int.
- When
np.int64is on the right ("Val: " + np.int64(42)): Python raisesTypeError: can only concatenate str (not "numpy.int64") to str. - When
np.int64is on the left (np.int64(42) + "Val"): NumPy’s universal function (ufunc) dispatcher intercepts the+operator and raisesUFuncTypeError: ufunc 'add' did not contain a loop with signature matching types (dtype('int64'), dtype('<U3')) -> None.
- The Trigger (Verified NumPy 1.26.3):
import numpy as np
ids = np.array([101, 102])
print("User_" + ids[0])
# TypeError: can only concatenate str (not "numpy.int64") to str- The Fix: Use an f-string (
f"User_{ids[0]}"), convert withstr(ids[0]), or extract a native Python integer withids[0].item().
7. Concatenating datetime, UUID, and bool Objects to a String
In web frameworks (Django, FastAPI, Flask) and database scripts, developers frequently concatenate timestamps, UUID primary keys, or boolean flags directly into URLs, cache keys, or log strings:
datetime.dateordatetime.datetime("Date: " + d): RaisesTypeError: can only concatenate str (not "datetime.date") to str. Fix withf"Date: {d:%Y-%m-%d}"or"Date: " + d.isoformat().uuid.UUID("/users/" + user_id): RaisesTypeError: can only concatenate str (not "UUID") to str. Even though UUIDs look like hyphenated strings when printed,uuid.uuid4()returns aUUIDobject. Fix withf"/users/{user_id}"orstr(user_id).bool("Active: " + True): RaisesTypeError: can only concatenate str (not "bool") to str. Even thoughboolis a subclass ofintin Python (soTrue + 1evaluates to2!),str.__add__rejects booleans just like integers. Fix withf"Active: {flag}".- The Trigger (Verified 3.10.16):
import datetime, uuid
today = datetime.date(2026, 10, 31)
user_uuid = uuid.UUID("12345678-1234-5678-1234-567812345678")
is_active = True
# Each line below raises TypeError: can only concatenate str (not "...") to str:
# print("Date: " + today) # not "datetime.date"
# print("ID: " + user_uuid) # not "UUID"
# print("Active: " + is_active) # not "bool"- The Fix: Combine all three cleanly inside an f-string:
print(f"Date: {today.isoformat()} | ID: {user_uuid} | Active: {is_active}")8. Concatenating float, NoneType, list, or bytes to a String
Python raises the same can only concatenate str (not "...") to str message for other core types:
not "float"("$" + 19.99): Format the float with an f-string (f"${price:.2f}") to control decimal places cleanly.not "NoneType"("User: " + user.get("name")): A dictionary lookup,os.getenv(), or regex search returnedNone(see ourNoneTypeAttributeError Guide). Coalesce optional values with"User: " + (user.get("name") or "Guest"), or validate required values first.not "list"("Tags: " + ["python", "seo"]): Convert the list into a string with", ".join(tags)before concatenating:"Tags: " + ", ".join(tags).not "bytes"("Hello " + b"world"vs.b"Hello " + "world"): Puttingstron the left ("Hello " + b"world") raisesTypeError: can only concatenate str (not "bytes") to str, while puttingbyteson the left (b"Hello " + "world") raisesTypeError: can't concat str to bytes. Decode thebytesobject first (raw_bytes.decode("utf-8")) if you want astr, or encode thestr(text.encode("utf-8")) if you wantbytes.
9. Concatenating String and Integer Columns in Pandas (df["code"] + df["id"]) and Polars
In Pandas 2.0.0, combining a string (object or string) column and an integer (int64) column with + raises a long traceback ending in TypeError: can only concatenate str (not "int") to str.
- The Trigger (Verified Pandas 2.0.0):
import pandas as pd
df = pd.DataFrame({"code": ["ORD", "ORD"], "id": [101, 102]})
# Fails: 'code' is object (str), 'id' is int64
df["label"] = df["code"] + "-" + df["id"]
# TypeError: can only concatenate str (not "int") to str- The Fix (Pandas & Polars):
- In Pandas: Convert the integer column to strings first with
.astype(str):
import pandas as pd
df = pd.DataFrame({"code": ["ORD", "ORD"], "id": [101, 102]})
df["label"] = df["code"] + "-" + df["id"].astype(str)
print(df["label"].tolist()) # ['ORD-101', 'ORD-102']- ⚠️ Missing Values Note (Pandas 2.x vs. 3.0+): When a numeric column contains
NoneorNaN, Pandas defaults the column tofloat64. In Pandas 2.0.0 (verified),.astype(str)converts missing values into literal text (['101.0', 'nan'], or['101', '<NA>']via"Int64"). In Pandas 3.0+ (not run by me; noted from documentation),.astype(str)uses the nullable string dtype so missing values stay missing (['101.0', nan]onfloat64, and['101', nan]via"Int64"). To avoid.0suffixes across all versions, cast to"Int64"first (df["id"].astype("Int64").astype(str)). - In Polars (not run by me; noted from documentation): Use
pl.format("{}-{}", pl.col("code"), pl.col("id"))or cast the integer column withpl.col("id").cast(pl.String)before concatenating.
Why "10" + 5 and 5 + "10" Raise Different TypeError Messages
A frequent point of confusion is why swapping the two sides of + changes the error message entirely:
| Expression | Verified Error Message (Python 3.10.16) | Why Python Raises This Specific Message |
"10" + 5 (str on left, int on right) | TypeError: can only concatenate str (not "int") to str | Python calls the left object’s "10".__add__(5) method. Because str.__add__ only accepts str instances, it immediately raises this string-specific TypeError. |
5 + "10" (int on left, str on right) | TypeError: unsupported operand type(s) for +: 'int' and 'str' | Python calls the left object’s (5).__add__("10") method, which returns NotImplemented. Python then checks "10".__radd__(5) (which does not exist) and raises the generic binary operator TypeError. |
np.int64(10) + "5" (NumPy int64 on left) | UFuncTypeError: ufunc 'add' did not contain a loop with signature matching types... | NumPy’s array add ufunc intercepts + and fails because no numeric addition loop exists between int64 and Unicode strings. |
In all three cases, the fix is identical: use f"{a}{b}" if you want a string, or int(a) + int(b) if you want arithmetic addition.
Sibling String & Operator TypeError Variants
Here are the closely related TypeError variants you will hit when mixing strings, bytes, and numbers across other Python operations (all verified on Python 3.10.16):
| Error Message Variant (Verified 3.10.16) | What Triggered It | The Fix |
TypeError: unsupported operand type(s) for +: 'int' and 'str' | Adding int + str (10 + "5"), or calling sum(["a", "b"]) (because sum() starts at integer 0 and computes 0 + "a"). | Convert "5" with int("5") for math, or use "".join(["a", "b"]) instead of sum() to join strings. |
TypeError: unsupported operand type(s) for -: 'str' and 'int' | Subtracting an integer from a string ("10" - 5), or using / or // with a string. | Convert the string to a number first: int("10") - 5. |
TypeError: can't concat str to bytes | Adding bytes + str (b"a" + "b"), the reverse of "a" + b"b". | Decode the bytes (b"a".decode("utf-8") + "b") or encode the string (b"a" + "b".encode("utf-8")). |
TypeError: sequence item 0: expected str instance, int found | Passing a list containing integers to str.join(), such as ", ".join([1, 2, 3]). | Convert each item to a string inside a generator expression: ", ".join(str(x) for x in [1, 2, 3]). |
TypeError: can't multiply sequence by non-int of type 'str' | Multiplying two strings ("10" * "5") or multiplying a list by a string (["a"] * "5"). | Convert the multiplier to an int ("10" * int("5")), or convert both sides to int for math (int("10") * int("5")). |
TypeError: can only concatenate list (not "int") to list | Trying to append an integer to a list using + ([1, 2] + 3 instead of [1, 2] + [3]). | Use my_list.append(3) (in place) or wrap the item in brackets: [1, 2] + [3]. |
Catch str + int Bugs Before Runtime with Type Checkers and Linters
Static analysis tools flag mismatched + operands and legacy % formatting traps in your editor before you run the script (linter and type checker behaviors noted from documentation):
- Pyright / Pylance (
reportOperatorIssue) & Mypy (operator): Both Mypy (under theoperatorerror code) and Pyright / Pylance (underreportOperatorIssue) immediately underline"Order #" + order_idoruser_age + 1in red when one side is typed asstrand the other asint. - Ruff (
UP031,UP032) & Pylint (C0209consider-using-f-string): These rules flag legacy%formatting and.format()calls and convert them to f-strings, which directly prevents the"%d" % 10 + 5operator precedence trap covered in Cause #5.
Worked Example: Fixing a String Price in an API Checkout Calculation
Let’s walk through a realistic bug where an e-commerce checkout function crashes because an external JSON payload returned "price" as a string "49" instead of an integer 49.
The Code (/var/www/app/main.py):
def calculate_total(item_payload, shipping_fee):
return item_payload["price"] + shipping_fee
payload = {"item": "Keyboard", "price": "49"}
print(calculate_total(payload, 10))Verified Traceback (Python 3.10.16):
Traceback (most recent call last):
File "/var/www/app/main.py", line 5, in <module>
print(calculate_total(payload, 10))
File "/var/www/app/main.py", line 2, in calculate_total
return item_payload["price"] + shipping_fee
TypeError: can only concatenate str (not "int") to strThe Thought Process & Fix:
- Line 2 attempts
item_payload["price"] + shipping_fee. - Because
item_payload["price"]is the string"49"(on the left of+) andshipping_feeis the integer10(on the right of+), Python calls"49".__add__(10)and raisesTypeError: can only concatenate str (not "int") to str. - The Result: Convert
"price"to a number before addingshipping_fee. 💡 Production Note on Decimal Prices: Real API payloads frequently store prices with cents like"49.99", which makesint("49.99")raiseValueError: invalid literal for int() with base 10. For whole-number quantities, useint(); for currency and financial totals, useDecimalfrom Python’s built-indecimalmodule to avoid floating-point rounding errors:
from decimal import Decimal, InvalidOperation
# Approach A (Optional whole-number field): Default to 0 if missing or invalid
def calculate_total_optional(item_payload, shipping_fee, default_price=0):
try:
price = int(item_payload.get("price", default_price))
except (ValueError, TypeError):
price = default_price
return price + shipping_fee
# Approach B (Required / Production Money): Validate with Decimal
def calculate_total_strict(item_payload, shipping_fee):
try:
price = Decimal(str(item_payload["price"]))
except (KeyError, InvalidOperation, TypeError) as err:
raise ValueError(f"Invalid or missing 'price' in payload: {item_payload!r}") from err
return price + Decimal(str(shipping_fee))
payload = {"item": "Keyboard", "price": "49.99"}
print("Total:", calculate_total_strict(payload, "10.00"))
# Verified Output: Total: 59.99Can You Fix This TypeError?
df = pd.DataFrame({"code": ["ORD"], "id": [101]}) df["label"] = df["code"] + "-" + df["id"] print(df["label"])
Choose the best fix:
Frequently Asked Questions
"10" * 3 work in Python ("101010"), while "10" + 3 raises a TypeError?In Python, multiplying a sequence (like a str, list, or tuple) by an integer n is explicitly defined as sequence repetition—repeating the string n times ("10" * 3 produces "101010"). By contrast, + on a string is defined strictly as string concatenation, which requires both sides to be strings so Python never confuses "10" + 3 ("103" vs. 13).
Use an f-string (f"Order #{order_id}: ${total:.2f}"). Not only are f-strings more readable than "Order #" + str(order_id), they are compiled directly into dedicated string-building bytecode in CPython and are usually faster than both + str() and .format().
sum(["a", "b", "c"]) raise TypeError: unsupported operand type(s) for +: 'int' and 'str'?Built-in sum(iterable, start=0) defaults its starting value to the integer 0. On the very first step, Python attempts 0 + "a", which fails because you cannot add an int and a str (and Python explicitly blocks sum(strings, "") for performance reasons). Always use "".join(["a", "b", "c"]) to concatenate a list of strings.
[1, 2, 3] into a comma-separated string "1, 2, 3"?Pass a generator expression that converts each integer to a str inside .join(): ", ".join(str(x) for x in [1, 2, 3]). Calling ", ".join([1, 2, 3]) directly without str(x) raises TypeError: sequence item 0: expected str instance, int found.
Official Documentation & Related Fixes
- Master Guide: The Ultimate Python Debugging Guide: Tracebacks, Exceptions, and Fixes
- Related Error: How to Fix ValueError: invalid literal for int() with base 10
- Related Error: How to Fix TypeError: list indices must be integers or slices, not str
- Related Error: How to Fix AttributeError: ‘NoneType’ object has no attribute ‘x’
- Official Python Docs: Formatted String Literals (f-strings)





