lists, dicts, and the shape of an api response
Every API response is the same two shapes nested
Every JSON blob that comes back from a Stripe call, every config file Cursor wrote you, every response from the OpenAI API, every row of data your front end fetches — all of it is some mix of the same two shapes wrapped around each other.
If you can read those two shapes, you can read 90% of the data you'll ever touch. If you can't, every API integration becomes a guessing game.
The two shapes
[10, 20, 30] # list
{"name": "Maya", "score": 92} # dict
- List
[a, b, c]— ordered. Items live at numbered positions. You get them by index:tags[0]is the first item. - Dict
{"key": value}— labeled. Items live at named keys. You get them by name:user["name"]is the value at the"name"key.
That's the entire vocabulary. Every nested data structure you'll ever read is one of these two shapes containing more of either of these two shapes.
The mental model: peel one layer at a time
When AI writes you code that reads response["data"]["users"][0]["email"],
your eyes try to read it all at once and your brain panics. Don't.
Read left to right, one bracket at a time:
response— start with the whole thing (a dict).["data"]— open the"data"key. What's inside? Probably another dict.["users"]— open the"users"key. Now you have a list.[0]— first item of that list. Whatever those items were — probably dicts.["email"]— open the"email"key on that one. Read the string.
Five steps, one layer each. The pattern never gets more complicated than this, no matter how nested the JSON gets. It just gets longer.
A worked example
The editor on the right has a typical user object. Read the structure top to bottom:
user = { # outer shape: dict
"name": "Maya", # name → a string
"tags": ["pm", "early-adopter"], # tags → a list of strings
"score": 92, # score → a number
}
Then two reads:
user["name"]— open the"name"key onuser. You get"Maya".user["tags"][0]— open the"tags"key onuser(you get a list), then take index0(you get the first string in that list,"pm").
That ["tags"][0] move — into a dict, then into a list — is the
shape you'll see in literally every codebase that talks to an API.
Where AI specifically gets this wrong
When Cursor writes code against an API response, it sometimes guesses
at the shape. It'll assume response["users"] is a list when it's
actually a dict, or vice versa, and the code crashes with a confusing
error like TypeError: list indices must be integers, not str.
When you see that error in AI-generated code, the fix is almost always: print the thing one level up, eyeball the actual shape, and adjust the brackets. We'll drill that in the loops chapter and again in nested data later in this chapter.
Run the editor. Two prints, two paths into the same little structure.