Quick answer
Python raises TypeError: can only concatenate str (not "NoneType") to str when one side of + is a string and the other is None. Find which expression produced None, then either fix its source, handle None explicitly, or use str(value) only when the literal text "None" is intended.
Short answer: Python raises TypeError: can only concatenate str (not "NoneType") to str when one side of + is a string and the other is None. Find which expression produced None — a function that returned nothing, a missing dict key, an unset attribute — then fix its source, guard it explicitly, or wrap it in str(value) only if the literal text "None" is genuinely what you want.
The error TypeError: can only concatenate str (not "NoneType") to str means Python reached a + expression with a string on one side and None on the other:
name = None
message = "Hello, " + name
# TypeError: can only concatenate str (not "NoneType") to strThe durable fix is not always “convert everything to a string.” First decide what None means in your program: a valid missing value, an unexpected bug, or a value that should literally be displayed as "None".
Choose the correct fix
What None means | Correct approach | Example |
|---|---|---|
| Missing text is allowed | Replace only None with an intentional default | suffix = value if value is not None else "" |
| The value is required | Reject it or fix the producer | if value is None: raise ValueError(...) |
The literal word None is useful | Convert deliberately | message = "Value: " + str(value) |
| A function unexpectedly returned it | Repair every return path | Add the missing return or raise an exception |
Avoid this shortcut when values such as 0 or False are meaningful:
text = value or ""or tests truthiness, not identity with None. It replaces None, 0, False, empty containers, and empty strings. To replace only None, write:
text = value if value is not None else ""The right fix depends on the meaning of None; conversion is only one of
three valid outcomes.
Find which value is None
Read the traceback from the bottom upward. The final frame in your code normally identifies the failing + expression. If the line contains several variables, inspect them individually with repr() and type():
first_name = "Ada"
middle_name = None
last_name = "Lovelace"
print(repr(first_name), type(first_name))
print(repr(middle_name), type(middle_name))
print(repr(last_name), type(last_name))
full_name = first_name + " " + middle_name + " " + last_nameThis prints None <class 'NoneType'> for middle_name, proving where the incompatible operand came from. During debugging, an assertion can expose the problem nearer its source:
assert middle_name is not None, "middle_name must be loaded before formatting"Use a real validation error rather than an assertion for input checks that must remain active in production.
Cause 1: a function has a missing return path
Python substitutes None when a function reaches the end without returning a value. The same happens with a bare return.
def grade_label(score: int) -> str:
if score >= 90:
return "A"
# Other scores fall through and return None.
message = "Grade: " + grade_label(75)Fix the function contract instead of converting the unexpected result:
def grade_label(score: int) -> str:
if score >= 90:
return "A"
if score >= 80:
return "B"
if score >= 70:
return "C"
return "Needs improvement"If a missing value represents an invalid state, raise an exception rather than returning an empty string that hides it.
Printing is not returning
print() displays text and returns None. A frequent bug is assuming printed output becomes the function result:
def build_name() -> None:
print("Ada Lovelace")
label = "User: " + build_name() # build_name() returned NoneReturn the string and let the caller decide whether to print it:
def build_name() -> str:
return "Ada Lovelace"
label = "User: " + build_name()
print(label)Cause 2: assigning the result of an in-place method
Methods that mutate a collection in place commonly return None. This deliberately prevents confusion between mutation and producing a new value.
names = ["Grace", "Ada"]
names = names.sort() # names is now None
message = "Names: " + namesKeep the mutated object:
names = ["Grace", "Ada"]
names.sort()
message = "Names: " + ", ".join(names)Or use the value-producing alternative:
names = ["Grace", "Ada"]
sorted_names = sorted(names)
message = "Names: " + ", ".join(sorted_names)The same pattern applies to methods such as list.append(), list.extend(), list.reverse(), dict.update(), and set.add().
Cause 3: optional dictionary, environment, API, or database data
External data often contains missing or explicit null values:
payload = {"username": "ada", "display_name": None}
display_name = payload.get("display_name", "Anonymous")
message = "Welcome, " + display_nameBecause the key exists, dict.get() returns its None value instead of the default. Handle it explicitly:
display_name = payload.get("display_name")
if display_name is None:
display_name = "Anonymous"
message = f"Welcome, {display_name}"os.getenv() also returns None when an environment variable is missing unless a default is supplied:
import os
region = os.getenv("APP_REGION")
if region is None:
raise RuntimeError("APP_REGION is required")
endpoint = "https://" + region + ".api.example.com"For API and database fields, validate the payload at the boundary. Decide whether a null value is valid before the value reaches presentation or URL-building code.
Safe fixes, compared
Replace only None with a default
suffix: str | None = None
label = "Order" + (suffix if suffix is not None else "")This preserves valid falsy values and makes the fallback explicit.
Reject an unexpected None
def build_url(host: str | None) -> str:
if host is None:
raise ValueError("host is required")
return "https://" + hostThis is the strongest fix when proceeding with missing data would create an invalid path, identifier, query, or configuration.
Convert deliberately with str()
value = None
message = "Debug value: " + str(value)
# Debug value: NoneThis is appropriate for diagnostics, but usually wrong for user-facing names, URLs, and file paths.
Use an f-string after deciding the fallback
An f-string avoids + concatenation, but it does not decide what missing data should mean:
value = None
print(f"Value: {value}") # Value: NoneHandle the value first when "None" should not appear:
display_value = "Not provided" if value is None else value
print(f"Value: {display_value}")Prevent the error with type hints
Mark values that can be None explicitly. Static type checkers can then flag unsafe concatenation before the code runs:
def build_label(name: str, suffix: str | None) -> str:
if suffix is None:
return name
return name + suffixDo not annotate a function as returning str if one branch can fall through. Keep runtime validation at external boundaries even when static typing is enabled.
Common mistakes to avoid
- Do not replace every
Nonewith""before understanding why it exists. - Do not use
value or ""when0orFalseare meaningful. - Do not assume f-strings remove missing data; they render
Noneas text. - Do not assign the result of an in-place mutating method.
- Do not catch
TypeErroraround a large block and continue with corrupted output. - Do not edit the concatenation if the real bug is a missing return statement upstream.
Authoritative Python references
- Python language reference: the return statement
- Python tutorial: list methods that return None
- Python built-in function: str()
- Python typing documentation: Optional and union types
This article was technically reviewed against the Python documentation on August 2, 2026.
Sources
Key takeaways
- •Use the traceback and repr(value) to identify the exact operand that is None before changing the concatenation.
- •Functions with no return statement—or a branch with no return—produce None; fix the return contract instead of hiding the symptom.
- •In-place methods such as list.sort(), list.append(), and dict.update() return None and should not be assigned back to the variable.
- •Use value if value is not None else '' when 0, False, or an empty container are valid values; value or '' discards all falsy values.
- •str(None) returns the literal text 'None', which prevents the exception but is correct only when that output is actually wanted.
Frequently asked questions
What does "can only concatenate str (not NoneType) to str" mean?
Python evaluated a + expression where one operand was a string and the other was None. None has the type NoneType and is not automatically converted during string concatenation, so Python raises TypeError.
Why did my Python function return None?
A Python function returns None when execution reaches the end without a return statement, when it executes a bare return, or when one conditional path has no value-returning statement. Inspect every reachable path and make the function's return contract explicit.
Should I fix the error with str(value)?
Use str(value) only if displaying the literal word None is acceptable. If None means missing data, replace it with an intentional default or omit that part of the output. If None is invalid, raise an error or fix the function that produced it.
Why is value or an empty string sometimes a bad fix?
The expression value or '' replaces every falsy value, including 0, False, empty lists, and empty strings. Use value if value is not None else '' when only None should be replaced.
Can an f-string concatenate None safely?
An f-string does not raise this concatenation TypeError, but f'{value}' renders None as the text 'None'. It changes formatting behavior rather than fixing missing data. Handle None explicitly before formatting when that text is not desired.
Why does dict.get() still return None when I provide a default?
dict.get(key, default) uses the default only when the key is absent. If the key exists with the value None, get() returns None. Check is None explicitly when both missing keys and explicit null values need the same fallback.
Can type hints prevent this NoneType error?
Type hints such as str | None make optional values visible to static type checkers. A checker can then require a None guard before concatenation. Type hints do not enforce behavior at runtime, but they catch many missing-return and optional-value paths earlier.
Software Engineering Leader & Technical Author · Updated September 9, 2026