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. Laptop displaying code with reflection, perfect for tech and programming themes.

ErrorTypeError: 'NoneType' object is not subscriptable
Where it happensPython 3.x - any runtime (CPython, scripts, Django/Flask/FastAPI handlers, pandas/NumPy glue code, Jupyter notebooks)
What it meansYou 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

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.

Close-up of colorful programming code on a blurred computer monitor. ## None of Those? Narrow It Down
  1. 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.
  2. Insert print(repr(var), type(var)) on the line immediately before the subscript. If it prints None, 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.
  3. 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.
  4. Check whether the right-hand side is a mutating method: sort, reverse, append, extend, insert, remove, clear, update, or a print/logging call. All of these return None, which rules in cause 3 or 9.
  5. 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(), or next(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).
  6. If the value came from parsed JSON or YAML, dump the decoded object with json.dumps(obj, indent=2) and look for a literal null at that key. A present key holding null behaves differently from an absent key, and only the dump distinguishes them.
  7. 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.
  8. 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 with args and p var to 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. A programmer working on code with a laptop and monitor setup in an office.

Stop It From Coming Back

  • Annotate functions that can return nothing as Optional[T] (or T | None on Python 3.10+) and run mypy or pyright in CI. A type checker flags user["name"] on an Optional[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, and assignment-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.get an explicit same-shaped default whenever the result will be subscripted: cfg.get("db", {}) for dicts, cfg.get("hosts", []) for lists. Reserve bare cfg.get("key") for values you will test for None.
  • Validate external payloads at the boundary with pydantic or a JSON Schema check, so a null where 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.get hand a None into request handling.

Related Guides

Errors You Will Probably Hit Next

  • AttributeError: 'NoneType' object has no attribute 'get'
  • TypeError: 'NoneType' object is not iterable
  • TypeError: 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.

댓글

이 블로그의 인기 게시물

TypeError: Cannot read properties of undefined (reading 'map') - 11 Causes and Fixes

psql: FATAL: password authentication failed for user - 10 Causes and Fixes

Error: ERR_MODULE_NOT_FOUND: Cannot find package - 10 Causes and Fixes