How to Fix TypeError: list indices must be integers or slices, not str

3D isometric tech render of a glowing integer index badge replacing a rejected string key tag on a numbered array track to fix a Python TypeError.

Parsing a JSON API response is the #1 place developers hit this wall. You write users["name"] expecting a dictionary, only to get TypeError: list indices must be integers or slices, not str because users is actually a list of dictionaries ([{"name": "Alice"}]).

Python lists only accept whole-number positions (users[0]) or slices (users[0:2]). Passing a string key—or an index number accidentally stored as a string like "0" from input()—triggers an immediate TypeError. This error is the exact mirror image of KeyError: 0, where a dictionary is mistakenly indexed like a list.

Reading the List Index TypeError Traceback

Read the traceback from the bottom up. The final line tells you two facts: the container on the left of the square brackets is a list, and the value inside the square brackets evaluated to a str.

Traceback (most recent call last):
  File "/var/www/app/main.py", line 2, in <module>
    print(users["name"])
TypeError: list indices must be integers or slices, not str

(Note: In Python 3.11+, the traceback adds ~~~~~^^^^^^^^ carets directly under the failing bracket lookup, which helps isolate the exact bracket when you chain lookups like data["users"]["name"] on a single line).

Tested Environment: Python 3.10.16 (Linux) | Python 3.11+ traceback carets and string-index message updates noted.

Part of: The Ultimate Python Debugging Guide

⚡ The Quick Fix: Index the List First, or Convert with int()

Look at the expression container[index] on the failing line and apply whichever fix matches your intent:

Scenario A: You Have a List of Dictionaries (JSON / API Data) If users is a list containing dictionaries, you must select a specific dictionary by its integer position ([0])—or loop through the list—before looking up the string key:

users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

# Fails: 'users' is a list, not a dict
print(users["name"])

# Fix 1: Access the first dictionary at index 0, then grab "name"
print(users[0]["name"])  # Prints: Alice

# Fix 2: Loop through every dictionary in the list
for user in users:
    print(user["name"])

Scenario B: Your Index Number Is Stored as a String ("0" Instead of 0) If you got the index from input(), a URL query string, or a CSV file, it is a string ("1"). Wrap it in int():

options = ["Start", "Load", "Quit"]
choice = "1"  # e.g., returned by input()

# Fails: "1" is a str
print(options[choice])

# Fix: Convert the string to an integer first
print(options[int(choice)])  # Prints: Load

Inspect Nested JSON Structures Before Indexing

When working with deeply nested API responses, it is easy to lose track of whether a level in the JSON hierarchy is a dictionary {} or a list [].

Drop this 3-line diagnostic right above the crash to print the outer container type and inspect its first element:

payload = [{"id": 101, "name": "Alice"}, {"id": 102, "name": "Bob"}]

print("Outer type:", type(payload), "| Length:", len(payload))
if isinstance(payload, list) and payload:
    print("First item type:", type(payload[0]), "| Sample:", payload[0])

Verified Output (Python 3.10.16):

Outer type: <class 'list'> | Length: 2
First item type: <class 'dict'> | Sample: {'id': 101, 'name': 'Alice'}

Seeing Outer type: <class 'list'> and First item type: <class 'dict'> immediately confirms you need payload[0]["name"] rather than payload["name"].

Common Causes of list indices must be integers or slices, not str

Here are the five most common ways this error appears in real codebases.

1. Indexing a JSON Array or List-Returning Function as a Dict

Many REST APIs wrap their payload in a top-level JSON array ([ {...} ]) or store nested records inside a list under a key like "results". Similarly, built-in and standard library functions like sorted(d.items()), re.findall(), str.split(), and os.listdir() always return a list—even when you started with a dictionary.

  • The Trigger:
response = {"status": "ok", "results": [{"temp": 22.5}]}
# Fails: response["results"] is a list, so ["temp"] fails
current_temp = response["results"]["temp"]

# Also fails: sorted(d.items()) returns a list of (key, value) tuples, not a dict!
scores = {"bob": 20, "alice": 50}
sorted_scores = sorted(scores.items())
print(sorted_scores["alice"])
  • The Fix: For JSON arrays, index the list with [0] before requesting the key (response["results"][0]["temp"]). If you sorted a dictionary with sorted(scores.items()), convert the resulting list of tuples back into a dictionary first: dict(sorted(scores.items()))["alice"].
  • How to tell it’s this one: When you print the variable, it starts and ends with square brackets [...] instead of curly braces {...}.

