TypeError: 'NoneType' object is not subscriptable in Python

TypeError: 'NoneType' object is not subscriptable means you used square brackets (x[0], x["key"]) on a value that is None. Something earlier returned None instead of the list, dict or match object you expected; the fix is to find that something and either make it return a real value or check for None before indexing.
What the error means
"Subscriptable" means the object supports obj[key], i.e. it implements __getitem__. Lists, tuples, strings, dicts and re.Match objects do; None does not. The error is never about the index itself; it is about the object in front of the brackets. Related messages come from the same root cause:
Operation on None | Error |
|---|---|
x["a"] | TypeError: 'NoneType' object is not subscriptable |
x["a"] = 1 | TypeError: 'NoneType' object does not support item assignment |
for i in x | TypeError: 'NoneType' object is not iterable |
len(x) | TypeError: object of type 'NoneType' has no len() |
x.append(1) | AttributeError: 'NoneType' object has no attribute 'append' |
The usual causes
1. Methods that modify in place return None
The classic case. list.sort(), list.append(), list.reverse(), dict.update(), random.shuffle() and pandas methods called with inplace=True all mutate the object and return None. Assigning their result throws away your data:
scores = [42, 7, 19]
top = scores.sort(reverse=True)
print(top)
print(top[0])
None
Traceback (most recent call last):
File "t1.py", line 4, in <module>
print(top[0])
~~~^^^
TypeError: 'NoneType' object is not subscriptable
Either use the function that returns a new object, or call the method and keep using the original name:
words = ["pear", "fig", "apple"]
print(sorted(words)[0]) # sorted() returns a new list
words.sort() # sort() sorts in place, returns None
print(words[0])
apple
apple
The same applies to pandas: df.sort_values("score", inplace=True) returns None. Prefer the non-inplace form, df = df.sort_values("score").
2. A function that forgets to return
A Python function without a return statement, or one where some branch falls off the end, returns None implicitly:
def load_user(user_id):
users = {1: {"name": "Ada"}, 2: {"name": "Linus"}}
if user_id in users:
return users[user_id]
# falls off the end -> returns None
print(load_user(3)["name"])
print(load_user(3)["name"])
~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Decide what the "not found" case should do: return a default, raise a specific exception (KeyError, LookupError, a custom error), or return None deliberately and document it with a type hint (see below).
3. dict.get() on a missing key
dict.get(key) returns None when the key is absent, so chaining an index after it fails:
config = {"db": {"host": "localhost"}}
port = config.get("cache")["port"]
port = config.get("cache")["port"]
~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Pass a default of the right type so the chain keeps working:
port = config.get("cache", {}).get("port", 6379)
print(port)
6379
4. re.match() and re.search() found nothing
The regex functions return None when there is no match. re.match only matches at the start of the string, which surprises people:
import re
m = re.match(r"(\d+)-(\d+)", "version 3-14")
print(m[1])
print(m[1])
~^^^
TypeError: 'NoneType' object is not subscriptable
Check the match before using it. The walrus operator (Python 3.8+) keeps this compact:
if (m := re.match(r"(\d+)-(\d+)", "3-14 release")) is not None:
print(m[1], m[2])
3 14
5. JSON null and API responses
json.loads turns JSON null into Python None. The key exists, so .get() with a default does not help:
import json
payload = json.loads('{"user": {"name": "Ada", "address": null}}')
city = payload["user"]["address"]["city"]
city = payload["user"]["address"]["city"]
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Use or to replace both a missing key and an explicit null:
address = payload["user"].get("address") or {}
print(address.get("city", "unknown"))
unknown
Other common sources: cursor.fetchone() when the query returns no rows, os.environ.get() for an unset variable, BeautifulSoup's find() when the element does not exist, and ORM .first() methods on an empty result.
Reading the traceback
The last line names the error; the line above it shows the exact source line. Since Python 3.11 the interpreter also underlines the failing sub-expression (PEP 657): the ~~~~ part is the object that was None, the ^^^^ part is the subscript you applied to it. In payload["user"]["address"]["city"] above, the tildes cover payload["user"]["address"], so that is the value that was None, not payload or payload["user"].
On Python 3.10 and older there is no underline, so a long chain gives you only the line. Break it into steps (or inspect it in a debugger) to find which link is None:
user = payload["user"]
address = user["address"]
print(repr(address)) # None -> this is the culprit
city = address["city"]
Then ask the real question: why is it None? Go to where the variable was assigned and look for one of the causes above. Running the script with python -m pdb script.py or adding breakpoint() just before the failing line lets you inspect every variable at that point.
Defensive patterns
- Check explicitly with
is None, not truthiness, when an empty list or0is a valid value:if rows is None: ... - Fail early with a useful message instead of letting
Nonetravel further:rows = get_rows() assert rows is not None, "get_rows() returned None - check the query"
(Assertions are stripped withAssertionError: get_rows() returned None - check the querypython -O; useif ...: raisefor checks that must always run.) - Use defaults of the right type:
d.get("items", []),d.get("meta") or {}. - Prefer raising over returning
Nonefor "this should never happen" cases; aKeyErrorat the source is easier to debug than aTypeErrorthree functions later. - Avoid
try/except TypeErroraround large blocks; it hides unrelated bugs. Check forNonewhere the value is produced.
Let mypy find it before runtime
Annotate functions that may return nothing as Optional[T] (or T | None on Python 3.10+). A type checker such as mypy then flags every place where you index the result without checking it first. Standard-library stubs already declare that dict.get and re.match can return None.
import re
from typing import Optional
def find_user(users: dict[int, dict[str, str]], user_id: int) -> Optional[dict[str, str]]:
return users.get(user_id)
def greeting(users: dict[int, dict[str, str]], user_id: int) -> str:
user = find_user(users, user_id)
return "Hello, " + user["name"]
def major_version(text: str) -> int:
m = re.match(r"(\d+)\.", text)
return int(m[1])
$ mypy t8.py
t8.py:11: error: Value of type "dict[str, str] | None" is not indexable [index]
t8.py:16: error: Value of type "Match[str] | None" is not indexable [index]
Found 2 errors in 1 file (checked 1 source file)
Adding the checks narrows the type, and mypy is satisfied:
def greeting(users: dict[int, dict[str, str]], user_id: int) -> str:
user = find_user(users, user_id)
if user is None:
return "Hello, stranger"
return "Hello, " + user["name"] # mypy knows user is a dict here
def major_version(text: str) -> int:
m = re.match(r"(\d+)\.", text)
if m is None:
raise ValueError(f"not a version string: {text!r}")
return int(m[1])
Success: no issues found in 1 source file
mypy only checks annotated functions by default; unannotated ones are skipped unless you pass --check-untyped-defs. Pyright and the type checkers built into most editors report the same problem.
Summary
The error means the value before the brackets is None. Read the traceback (the underlined part on Python 3.11+) to find which value, then trace back to where it was assigned. The usual suspects are in-place methods such as list.sort(), functions missing a return, dict.get() on missing keys, failed re.match() calls and JSON null. Fix the source, check with is None where absence is legitimate, and annotate with Optional so mypy catches the next one for you.
PythonNested dictionary values
Get values from nested dictionaries in Python: chained indexing, safe .get() chains, a deep_get helper, lists in dicts, recursion, json_normalize and glom.
PythonCould not convert string to float
Why float() raises "could not convert string to float": empty strings, commas, currency symbols, N/A values, and how to fix them in Python and pandas.
PythonSplit a string into a list
Split strings into lists in Python: str.split with and without sep, maxsplit, rsplit, splitlines, re.split for multiple delimiters, list(), csv and shlex.