"TypeError: 'NoneType' object is not subscriptable" means you used square brackets, as in x[0], x["name"] or x[:4], on a variable that holds None. Python can't index or slice None. To fix it, find the expression the traceback underlines, work out which function or lookup handed you None, and either make it return real data or check if x is not None before you index it.
In practice the None nearly always comes from one of a few places. A function forgot to return, you saved the result of list.sort(), a lookup such as dict.get(), re.match() or fetchone() found nothing, or an API sent back null. Python's docs describe TypeError as the exception for an operation applied to an object of the wrong type, and that's exactly what's happening here.
I reproduced every example in this guide myself. Each broken script ran on Python 3.11.17, 3.13.5, 3.14.8 and 3.15.0rc3, and all four printed the same traceback and message. The type-checker examples used mypy 2.4.1 and Pyright 1.1.414, and the HTML example used Beautiful Soup 4.15.0.
In this guide:
- What 'not subscriptable' actually means
- Read the traceback first: the carets point at the None
- 1. A function that forgets to return
- 2. Saving the result of list.sort(), append() or another in-place method
- 3. dict.get() returned None for a missing key
- 4. re.match() or re.search() found nothing
- 5. A JSON null or an API field that is None
- 6. A variable that starts as None and never gets replaced
- 7. cursor.fetchone() returned no row
- 8. A function that only returns on some paths
- 9. BeautifulSoup find() did not match
- 10. os.environ.get() for a variable that is not set
- Writing to None: 'does not support item assignment'
- Catch it before it runs: type hints with mypy and Pyright
- A quick debugging checklist
- Which fix should you use?
- Frequently asked questions
What 'not subscriptable' actually means
"Subscript" is Python's word for the square-bracket operation. A list, tuple, string or dict lets you subscript it because its type defines a __getitem__ method, which the data model reference covers. None doesn't have one, so None[0] fails straight away.
The same None value causes a family of closely related errors, depending on what you tried to do with it. I ran each operation against x = None on all four Python versions:
| Message | What you wrote | Same root cause? |
|---|
'NoneType' object is not subscriptable | x[0], x["key"], x[:3] | Yes, x is None |
'NoneType' object does not support item assignment | x["key"] = 1 | Yes, but you were writing, not reading |
'NoneType' object has no attribute ... | x.name, x.group(1) | Yes, you used a dot instead of brackets |
'NoneType' object is not iterable | for i in x, a, b = x | Yes, you looped over or unpacked it |
'int' object is not subscriptable | 5[0] | No, the value is a number, not None |
If you came here from the attribute or iterable version of this error, the causes below apply to you too.
Read the traceback first: the carets point at the None
Since Python 3.11, tracebacks underline the exact part of the line that failed. The ~ characters mark the value that turned out to be None, and the ^ characters mark the subscript you tried to apply to it. That's a big help when one line chains several calls together:
print(find_product(products, "B2")["price"])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Here the tildes sit under find_product(products, "B2"), so that call returned None. The ["price"] part is fine. Once you know which expression is None, the cause is usually one of the ten below.
1. A function that forgets to return
This is the most common cause by far. A Python function without a return statement still returns something: None. The function below builds the right dict, prints from it, then throws it away:
def get_user(user_id):
user = {"id": user_id, "name": "Ana"}
print("loaded", user["name"]) # forgot: return user
user = get_user(1)
print(user["name"])
loaded Ana
Traceback (most recent call last):
File "/workspace/seo-posts-nov04/test/python/c01_noreturn.py", line 6, in <module>
print(user["name"])
~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
The "loaded Ana" line shows the function ran and had the data, so the problem is what it handed back. Fix: return the value.
def get_user(user_id):
user = {"id": user_id, "name": "Ana"}
return user
Ana
Watch out for print() standing in for return, especially in code you wrote while experimenting in the REPL. Printing shows the value on screen, but the caller still gets None.
2. Saving the result of list.sort(), append() or another in-place method
Methods that change a list in place return None on purpose, as a reminder that they didn't create a new list. Assign that result back to your variable and you've replaced your list with None:
scores = [42, 7, 19]
scores = scores.sort()
print(scores[0])
print(scores[0])
~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
I checked the usual suspects. Every one of these returns None:
list.append() returns None
list.extend() returns None
list.insert() returns None
list.sort() returns None
list.reverse() returns None
list.clear() returns None
dict.update() returns None
random.shuffle() returns None
Fix: call the method on its own line, or use sorted(), which returns a new list and leaves the original alone:
scores = [42, 7, 19]
scores.sort()
print(scores[0])
prices = [42, 7, 19]
cheapest_first = sorted(prices)
print(cheapest_first[0], prices)
7
7 [42, 7, 19]
The Python docs on built-in types note that sort() modifies the list in place and doesn't return the sorted sequence, for exactly this reason.
3. dict.get() returned None for a missing key
dict.get(key) is popular because it doesn't raise KeyError. The catch is that it returns None when the key is missing, so chaining another lookup onto it moves the crash one step to the right:
config = {"debug": True}
host = config.get("database")["host"]
host = config.get("database")["host"]
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Fix: give get() a default of the right type, or check before you go deeper:
host = config.get("database", {}).get("host", "localhost")
print("host:", host)
db = config.get("database")
if db is not None:
print(db["host"])
else:
print("no database section in config")
host: localhost
no database section in config
Use the empty-dict default when a missing section is normal, and the explicit check when it's a real problem worth reporting. If you're still choosing between dicts, lists and tuples for data like this, see my comparison of Python list vs tuple vs set vs dict.
4. re.match() or re.search() found nothing
Regex functions return a match object when they succeed and None when they don't. The classic mistake is using re.match(), which only matches at the start of the string, when the text you want is in the middle:
import re
line = "Order total: 45 GBP"
m = re.match(r"(\d+) GBP", line)
print(m)
print(m[1])
None
print(m[1])
~^^^
TypeError: 'NoneType' object is not subscriptable
If you write m.group(1) instead of m[1], you get the attribute version: 'NoneType' object has no attribute 'group'. Same cause.
Fix: use re.search() to find the pattern anywhere in the string, and always check the result. The walrus operator keeps it short:
print("match: ", re.match(r"(\d+) GBP", line))
print("search:", re.search(r"(\d+) GBP", line))
if (m := re.search(r"(\d+) GBP", line)):
print("amount =", m[1])
else:
print("no amount found")
match: None
search: <re.Match object; span=(13, 19), match='45 GBP'>
amount = 45
The re module docs have a short section on search() vs match() that's worth reading once.
5. A JSON null or an API field that is None
JSON's null becomes Python's None when you parse it. APIs often send null for "no value yet", such as a guest checkout with no user. The key exists, so data["user"] works, but the value inside it is None:
payload = '{"user": null, "items": []}'
data = json.loads(payload)
print(data)
print(data["user"]["email"])
{'user': None, 'items': []}
print(data["user"]["email"])
~~~~~~~~~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Notice that get("user", {}) would not help here, because the key is present. The default only applies to missing keys. Fix: use or {}, which also replaces an explicit None:
data = json.loads('{"user": null, "items": []}')
user = data.get("user") or {}
print("email:", user.get("email", "not set"))
email: not set
Printing the parsed data is the quickest way to spot a hidden None. For saving that data back out in readable form, see how to write JSON to a file with pretty printing.
6. A variable that starts as None and never gets replaced
Starting a variable as None and filling it inside a loop works until the loop runs zero times, say on an empty file or an empty API page:
best = None
for row in []: # e.g. an empty API page or empty file
best = row
print(best["score"])
print(best["score"])
~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Fix: handle the empty case on purpose. For "find the biggest" logic, max() with a default makes the empty case explicit:
rows = []
best = max(rows, key=lambda r: r["score"], default=None)
if best is None:
print("no rows yet")
else:
print(best["score"])
no rows yet
The built-in functions page documents the default argument for both max() and min().
7. cursor.fetchone() returned no row
Database cursors return None from fetchone() when the query matched nothing. That's easy to forget when you're looking up a single record by ID:
row = con.execute("SELECT email FROM users WHERE id = ?", (99,)).fetchone()
print(row)
print(row[0])
None
print(row[0])
~~~^^^
TypeError: 'NoneType' object is not subscriptable
Fix: treat "not found" as a normal result and check for it:
for uid in (1, 99):
row = con.execute("SELECT email FROM users WHERE id = ?", (uid,)).fetchone()
if row is None:
print(f"user {uid}: not found")
else:
print(f"user {uid}: {row[0]}")
user 1: ana@example.com
user 99: not found
I used the built-in sqlite3 module, but MySQL and PostgreSQL drivers behave the same way. fetchall(), by contrast, gives you an empty list.
8. A function that only returns on some paths
This one is sneakier than cause 1, because the function does return, just not every time. With no match, Python falls off the end and returns None:
def find_product(products, sku):
for p in products:
if p["sku"] == sku:
return p
products = [{"sku": "A1", "price": 9.99}]
print(find_product(products, "A1")["price"])
print(find_product(products, "B2")["price"])
9.99
print(find_product(products, "B2")["price"])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Tests that only try the happy path will pass. Fix: decide what "not found" should do. Either return None on purpose and make callers check for it, or raise a clear error:
def find_product(products, sku):
for p in products:
if p["sku"] == sku:
return p
raise LookupError(f"no product with sku {sku!r}")
LookupError: no product with sku 'B2'
A LookupError that names the missing SKU tells you far more than a TypeError three lines later. JavaScript has the same trap, where it shows up as Cannot read properties of undefined.
9. BeautifulSoup find() did not match
If you scrape HTML, you'll meet this one often. Beautiful Soup's find() returns None when no tag matches, so a small typo in a class name, or a site redesign, breaks a line like this:
html = '<div><a class="btn" href="/signup">Sign up</a></div>'
soup = BeautifulSoup(html, "html.parser")
print(soup.find("a", class_="btn")["href"])
print(soup.find("a", class_="button")["href"])
/signup
print(soup.find("a", class_="button")["href"])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Fix: store the tag, check it, and use .get() for attributes that might be missing:
link = soup.find("a", class_="button")
print(link["href"] if link is not None else "link not found")
print(soup.select_one("a.btn").get("href"))
link not found
/signup
select_one() also returns None when nothing matches, so give it the same check in real code. The Beautiful Soup documentation covers both methods.
10. os.environ.get() for a variable that is not set
Secrets and settings often come from environment variables. os.environ.get() returns None when the variable isn't set, which is common on a new machine, in a fresh terminal, or in a CI job that doesn't have the secret:
secret = os.environ.get("API_SECRET")
print("first 4 chars:", secret[:4])
print("first 4 chars:", secret[:4])
~~~~~~^^^^
TypeError: 'NoneType' object is not subscriptable
Slicing counts as subscripting too, which is why [:4] triggers the same error. Fix: fail early with a message a human can act on:
secret = os.environ.get("API_SECRET")
if not secret:
raise SystemExit("API_SECRET is not set. Export it before running this script.")
print("first 4 chars:", secret[:4])
API_SECRET is not set. Export it before running this script.
first 4 chars: sk_t
The first line is the run with the variable unset, and the second is the run with API_SECRET=sk_test_123. If the variable is required, os.environ["API_SECRET"] is also fine, because it raises a KeyError that names the missing variable. The os module docs cover both forms.
Writing to None: 'does not support item assignment'
Use brackets on the left of an equals sign and the message changes slightly, but the cause stays the same:
cart = None
cart["apples"] = 3
cart["apples"] = 3
~~~~^^^^^^^^^^
TypeError: 'NoneType' object does not support item assignment
The fix is to create the container before you fill it, such as cart = {}, or to find out why the function you expected to return a dict returned None. My free tools page has more small coding helpers.
Catch it before it runs: type hints with mypy and Pyright
If a function can return None, say so in its type hint. A type checker will then flag every place where you index the result without checking it first:
from typing import Optional
def load_settings(path: str) -> Optional[dict[str, str]]:
if path.endswith(".json"):
return {"theme": "light"}
return None
settings = load_settings("app.toml")
print(settings["theme"])
c13_typed.py:9: error: Value of type "dict[str, str] | None" is not indexable [index]
Found 1 error in 1 file (checked 1 source file)
/workspace/seo-posts-nov04/test/python/c13_typed.py:9:7 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
The first block is mypy and the second is Pyright, the checker behind VS Code's Pylance extension. After I added an if settings is None: branch, mypy reported Success: no issues found in 1 source file. The mypy guide to Optional types explains how this narrowing works.
A quick debugging checklist
- Find the underlined expression in the traceback. The part under the
~ characters is the value that is None. - Print it with its type just before the failing line:
print(type(x), repr(x)). - Trace it back to where it was assigned. Was it a function call, a
.get(), a regex, a database fetch or a parsed response? - Decide what "no value" should mean there: a default, a clear error, or a skip.
For an assumption that must always hold, an assert gives a far clearer failure than the TypeError would:
order = get_order()
print(type(order), repr(order))
assert order is not None, "get_order() returned None - check the API call above"
<class 'NoneType'> None
AssertionError: get_order() returned None - check the API call above
You can also run the script under the debugger with python3 -m pdb -c continue script.py. When the error happens, pdb stops on the failing line so you can inspect variables:
Uncaught exception. Entering post mortem debugging
-> print(user["name"])
(Pdb) None
(Pdb) <class 'NoneType'>
I typed p user and p type(user) at those two prompts. Python strips assert statements under -O, so don't use them to validate user input.
Which fix should you use?
| Where the None came from | Best fix |
|---|
| Your own function | Add the missing return, or raise an error on the "not found" path |
list.sort(), append(), update() | Don't assign the result; use sorted() if you need a new list |
dict.get() | Pass a default: d.get("k", {}) |
JSON null | data.get("k") or {} |
re.match(), re.search() | if (m := re.search(...)): |
fetchone(), find(), select_one() | if row is None: then handle "not found" |
os.environ.get() | Exit early with a clear message |
Avoid wrapping the line in try/except TypeError as a first resort. It hides the real problem and can swallow unrelated errors. If your script then fails on an import instead, my guide to ModuleNotFoundError: No module named covers that next step.
Frequently asked questions
What does 'NoneType' object is not subscriptable mean?
It means you used square brackets, such as x[0] or x["key"], on a variable whose value is None. None has no items to index, so Python raises a TypeError. The fix is to find out why the variable is None and either give it real data or check for None first.
Why does my function return None in Python?
A function returns None when it has no return statement, when a return is skipped on some code path, or when you return the result of an in-place method such as list.sort(). Printing a value inside the function doesn't return it to the caller.
Is NoneType the same as None?
None is the value and NoneType is its type. type(None) prints <class 'NoneType'>, and None is the only value of that type, which is why error messages mention NoneType.
How do I check if a variable is None in Python?
Use x is None or x is not None. PEP 8 recommends is rather than ==, because is checks identity and can't be fooled by objects that define their own equality.
Why does list.sort() return None?
sort() changes the list in place, and Python's in-place methods return None to make that clear. Call scores.sort() on its own line, or use sorted(scores) if you want a new sorted list.
How do I fix this error with requests or a JSON API?
Print the parsed response to see which field is null, then use data.get("field") or {} for optional objects. Also check the status code first, because an error response often has a different shape from a successful one.
Can VS Code warn me before I run the code?
Yes. Turn on type checking (set python.analysis.typeCheckingMode to basic or stricter), add a return type such as Optional[dict[str, str]], and Pylance, which uses Pyright, flags the line with reportOptionalSubscript. mypy reports the same problem as a [index] error.
Last checked: 4 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.