2. Using the Loop Item Instead of the Loop Index

If you come to Python from C, Java, or JavaScript, it is easy to write for x in my_list: and mistakenly treat x as a numerical loop counter. In Python, a for loop iterates directly over the elements of the list, not their integer indices.

  • The Trigger:
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    # Fails: 'fruit' is the string "apple", so fruits["apple"] crashes
    print(fruits[fruit])

The Fix: Print the loop variable directly (print(fruit)). If you also need the integer index, use enumerate():

for index, fruit in enumerate(fruits):
    print(index, fruit)
  • How to tell it’s this one: The crash happens inside a for x in list: block on an expression written as list[x].

3. Passing input() Directly Into List Brackets

In Python 3, the built-in input() function always returns a str, even if the user types digits like 0 or 2.

  • The Trigger:
servers = ["us-east", "eu-west", "ap-south"]
idx = input("Select server (0-2): ")
selected = servers[idx]  # Fails: idx is '0' (str), not 0 (int)
  • The Fix: Convert the input to an integer with int(idx). (Note: If the user types non-digits like "abc", int() will raise a ValueError, which you can catch with try/except ValueError:).
  • How to tell it’s this one: The variable inside the square brackets comes from input(), os.getenv(), or sys.argv.

4. Reading Rows with csv.reader Instead of csv.DictReader

Python’s standard csv module has two readers. csv.reader() yields each row as a list of strings (['1', 'Alice']), whereas csv.DictReader() yields each row as a dict keyed by the header row ({'id': '1', 'name': 'Alice'}).

  • The Trigger:
import csv, io

raw_csv = "id,name\n1,Alice\n"
reader = csv.reader(io.StringIO(raw_csv))
for row in reader:
    print(row["name"])  # Fails: 'row' is a list
  • The Fix: Replace csv.reader(...) with csv.DictReader(...) if you want to look up columns by name, or use integer indices (row[1]) with csv.reader.
  • How to tell it’s this one: The crash occurs inside a loop over a CSV file or database cursor where each row is a sequence rather than a mapping.

5. Missing a Comma Between Nested Lists in a Multi-Line List

When defining a list of lists across multiple lines, forgetting a trailing comma after one of the inner lists causes Python to interpret the next inner list as an index lookup (list_a[list_b]). If the second list contains a single string, Python evaluates ["admin", "editor"]["viewer"].

  • The Trigger:
roles = [
    ["admin", "editor"]  # <-- Missing comma here!
    ["viewer"]
]

Verified Output (Python 3.10.16):

<string>:2: SyntaxWarning: list indices must be integers or slices, not str; perhaps you missed a comma?
Traceback (most recent call last):
  File "<string>", line 2, in <module>
TypeError: list indices must be integers or slices, not str
  • The Fix: Add the missing trailing comma at the end of the first inner list: ["admin", "editor"],.
  • How to tell it’s this one: Python prints a SyntaxWarning: ... perhaps you missed a comma? right above the TypeError, and the traceback points to a multi-line list or dictionary literal.

Python raises variations of this exact TypeError depending on the sequence type (list, tuple, str) and the wrong index type you passed in:

Error Message VariantTypical CauseThe Fix
list indices must be integers or slices, not float (Verified 3.10.16)Using single-slash division / to calculate a midpoint (e.g., items[len(items) / 2]). In Python 3, / always returns a float (2.0).Use floor division // so the result is an int: items[len(items) // 2].
list indices must be integers or slices, not tuple (Verified 3.10.16)Indexing a 2D nested Python list using NumPy comma syntax (matrix[0, 1]). In Python, 0, 1 inside brackets creates the tuple (0, 1).Chain separate brackets for standard Python lists (matrix[0][1]), or convert the list to a NumPy array (np.array(matrix)[0, 1]).
list indices must be integers or slices, not NoneType (Verified 3.10.16)Passing a variable into items[idx] when idx evaluated to None (e.g., from a function that forgot a return statement or a failed .get()).Check if idx is not None: before indexing the list.
tuple indices must be integers or slices, not str (Verified 3.10.16)Trying to access a named field on a standard tuple (e.g., a database row from cursor.fetchall()).Index by integer position (row[0]), or use collections.namedtuple / a dictionary cursor.
string indices must be integers (Verified Python 3.10.16)Calling data["key"] when data is still an unparsed JSON string, or iterating over a dict (for k in d:) and indexing k["subkey"].Parse the raw JSON string first with data = json.loads(data), or iterate over d.values() / d.items().
string indices must be integers, not 'str' (Python 3.11+ — not run by me)Same cause as above; Python 3.11+ updated the str index error message to explicitly name the offending type (not 'str').Parse the JSON string with json.loads() or iterate over .values() / .items().

