DEV Community

Cover image for TypeError: 'NoneType' object is not subscriptable in Python: 10 Causes and Fixes (Tested Examples)
Mahir Faysal
Mahir Faysal

Posted on Originally published at mfaysal.com

TypeError: 'NoneType' object is not subscriptable in Python: 10 Causes and Fixes (Tested Examples)

"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
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
Ana
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode
    print(scores[0])
          ~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
7
7 [42, 7, 19]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode
    host = config.get("database")["host"]
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode
host: localhost
no database section in config
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode
None
    print(m[1])
          ~^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode
match:  None
search: <re.Match object; span=(13, 19), match='45 GBP'>
amount = 45
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
{'user': None, 'items': []}
    print(data["user"]["email"])
          ~~~~~~~~~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode
email: not set
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
    print(best["score"])
          ~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
no rows yet
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode
None
    print(row[0])
          ~~~^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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]}")
Enter fullscreen mode Exit fullscreen mode
user 1: ana@example.com
user 99: not found
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
9.99
    print(find_product(products, "B2")["price"])
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode
LookupError: no product with sku 'B2'
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
/signup
    print(soup.find("a", class_="button")["href"])
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode
link not found
/signup
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode
    print("first 4 chars:", secret[:4])
                            ~~~~~~^^^^
TypeError: 'NoneType' object is not subscriptable
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode
API_SECRET is not set. Export it before running this script.
first 4 chars: sk_t
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
    cart["apples"] = 3
    ~~~~^^^^^^^^^^
TypeError: 'NoneType' object does not support item assignment
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode
  /workspace/seo-posts-nov04/test/python/c13_typed.py:9:7 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
Enter fullscreen mode Exit fullscreen mode

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

  1. Find the underlined expression in the traceback. The part under the ~ characters is the value that is None.
  2. Print it with its type just before the failing line: print(type(x), repr(x)).
  3. Trace it back to where it was assigned. Was it a function call, a .get(), a regex, a database fetch or a parsed response?
  4. 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"
Enter fullscreen mode Exit fullscreen mode
<class 'NoneType'> None
AssertionError: get_order() returned None - check the API call above
Enter fullscreen mode Exit fullscreen mode

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'>
Enter fullscreen mode Exit fullscreen mode

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 , 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.


Originally published on mfaysal.com.

I'm learning cybersecurity on TryHackMe — follow my progress at tryhackme.com/p/mfaysal.dev.

Top comments (0)