TypeError: list indices must be integers or slices, not str means you indexed a list with a string key, the way you would index a dictionary. A list only accepts an integer or a slice inside the brackets — "name" is not a valid index.
Nearly always this happens while parsing JSON: json.loads() or response.json() returned a list of dictionaries, so data["name"] has to become data[0]["name"]. The message even names the culprit: the word list says the outer object is a list, not a dict.
What does "list indices must be integers or slices, not str" mean?
Python is telling you that the object to the left of the square brackets is a list, while the thing inside the brackets is a string. A list stores items by position, so you can only reach in with a number (data[0], data[-1]) or a slice (data[:3]). String keys belong to dictionaries, which are built differently. The error fires immediately, before Python ever looks for a field with that name.
import json
raw = '[{"name": "Maria", "score": 91}, {"name": "Igor", "score": 78}]'
data = json.loads(raw)
print(data["name"])
Traceback (most recent call last):
File "app.py", line 6, in <module>
print(data["name"])
~~~~^^^^^^^^
TypeError: list indices must be integers or slices, not str
The most useful part of the message is the first word. list indices means the object in front of the brackets is a list; a string would give string indices, a tuple tuple indices. Python has already named the real type of your data — you only have to believe it.
Why does JSON hand you a list of dictionaries?
The top level of a JSON document is either an object {...} or an array [...]: an object becomes a Python dict, an array becomes a list. API docs usually print one element, while the server returns an array of them — and that is where intuition breaks. The path copied from the docs, ["items"]["title"], needs one more level between the two keys: the element number.
response = {
"status": "ok",
"items": [
{"id": 17, "title": "Coffee grinder", "price": 3990},
{"id": 24, "title": "Travel mug", "price": 1290},
],
}
print(response["items"]["title"])
Traceback (most recent call last):
File "app.py", line 9, in <module>
print(response["items"]["title"])
~~~~~~~~~~~~~~~~~^^^^^^^^^
TypeError: list indices must be integers or slices, not str
response["items"] is already a list, so the next step has to be a number or a loop:
response = {
"status": "ok",
"items": [{"title": "Coffee grinder", "price": 3990}, {"title": "Travel mug", "price": 1290}],
}
print(response["items"][0]["title"])
print(sum(item["price"] for item in response["items"]))
Coffee grinder
5280
The same idea as a table — what actually sits in the variable, and the access that works.
| What you have |
Broken |
Correct |
List of dicts [{...}, {...}] |
data["name"] |
data[0]["name"] |
Dict with a list inside {"items": [...]} |
data["items"]["title"] |
data["items"][0]["title"] |
String holding JSON '[{"name": ...}]' |
data[0]["name"] |
json.loads(data)[0]["name"] |
Row from csv.reader ["17", "Coffee grinder"] |
row["title"] |
row[1] or csv.DictReader |
CSV sets the same trap: csv.reader yields lists of strings, not dictionaries, so row["title"] raises exactly this error. Switch to csv.DictReader — it reads the header row and turns every line into a dict, after which row["title"] works.
How do you check what you actually have in one line?
Stop guessing at the structure and print it. Three lines settle the question: type(data).__name__ gives the type, len(data) gives the size, and list(data)[:5] shows the first chunk without flooding the terminal with a megabyte of output. Reach for list(data)[:5] rather than a data[:1] slice: slicing a dict raises, while that one line is safe on a list and on a dict alike. More reliable than printing data whole: in a large response you cannot see the shape by eye. One precondition: the print has to land somewhere you can read it. Started by double-click, the window closes before you see either the output or the traceback — the fix is a way of launching the file, covered in the console window closes immediately.
import json
raw = '{"count": 2, "results": [{"name": "Maria"}, {"name": "Igor"}]}'
data = json.loads(raw)
print(type(data).__name__)
print(list(data)[:5])
items = data["results"]
print(type(items).__name__, len(items))
print(items[0])
dict
['count', 'results']
list 2
{'name': 'Maria'}
Now the shape is visible: a dict outside with keys count and results, and a list of two dicts inside results. The path to a name is data["results"][0]["name"]. When an endpoint returns a list sometimes and a dict other times, branch on isinstance(payload, list) instead of hoping — see type checks with isinstance.
How is this different from "string indices must be integers"?
They are neighbouring messages about the same confusion, but with different causes. list indices means the JSON was parsed and you are holding a list — an integer index is missing. string indices must be integers, not 'str' means nothing was parsed at all: the variable still holds the raw JSON text, and Python is dutifully trying to take a character out of it by position. The fixes differ: add an index in the first case, call json.loads() in the second.
text = '[{"name": "Maria", "score": 91}]'
print(text[0]["name"])
Traceback (most recent call last):
File "app.py", line 3, in <module>
print(text[0]["name"])
~~~~~~~^^^^^^^^
TypeError: string indices must be integers, not 'str'
text[0] returned the single character [, and then a key was requested from that character. Replace f.read() with json.load(f) and it becomes a real list of dicts. The same trap hits requests users: response.text is a string, the parsed structure comes from response.json().
The other common source of string indices is looping over a dictionary: for user in data yields keys, which are strings, so user["name"] blows up. Use data.values() or data.items().
What can go inside the brackets of a list?
An integer, a negative integer, or a slice — that is the complete list. Nothing else is accepted, neither a string nor a float.
prices = [199, 349, 990, 1290]
print(prices[0], prices[-1], prices[1:3])
199 1290 [349, 990]
A float index produces almost the same message, with a different type at the end:
prices = [199, 349, 990, 1290]
print(prices[len(prices) / 2])
Traceback (most recent call last):
File "app.py", line 2, in <module>
print(prices[len(prices) / 2])
~~~~~~^^^^^^^^^^^^^^^^^
TypeError: list indices must be integers or slices, not float
/ always produces a float in Python, even on an exact division. For the middle of a list use //: prices[len(prices) // 2] returns 990. Every slicing form is in the handbook under list slicing.
The mirror-image case — a list used as a dict key — raises TypeError: unhashable type: 'list', explained in TypeError: unhashable type 'list' in Python.
How do you loop over a list of dicts and read a field?
Loop with for: on each pass the loop variable is a whole dictionary you can index by key, so no positions are counted by hand. For one field from every element, a list comprehension is shorter; for a single element matching a condition, next() over a generator with a default does it in one line.
data = [{"name": "Maria", "score": 91}, {"name": "Igor", "score": 78}]
for user in data:
print(user["name"], user["score"])
names = [user["name"] for user in data]
print(names)
igor = next((u for u in data if u["name"] == "Igor"), None)
print(igor)
Maria 91
Igor 78
['Maria', 'Igor']
{'name': 'Igor', 'score': 78}
Drill exactly this on the free task Cart total with a discount: the input is a list of item dictionaries to sum and discount. Watch out for the neighbouring bug — deleting elements inside such a loop silently skips some of them, as shown in deleting from a list while looping skips items.
TypeError vs KeyError vs IndexError
The three look similar but answer different questions. TypeError means the index has the wrong type: a string can never index a list. KeyError means the object really is a dict, but that key is missing. IndexError means it really is a list, but shorter than the position you asked for.
| Message |
What happened |
First move |
TypeError: list indices must be integers or slices, not str |
a list indexed with a string |
add a position: data[0]["name"] |
TypeError: string indices must be integers, not 'str' |
JSON not parsed yet, still a string |
json.loads(text) or response.json() |
KeyError: 'email' |
it is a dict, but the key is missing |
inspect list(user) or use user.get("email") |
IndexError: list index out of range |
it is a list, but it is shorter |
check len(data) |
Forgiving dictionary access (get, defaults, setdefault) is in dict access and defaults.
Common mistakes
- Copying a path from the API docs without checking the response shape. The docs show one object; the server returns an array of them. Fix:
print(type(data).__name__, len(data)) before the first field access.
- Using
response.text instead of response.json(). You keep a string, so you get string indices must be integers. Fix: response.json() or json.loads(response.text).
- Reading a file with
f.read() instead of json.load(f). Same string-instead-of-structure problem.
- Looping over a dict and expecting values.
for user in data gives string keys. Fix: data.values() or data.items().
- Indexing a list with whatever came out of
input(). It is always a string. Fix: prices[int(n)].
- Computing the middle of a list with
/. True division yields a float. Fix: use //.
- Reading a
csv.reader row by column name. That reader yields lists. Fix: csv.DictReader.
Practice: take a nested JSON apart in the browser
This sticks only once you run code. On Python Arena tasks are graded by hidden tests in the browser, with nothing to install. Start with Merge similar items: you walk a list and combine matching entries — the exact operation where this error shows up. Then take the free dictionary block: dictionary tasks.
Reading and writing JSON safely, json.dump and json.load included, is covered under JSON serialization. And if saving a file turned non-ASCII letters into escape sequences, nothing is broken — see Python JSON: Cyrillic saved as Unicode escapes.
In short
list indices must be integers… means a string sits in the brackets of a list, where a position or slice belongs.
- The first word is the diagnosis:
list indices means you hold a list, string indices means you still hold a string.
- JSON is the usual source: a top-level array becomes a list of dicts, so
data["name"] becomes data[0]["name"].
- Print
type(data).__name__, len(data) and list(data)[:5] before the first field access.
string indices must be integers, not 'str' is a missing json.loads(), not a missing index.
- Loop a list of dicts with
for: the loop variable is already a dict and takes keys.
TypeError is a wrong index type, KeyError a missing key, IndexError a list that is too short.
TypeError: list indices must be integers or slices, not strmeans you indexed a list with a string key, the way you would index a dictionary. A list only accepts an integer or a slice inside the brackets —"name"is not a valid index.Nearly always this happens while parsing JSON:
json.loads()orresponse.json()returned a list of dictionaries, sodata["name"]has to becomedata[0]["name"]. The message even names the culprit: the wordlistsays the outer object is a list, not a dict.What does "list indices must be integers or slices, not str" mean?
Python is telling you that the object to the left of the square brackets is a list, while the thing inside the brackets is a string. A list stores items by position, so you can only reach in with a number (
data[0],data[-1]) or a slice (data[:3]). String keys belong to dictionaries, which are built differently. The error fires immediately, before Python ever looks for a field with that name.import json raw = '[{"name": "Maria", "score": 91}, {"name": "Igor", "score": 78}]' data = json.loads(raw) print(data["name"])The most useful part of the message is the first word.
list indicesmeans the object in front of the brackets is a list; a string would givestring indices, a tupletuple indices. Python has already named the real type of your data — you only have to believe it.Why does JSON hand you a list of dictionaries?
The top level of a JSON document is either an object
{...}or an array[...]: an object becomes a Python dict, an array becomes a list. API docs usually print one element, while the server returns an array of them — and that is where intuition breaks. The path copied from the docs,["items"]["title"], needs one more level between the two keys: the element number.response = { "status": "ok", "items": [ {"id": 17, "title": "Coffee grinder", "price": 3990}, {"id": 24, "title": "Travel mug", "price": 1290}, ], } print(response["items"]["title"])response["items"]is already a list, so the next step has to be a number or a loop:response = { "status": "ok", "items": [{"title": "Coffee grinder", "price": 3990}, {"title": "Travel mug", "price": 1290}], } print(response["items"][0]["title"]) print(sum(item["price"] for item in response["items"]))The same idea as a table — what actually sits in the variable, and the access that works.
[{...}, {...}]data["name"]data[0]["name"]{"items": [...]}data["items"]["title"]data["items"][0]["title"]'[{"name": ...}]'data[0]["name"]json.loads(data)[0]["name"]csv.reader["17", "Coffee grinder"]row["title"]row[1]orcsv.DictReaderCSV sets the same trap:
csv.readeryields lists of strings, not dictionaries, sorow["title"]raises exactly this error. Switch tocsv.DictReader— it reads the header row and turns every line into a dict, after whichrow["title"]works.How do you check what you actually have in one line?
Stop guessing at the structure and print it. Three lines settle the question:
type(data).__name__gives the type,len(data)gives the size, andlist(data)[:5]shows the first chunk without flooding the terminal with a megabyte of output. Reach forlist(data)[:5]rather than adata[:1]slice: slicing a dict raises, while that one line is safe on a list and on a dict alike. More reliable than printingdatawhole: in a large response you cannot see the shape by eye. One precondition: theprinthas to land somewhere you can read it. Started by double-click, the window closes before you see either the output or the traceback — the fix is a way of launching the file, covered in the console window closes immediately.import json raw = '{"count": 2, "results": [{"name": "Maria"}, {"name": "Igor"}]}' data = json.loads(raw) print(type(data).__name__) print(list(data)[:5]) items = data["results"] print(type(items).__name__, len(items)) print(items[0])Now the shape is visible: a dict outside with keys
countandresults, and a list of two dicts insideresults. The path to a name isdata["results"][0]["name"]. When an endpoint returns a list sometimes and a dict other times, branch onisinstance(payload, list)instead of hoping — see type checks with isinstance.How is this different from "string indices must be integers"?
They are neighbouring messages about the same confusion, but with different causes.
list indicesmeans the JSON was parsed and you are holding a list — an integer index is missing.string indices must be integers, not 'str'means nothing was parsed at all: the variable still holds the raw JSON text, and Python is dutifully trying to take a character out of it by position. The fixes differ: add an index in the first case, calljson.loads()in the second.text = '[{"name": "Maria", "score": 91}]' # what f.read() or response.text gives you print(text[0]["name"])text[0]returned the single character[, and then a key was requested from that character. Replacef.read()withjson.load(f)and it becomes a real list of dicts. The same trap hitsrequestsusers:response.textis a string, the parsed structure comes fromresponse.json().The other common source of
string indicesis looping over a dictionary:for user in datayields keys, which are strings, souser["name"]blows up. Usedata.values()ordata.items().What can go inside the brackets of a list?
An integer, a negative integer, or a slice — that is the complete list. Nothing else is accepted, neither a string nor a float.
prices = [199, 349, 990, 1290] print(prices[0], prices[-1], prices[1:3])A float index produces almost the same message, with a different type at the end:
prices = [199, 349, 990, 1290] print(prices[len(prices) / 2])/always produces a float in Python, even on an exact division. For the middle of a list use//:prices[len(prices) // 2]returns990. Every slicing form is in the handbook under list slicing.The mirror-image case — a list used as a dict key — raises
TypeError: unhashable type: 'list', explained in TypeError: unhashable type 'list' in Python.How do you loop over a list of dicts and read a field?
Loop with
for: on each pass the loop variable is a whole dictionary you can index by key, so no positions are counted by hand. For one field from every element, a list comprehension is shorter; for a single element matching a condition,next()over a generator with a default does it in one line.data = [{"name": "Maria", "score": 91}, {"name": "Igor", "score": 78}] for user in data: print(user["name"], user["score"]) names = [user["name"] for user in data] print(names) igor = next((u for u in data if u["name"] == "Igor"), None) print(igor)Drill exactly this on the free task Cart total with a discount: the input is a list of item dictionaries to sum and discount. Watch out for the neighbouring bug — deleting elements inside such a loop silently skips some of them, as shown in deleting from a list while looping skips items.
TypeError vs KeyError vs IndexError
The three look similar but answer different questions.
TypeErrormeans the index has the wrong type: a string can never index a list.KeyErrormeans the object really is a dict, but that key is missing.IndexErrormeans it really is a list, but shorter than the position you asked for.TypeError: list indices must be integers or slices, not strdata[0]["name"]TypeError: string indices must be integers, not 'str'json.loads(text)orresponse.json()KeyError: 'email'list(user)or useuser.get("email")IndexError: list index out of rangelen(data)Forgiving dictionary access (
get, defaults,setdefault) is in dict access and defaults.Common mistakes
print(type(data).__name__, len(data))before the first field access.response.textinstead ofresponse.json(). You keep a string, so you getstring indices must be integers. Fix:response.json()orjson.loads(response.text).f.read()instead ofjson.load(f). Same string-instead-of-structure problem.for user in datagives string keys. Fix:data.values()ordata.items().input(). It is always a string. Fix:prices[int(n)]./. True division yields a float. Fix: use//.csv.readerrow by column name. That reader yields lists. Fix:csv.DictReader.Practice: take a nested JSON apart in the browser
This sticks only once you run code. On Python Arena tasks are graded by hidden tests in the browser, with nothing to install. Start with Merge similar items: you walk a list and combine matching entries — the exact operation where this error shows up. Then take the free dictionary block: dictionary tasks.
Reading and writing JSON safely,
json.dumpandjson.loadincluded, is covered under JSON serialization. And if saving a file turned non-ASCII letters into escape sequences, nothing is broken — see Python JSON: Cyrillic saved as Unicode escapes.In short
list indices must be integers…means a string sits in the brackets of a list, where a position or slice belongs.list indicesmeans you hold a list,string indicesmeans you still hold a string.data["name"]becomesdata[0]["name"].type(data).__name__,len(data)andlist(data)[:5]before the first field access.string indices must be integers, not 'str'is a missingjson.loads(), not a missing index.for: the loop variable is already a dict and takes keys.TypeErroris a wrong index type,KeyErrora missing key,IndexErrora list that is too short.