Prevent List Index TypeErrors with Type Hints and Linters

Because dynamic JSON payloads and untyped variables only fail at runtime, you can catch these bugs in your editor before running the script:

  1. Enable Ruff Rules RUF016 and COM812: Ruff rule RUF016 (invalid-index-type) directly flags indexing a list, tuple, or str with a non-integer type like a string or float, while COM812 (missing-trailing-comma) enforces trailing commas on multi-line collections. Furthermore, running Black or Ruff Format on a list with a missing comma collapses the two lines into roles = [["admin", "editor"]["viewer"]], making the accidental subscript immediately visible.
  2. Use Static Type Checkers (Mypy or Pyright): Annotate functions that accept list vs. dict structures (e.g., def process_users(users: list[dict[str, str]]) -> None:). If you write users["name"], Pyright (built into VS Code’s Pylance extension) and Mypy immediately underline "name" in red before you ever execute the file.

Worked Example: Fixing json.loads() on a List of Dictionaries

Let’s walk through a real traceback where a developer parses a JSON string from an e-commerce API and tries to read the price.

The Code (/var/www/app/main.py):

import json

raw_response = '[{"id": 1, "price": 29.99}, {"id": 2, "price": 49.99}]'

def get_first_price(json_str):
    products = json.loads(json_str)
    return products["price"]

print(get_first_price(raw_response))

Verified Traceback (Python 3.10.16):

Traceback (most recent call last):
  File "/var/www/app/main.py", line 9, in <module>
    print(get_first_price(raw_response))
  File "/var/www/app/main.py", line 7, in get_first_price
    return products["price"]
TypeError: list indices must be integers or slices, not str

The Thought Process & Fix:

  1. The crash happens on line 7: return products["price"].
  2. Looking at raw_response on line 3, the JSON string starts with [ and ends with ]. That means json.loads(json_str) returned a Python list containing two dictionaries, not a single dictionary.
  3. The Result: To get the first product’s price safely (while guarding against an empty list that would raise an IndexError), index element 0 first:
def get_first_price(json_str):
    products = json.loads(json_str)
    if not products:
        return None
    return products[0].get("price")

Frequently Asked Questions

What does “or slices” mean in list indices must be integers or slices?

Besides single integers (items[0]), Python lists allow slice objects created with colons, such as items[0:3] or items[::-1] (or explicitly via items[slice(0, 3)]). Integers and slices are the only two types standard Python lists accept inside square brackets.

Why do I get TypeError: string indices must be integers when reading JSON?

If you get string indices must be integers (instead of list indices), your variable is still a raw str, not a parsed dictionary or list. This happens when you call response.text["key"] instead of response.json()["key"] with the requests library, or when a JSON file was double-encoded so json.loads() returned a string and needs to be parsed once more.

Why does matrix[0, 1] fail on a Python list when it works in NumPy?

In standard Python, putting a comma inside square brackets (matrix[0, 1]) passes the tuple (0, 1) to list.__getitem__(). Standard Python lists are 1-dimensional containers and do not support tuple indexing—you must write matrix[0][1]. NumPy arrays (numpy.ndarray) and Pandas .loc/.iloc indexers specifically override __getitem__ to accept tuples for multi-dimensional indexing.

Why did dividing my list length (items[len(items) / 2]) cause a TypeError?

In Python 3, the / operator performs true floating-point division and always returns a float (so 4 / 2 evaluates to 2.0, not 2). Even though 2.0 is a whole number mathematically, Python refuses float indices and raises TypeError: list indices must be integers or slices, not float. Always use the floor division operator // (items[len(items) // 2]) when calculating indices.

How do I find a dictionary inside a list by a key value (like "id")?

Since you cannot write users["Alice"] on a list of dictionaries, use next() with a generator expression to find the matching dictionary safely:
alice = next((u for u in users if u.get("name") == "Alice"), None)

This returns the first dictionary where "name" is "Alice", or None if no match exists.

Similar Posts

Leave a Reply