TypeError: 'NoneType' object is not subscriptable - 10 Causes and Fixes
TypeError: 'NoneType' object is not subscriptable means you used [...] on a value that is None. Python raises this the moment you write x[0], x["key"], or x[1:3] and x happens to be None - the subscript operator simply does not exist on the NoneType type.
The traceback tells you where the subscript happened, not where the None came from. That gap is the whole debugging problem: the real bug is usually several lines (or several functions) earlier, at the point something returned None and you assumed it returned a dict, a list, or a string.
This page lists the concrete ways a None gets into a subscript - functions with a missing return, mutating methods like sort() and append(), regex search() misses, dict.get() on an absent key, JSON null, DB queries with no rows - each with a reproduction, the fix, and a way to verify. If your case does not match any of them, work the diagnostic checklist instead.
| Error | TypeError: 'NoneType' object is not subscriptable |
|---|---|
| Where it happens | Python 3.x - any runtime (CPython, scripts, Django/Flask/FastAPI handlers, pandas/NumPy glue code, Jupyter notebooks) |
| What it means | You applied the indexing operator to None, and NoneType does not implement __getitem__. |
The Fast Fix
Find the subscript on the last line of the traceback, then print the variable just before it. It will be None.
print(repr(user)) # None <- the real bug is wherever user was assigned
print(type(user)) # <class 'NoneType'>
user["name"] # TypeError here
The fast patch is a guard; the actual fix is upstream, where the value was produced.
if user is None:
raise ValueError("user lookup returned no row") # or handle the miss
name = user["name"]
If you want a default instead of an error, use (user or {}).get("name") for dicts. If you do not know which variable is None, run the diagnostic checklist below.
What Is Actually Causing It
Jump to your case
- 1. A function with no return statement returned None
- 2. A conditional return path falls through without returning
- 3. You assigned the result of an in-place method like sort() or append()
- 4. re.search or re.match found no match and returned None
- 5. dict.get() on a missing key returned None
- 6. JSON contained null, which decodes to Python None
- 7. A database query returned no row (fetchone / first)
- 8. os.environ.get or a config lookup returned None for an unset variable
- 9. A print() or logging call was used where a value was expected
- 10. A nested dict path has a None at an intermediate level
1. A function with no return statement returned None
Reproduce it
def get_config():
config = {"host": "localhost", "port": 5432}
# no return here
config = get_config()
print(config["host"])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — Every Python function that falls off its end returns None implicitly. get_config built the dict and threw it away, so config is None and config["host"] has nothing to index.
The fix
def get_config():
config = {"host": "localhost", "port": 5432}
return config # added the return
config = get_config()
print(config["host"]) # localhost
The only change is the explicit return - the dict now leaves the function.
Confirm it worked — Call the function alone and check it: assert get_config() is not None. Or run python -c "import mod; print(mod.get_config())" and confirm you see the dict, not None.
2. A conditional return path falls through without returning
Reproduce it
def find_user(users, user_id):
for user in users:
if user["id"] == user_id:
return user
# no return when nothing matches
user = find_user([{"id": 1, "name": "ada"}], 99)
print(user["name"])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — The function returns a value only on the matching branch. When the loop finishes without a match, control reaches the end of the function and Python returns None. This is the single most common source of the error in real code, because the happy path works in testing.
The fix
def find_user(users, user_id):
for user in users:
if user["id"] == user_id:
return user
return None # explicit: callers must handle the miss
user = find_user([{"id": 1, "name": "ada"}], 99)
if user is None:
print("no such user")
else:
print(user["name"])
The miss is now explicit at both ends: the function states it can return None, and the caller checks before subscripting.
Confirm it worked — Add a test for the not-found case: assert find_user([], 1) is None and assert the caller handles it without raising.
3. You assigned the result of an in-place method like sort() or append()
Reproduce it
numbers = [3, 1, 2]
numbers = numbers.sort() # sort() mutates and returns None
print(numbers[0])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — Python's mutating list methods - sort(), reverse(), append(), extend(), insert(), clear() - change the list in place and return None by convention. Assigning their result overwrites your list with None.
The fix
numbers = [3, 1, 2]
numbers.sort() # mutate in place, do not assign
print(numbers[0]) # 1
# or, if you want a new list:
numbers = [3, 1, 2]
ordered = sorted(numbers) # sorted() returns a new list
print(ordered[0]) # 1
Either drop the assignment, or switch to the non-mutating builtin (sorted, reversed, list + list).
Confirm it worked — print(repr(numbers)) right after the call. If you see None, the assignment is the bug. As a rule: if the method name is a verb that changes the object, it returns None.
4. re.search or re.match found no match and returned None
Reproduce it
import re
line = "status=ok"
m = re.search(r"code=(\d+)", line)
print(m[1])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — re.search, re.match, and re.fullmatch return a match object on success and None on failure. Since Python 3.6 a match object supports m[1] as a shorthand for m.group(1), so a failed match turns your subscript into a None subscript.
The fix
import re
line = "status=ok"
m = re.search(r"code=(\d+)", line)
if m: # truthy only when there is a match
print(m[1])
else:
print("no code field in line")
The added if m: makes the no-match case a branch instead of a crash.
Confirm it worked — Log repr(m) before indexing - None means the pattern did not match. Test your regex against the exact input string in a REPL: re.search(pattern, line).
5. dict.get() on a missing key returned None
Reproduce it
payload = {"user": {"name": "ada"}}
profile = payload.get("profile") # key absent -> None
print(profile["email"])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — dict.get(key) is designed not to raise on a missing key - it returns None instead. That turns a KeyError you would have caught into a TypeError one line later, at the nested subscript.
The fix
payload = {"user": {"name": "ada"}}
profile = payload.get("profile", {}) # default to an empty dict
print(profile.get("email")) # None, no crash
# or demand the key and fail loudly:
profile = payload["profile"] # raises KeyError with the key name
Either give get() a subscriptable default, or use [...] so the failure names the missing key.
Confirm it worked — Print the keys you actually have: print(payload.keys()). If "profile" is absent, the default-or-bracket choice is yours to make deliberately.
6. JSON contained null, which decodes to Python None
Reproduce it
import json
raw = '{"data": null, "ok": true}'
body = json.loads(raw)
print(body["data"]["id"])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — json.loads maps JSON null to Python None. The key exists, so body["data"] succeeds and hands you None; the second subscript is what fails. An API that returns null for an empty result produces this every time.
The fix
import json
raw = '{"data": null, "ok": true}'
body = json.loads(raw)
data = body.get("data")
if data is None: # null and missing both land here
print("no data in response")
else:
print(data["id"])
Check for None separately from checking for the key - a present key can still hold null.
Confirm it worked — Dump the decoded body: print(json.dumps(body, indent=2)). A literal null in the response confirms the source. Curl the endpoint directly to see the raw payload.
More causes (4 remaining)
7. A database query returned no row (fetchone / first)
Reproduce it
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
row = conn.execute("SELECT name FROM users WHERE id = 1").fetchone()
print(row[0])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — DB-API cursor.fetchone() returns None when the result set is exhausted - including when it was empty to begin with. Empty results are not an error at the driver level, so nothing raises until you index the row.
The fix
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
row = conn.execute("SELECT name FROM users WHERE id = 1").fetchone()
if row is None:
print("user 1 not found")
else:
print(row[0])
The guard distinguishes no row from a row whose column is NULL - those are different bugs with the same symptom.
Confirm it worked — Run the same SQL in a client and count the rows, or swap in fetchall() and print the list - an empty list confirms the query matched nothing.
8. os.environ.get or a config lookup returned None for an unset variable
Reproduce it
import os
# DB_DSN is not set in this shell
dsn = os.environ.get("DB_DSN")
print(dsn[:8]) # want the scheme prefix
# TypeError: 'NoneType' object is not subscriptable
Why it happens — os.environ.get returns None for an unset variable, and slicing is a subscript too - dsn[:8] fails exactly like dsn[0]. This typically shows up only in the environment where the variable is missing, so it passes locally and breaks in CI or production.
The fix
import os
dsn = os.environ.get("DB_DSN")
if not dsn:
raise RuntimeError("DB_DSN is not set") # fail at startup, not mid-request
print(dsn[:8])
# or require it directly:
dsn = os.environ["DB_DSN"] # KeyError names the variable
Validate required config once at startup so the failure message names the variable instead of a slice.
Confirm it worked — Print the value's presence without leaking the secret: print("DB_DSN set:", "DB_DSN" in os.environ). In CI, dump the list of expected keys and compare.
9. A print() or logging call was used where a value was expected
Reproduce it
def load_row(path):
with open(path) as f:
print(f.readline().split(",")) # printed instead of returned
row = load_row("data.csv")
print(row[0])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — print() returns None. Debugging by wrapping an expression in print() and forgetting to also return it leaves the caller holding None. The same happens with logging.info(...), which also returns None.
The fix
def load_row(path):
with open(path) as f:
row = f.readline().split(",")
print(row) # keep the debug output if you want it
return row # and return the value
row = load_row("data.csv")
print(row[0])
Separate the side effect from the return value - print is never a substitute for return.
Confirm it worked — Grep the function for return: grep -n "return" yourmodule.py. A function whose last statement is a call to print or a logger returns None.
10. A nested dict path has a None at an intermediate level
Reproduce it
response = {"user": {"address": None}}
print(response["user"]["address"]["city"])
# TypeError: 'NoneType' object is not subscriptable
Why it happens — Chained subscripts fail at the first None in the chain, and the traceback points at the whole expression - it does not tell you which link broke. Here response["user"] and ["address"] both succeed; address is None, so the third subscript raises.
The fix
response = {"user": {"address": None}}
address = response.get("user", {}).get("address")
city = address["city"] if address else None
print(city) # None, no crash
Walk the path in steps you can inspect instead of one long chain, so the failing level is obvious.
Confirm it worked — Index one level at a time in a REPL or debugger and print each result; the first None is your culprit. For deep, repeated paths, write a small helper that takes a list of keys and returns a default on the first None.
## None of Those? Narrow It Down
- Read the last frame of the traceback and note the exact subscript expression (
x[0],x["k"],x[a:b]). This is where the error surfaced, not where the None was created - but it names the variable you are chasing. - Insert
print(repr(var), type(var))on the line immediately before the subscript. If it printsNone, you have confirmed the variable; if it prints a value, the None is at a different subscript on the same line (a chained path) - split the chain into separate statements and re-run. - Trace that variable back to its assignment. If it came from a function call, call that function in isolation and check
is None. A function that returns None on one path is cause 1 or 2 above. - Check whether the right-hand side is a mutating method:
sort,reverse,append,extend,insert,remove,clear,update, or aprint/logging call. All of these return None, which rules in cause 3 or 9. - Check whether the value came from a lookup that signals absence with None:
dict.get,re.search/re.match,cursor.fetchone,os.environ.get, an ORM.first(), ornext(iterator, None). If so, the real question is why the lookup missed - inspect the actual input (keys, SQL rows, env vars, the string being matched). - If the value came from parsed JSON or YAML, dump the decoded object with
json.dumps(obj, indent=2)and look for a literalnullat that key. A present key holding null behaves differently from an absent key, and only the dump distinguishes them. - If it reproduces in one environment but not another, diff the inputs rather than the code: environment variables, config files, database contents, API responses. Code that works locally and raises in CI almost always has a missing config value or an empty table.
- If you still cannot see it, run under the debugger and stop at the failure:
python -m pdb -c continue yourscript.py. At the post-mortem prompt, inspect the frame's locals withargsandp varto see every value at the moment it broke.
Why This Error Exists At All
Python has no null-safe navigation operator and no compile-time type checking of subscripts. Indexing is resolved entirely at runtime by looking for a __getitem__ method on the object's type. NoneType - the type of the single None object - deliberately defines almost nothing: no __getitem__, no __len__, no __iter__. So None[0] is not a special-cased null check; it is the generic "this type does not support indexing" error, and the message says exactly that. You can see the whole mechanism in one line: hasattr(None, "__getitem__") is False.
The deeper design decision is that Python uses None as the universal "nothing here" sentinel, and uses it for several semantically different situations: a function that completed without producing a value, a lookup that missed, a mutation that has no result to give back, and a field that is genuinely empty. Returning None instead of raising is a convenience - dict.get and re.search exist precisely so you do not have to wrap every lookup in try. The cost is that the failure is deferred: a missed lookup is silent at the point it happens and only becomes an exception later, at whatever line first treats the result as a container. That delay is why the traceback so often points at innocent code.
Once you see the pattern, you recognise the whole family. 'NoneType' object has no attribute 'x' is the same bug reached through attribute access; 'NoneType' object is not iterable is the same bug reached through a for loop or unpacking; object of type 'NoneType' has no len() is the same bug reached through len(). In every case the question is identical and the subscript is a red herring: which upstream operation returned None, and was that a legitimate "nothing found" you failed to handle, or a bug in the producer? Answer that and you fix the cause instead of adding a guard.
Stop It From Coming Back
- Annotate functions that can return nothing as
Optional[T](orT | Noneon Python 3.10+) and run mypy or pyright in CI. A type checker flagsuser["name"]on anOptional[dict]before it ships - this catches causes 1, 2, and 5 statically. - Enable pylint, which reports
inconsistent-return-statements(R1710) when some paths return a value and others fall through, andassignment-from-none(E1128) when you assign the result of a function that only ever returns None. Both map directly to the top causes on this page. - Give
dict.getan explicit same-shaped default whenever the result will be subscripted:cfg.get("db", {})for dicts,cfg.get("hosts", [])for lists. Reserve barecfg.get("key")for values you will test for None. - Validate external payloads at the boundary with pydantic or a JSON Schema check, so a
nullwhere you expected an object is rejected with a field path instead of surfacing as a TypeError deep in business logic. - Write a test for the empty case of every lookup: no matching row, no regex match, missing key, unset environment variable. The not-found path is the one that produces this error in production, and it is the path integration tests usually skip.
- Validate required configuration once at startup and fail fast with a message naming the variable, rather than letting
os.environ.gethand aNoneinto request handling.
Related Guides
-
TypeError: Cannot read properties of undefined (reading 'map')
-
SettingWithCopyWarning: A value is trying to be set on a copy of a slice
Errors You Will Probably Hit Next
AttributeError: 'NoneType' object has no attribute 'get'TypeError: 'NoneType' object is not iterableTypeError: object of type 'NoneType' has no len()
The subscript is where the error surfaces; the assignment is where the bug lives. When you see TypeError: 'NoneType' object is not subscriptable, print repr() of the variable, then walk backwards to whatever produced it and ask whether None was a legitimate "nothing found" or a missing return. Guard the caller only after you know which - a guard on top of a real bug just moves the failure somewhere quieter.
댓글
댓글 쓰기