'NoneType' object is not subscriptable: find the None
NoneType object is not subscriptable means something returned None and you indexed it. The usual culprit is a method that sorts in place.
TypeError: 'NoneType' object is not subscriptable means you used square brackets on None. Subscripting is the [...] operation, None is the only value of type NoneType, and it holds no items, so there is nothing to index.
value = None
print(value[0])
# TypeError: 'NoneType' object is not subscriptable
The error is never really about the line it points at. Something upstream produced None, and the interesting question is what. Everything below was run in Python 3.13 through Pyodide 0.28.3.
The in-place method trap
This is the single most common cause, and it looks completely reasonable:
nums = [3, 1, 2]
ordered = nums.sort()
print(ordered[0])
# TypeError: 'NoneType' object is not subscriptable
list.sort() sorts the list in place and returns None. It does not return the sorted list. So ordered is None, and indexing it fails.
The same trap is set by every in-place list method:
rows = []
rows = rows.append('a') # rows is now None
nums = [1, 2]
r = nums.reverse() # r is None
append, extend, insert, remove, reverse, sort and clear all mutate and return None. The rule behind it is a deliberate Python convention: a method that changes an object in place returns nothing, so you cannot mistake it for one that produces a new object.
There are two correct forms. Either mutate and keep using the original name:
nums = [3, 1, 2]
nums.sort()
print(nums[0]) # 1
Or use the function that returns a new list:
nums = [3, 1, 2]
ordered = sorted(nums)
print(ordered[0]) # 1
sorted() returns a new sorted list and leaves the original alone. reversed() does the same for reversal. Whenever you want to keep the original, reach for those rather than the methods.
A function that forgets to return
Every Python function without an explicit return gives back None:
def load():
data = [1, 2]
result = load()
print(result[0])
# TypeError: 'NoneType' object is not subscriptable
load() builds data and then discards it. The fix is one word, and the reason it is easy to miss is that the function body looks like it does the work. It does; it just does not hand the result back.
A subtler version is a return inside a branch that did not run:
def find_user(users, name):
for u in users:
if u['name'] == name:
return u
# no match: falls off the end, returns None
That is often correct behaviour, but the caller has to treat "no match" as a real outcome rather than assuming a user comes back.
A lookup that found nothing
dict.get() returns None when the key is absent, which is exactly what makes it safe, and exactly what causes this error one line later:
user = {'name': 'Ada'}
print(user.get('address')['city'])
# TypeError: 'NoneType' object is not subscriptable
user.get('address') is None, so ['city'] fails. .get() protected you from a KeyError and handed you a None to trip over instead.
Give it a default that is the right shape:
print(user.get('address', {}).get('city')) # None, no error
The same pattern covers regular expression matches, which return None when nothing matched, and database or API lookups that find no row.
Where None comes from in real programs
Beyond the four causes above, None arrives from the outside world in three predictable places, and each has a shape of fix.
Environment variables. os.environ.get('API_KEY') returns None when the variable is not set, which is why a program works on your machine and fails in a container. Fail loudly at startup rather than deep in a request:
import os
API_KEY = os.environ.get('API_KEY')
if API_KEY is None:
raise RuntimeError('API_KEY is not set')
Checking at import time turns a confusing NoneType error three files away into a clear message before anything runs.
JSON that did not contain what you expected. Parsed JSON is dictionaries all the way down, so a missing field is a missing key, and any .get() on the way produces None. An API that returns {"data": null} for an empty result gives you a real None rather than a missing key, so 'data' in payload is True and payload['data'][0] still fails.
Database and query results. A query for one row returns None when there is no row. This is the version most likely to reach production, because it depends on the data rather than the code, and your test fixtures usually have the row.
The common thread is that all three are "no result" expressed as a value. The code that reads them has to decide what no result means, and the error on this page is what happens when it never decided.
The rest of the NoneType family
The wording changes with the operation, and each one tells you what you tried to do:
| Code | Error |
|---|---|
value[0] |
'NoneType' object is not subscriptable |
value.name |
AttributeError: 'NoneType' object has no attribute 'name' |
value() |
'NoneType' object is not callable |
len(value) |
object of type 'NoneType' has no len() |
for x in value |
'NoneType' object is not iterable |
'x' + value |
can only concatenate str (not "NoneType") to str |
All six mean the same thing: a variable you expected to hold data holds None. Which one you get depends only on what you did next.
The message also names the type, so it doubles as a type check. 'int' object is not subscriptable means your variable is a number, not a list:
n = 5
print(n[0])
# TypeError: 'int' object is not subscriptable
That is a different bug with the same shape, and the message is what distinguishes them.
Finding the None
Work backwards from the failing line to the assignment that produced the value, then print it:
print(repr(ordered)) # None
repr() rather than plain printing, because print(None) shows None and print('') shows a blank line, and at a glance those look similar in a wall of output.
If the value came through several steps, print at each one. The assignment where it turns into None is the line to fix, and it is usually one of the four causes above.
The mutable default argument
One more source, included because it produces a None that appears from nowhere and is the reason a well-known Python rule exists:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
The if items is None line looks like clutter you could remove by writing items=[] in the signature instead. Doing that introduces a worse bug: default arguments are evaluated once, when the function is defined, so every call without an explicit list shares the same list and it accumulates across calls.
So None is used deliberately here as a marker meaning "no value supplied", and the guard converts it into a fresh list. If you see this pattern, it is correct. If you remove it because the None check looks unnecessary, you trade this error for a stranger one.
That is the general shape of None in Python: it is the conventional way to say "nothing here", which is why it turns up in so many return values, and why the fix is nearly always to decide what nothing should mean rather than to prevent it existing.
Guarding it, once you know why
When None is a legitimate possible value, check for it explicitly:
address = user.get('address')
if address is not None:
print(address['city'])
Use is None rather than == None. Both are True here, so the difference is not behaviour in the ordinary case:
v = None
print(v is None, v == None) # True True
is compares identity, and since None is a singleton there is exactly one of it, so identity is the precise question. == calls __eq__, which a class can override to return something surprising. is None cannot be lied to, which is why it is the convention.
Type hints make the possibility visible to your editor before you run anything:
def find_user(users, name) -> dict | None:
...
That signature says out loud that None is a possible answer. A type checker such as mypy or Pyright will then refuse to let you subscript the result without a check, which converts this runtime error into a warning while you type. The hint alone does nothing at runtime; the value is in the checker reading it.
The deeper fix is usually to stop producing the None at all. A function that returns an empty list instead of None when it finds nothing lets every caller carry on without a guard, because an empty list still supports iteration, len(), and being passed along.
Want to build the habit on real code? Start with the Python track, or read how to loop with an index for the loops these values usually flow into.
More from the blog
Each child in a list should have a unique key prop
The unique key prop warning in React still renders your list. Here is the bug it predicts, shown with real output from two runs.
Read moreToo many re-renders in React: the one real cause
Too many re-renders always means a state update running during render. Here is how to spot which of the four shapes you have written.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.