Programming with AI
ENRU
2.4

Variables, types, data structures

four containers that almost everything is built from

What it is

Almost all data in a program sits in one of four structures: a dictionary, a list, a tuple or a set. Knowing how they differ makes reading code much faster — the shape of the brackets alone tells you what is going on.

We will also cover type annotations (text: str) separately: they appear constantly in AI-written code and read more easily than they look.

What it looks like in code

user = {"id": 7, "name": "Ola", "cities": ["Tønsberg", "Oslo"]}

print(user["name"])
print(user.get("email"))
print(user.get("email", "not set"))

cities = user["cities"]
cities.append("Bergen")
print(user["cities"])          # the original changed too

point = (59.27, 10.41)
lat, lon = point
print(lat, lon)

seen = {"Oslo", "Bergen", "Oslo"}
print(sorted(seen), len(seen))

Output:

measured
Ola
None
not set
['Tønsberg', 'Oslo', 'Bergen']
59.27 10.41
['Bergen', 'Oslo'] 2

The four structures

KindBracketsWhat forSign in code
Dictionary{"key": value}data with named fieldsaccess by x["name"]
List[1, 2, 3]an ordered collectionaccess by index x[0], .append()
Tuple(1, 2)a set of values nobody changesunpacking a, b = x
Set{1, 2, 3}unique valuesa check like if x in seen

Curly braces mean both a dictionary and a set — easy to tell apart: if there are colons inside, it's a dictionary.

Two lines worth noticing

print(user.get("email"))           # None
print(user.get("email", "not set"))

user["email"] raises KeyError when the key is missing. user.get("email") quietly returns None. The difference matters and comes up constantly in AI-written code: .get() is a favourite way to "fix" an error by hiding it (article 2.14).

cities = user["cities"]
cities.append("Bergen")
print(user["cities"])   # ['Tønsberg', 'Oslo', 'Bergen']

cities is not a copy, it is the same thing. Lists and dictionaries are passed by reference: change one and both change. This is the source of a whole class of bugs that look like magic.

Type annotations

def add_reminder(user_id: int, text: str) -> int:

Read left to right: user_id is an integer, text is a string, the function returns an integer. This is a hint for the reader, not a check: Python does not enforce annotations, and nothing stops you passing a string where a number was promised.

The ones you will meet most often:

WrittenMeans
str, int, float, boolstring, integer, float, true/false
list[str]a list of strings
dict[str, int]a dictionary: string keys, integer values
str | Nonea string or nothing
-> Nonethe function returns nothing

str | None is especially useful when reading: it tells you outright that the result has to be checked.

What to tell the AI

Prompt
Which data structures are used in this file and what is in
each? Briefly, one line per variable.

A useful question when data comes from outside:

Prompt
Show me an example of the data that will reach this function.
One happy case and one with missing fields.

When you need to know whether something changes unexpectedly:

Prompt
Does this function modify the list it was given, or build
a new one?

What to look for

.get() where an error is wanted. If a field is required, its absence is an error, not a reason to substitute None. A silent None surfaces later and further from the cause.

Modifying what was passed in. A function received a list and appended to it — the caller's list changed too. If that wasn't intended, hard bugs follow.

A dictionary instead of a proper structure. data["result"]["items"][0]["price"] is a sign nobody parsed the data. Every link in that chain can fail, and the traceback will only show the last one.

Annotations that don't match the code. -> str while the function sometimes returns None. Annotations check nothing, so they can drift away from the code unnoticed.

Cheat sheet

  • Four structures: dictionary · list · tuple · set.

  • Curly braces with colons — a dictionary; without — a set.

  • x["k"] fails on a missing key, x.get("k") returns None.

  • Lists and dictionaries are passed by reference: change one, both change.

  • str | None in an annotation — the result must be checked.

  • Annotations check nothing; they are for the reader.