TechByteByByte

Python Collections

Learn how Python stores and manages collections of data using lists, tuples, sets and dictionaries, with practical examples from AI applications.

#Python#Collections#Lists#Dictionaries#Sets#Tuples#AI

The problem: Module 1 used individual values, but real applications need groups: thousands of documents, a sequence of tokens, or a conversation full of messages. Creating document1, document2, and hundreds of other variable names would be impossible to manage.

What you will learn: Collections let Python hold many related values as one organized object. You will compare lists, tuples, sets, and dictionaries, then choose among them based on order, changeability, uniqueness, and key-based lookup.


1. Lists

First picture the difference between one value and a collection:

one document   → "Python is readable."
many documents → [document 0, document 1, document 2]
                       ↑          ↑          ↑
                    position   position   position

The brackets create one list object that can be passed to a function, looped over, or returned from an API-processing step. The values do not have to be text, although grouping related kinds of values usually makes code clearer.

What a List Stores

A list is an ordered, changeable collection of values.

tokens = ["The", "capital", "of", "France", "is", "Paris"]

Why One Variable Is Better Than Hundreds

Without lists, you’d need a separate variable for every single item — token1, token2, token3… completely unworkable once you have hundreds or thousands of items.

Picture It as Numbered Boxes

A list is a numbered row of boxes, all bundled under one name. You can add boxes, remove boxes, or look inside any box by its position.

A Familiar Example

Think of a list as a shopping list on paper — items in order, and you can add an item, cross one out, or check what’s at position 3.

Syntax

my_list = [item1, item2, item3]

Example

# Storing retrieved documents for a RAG pipeline
documents = [
    "Paris is the capital of France.",
    "The Eiffel Tower is located in Paris.",
    "France is a country in Western Europe."
]

# Adding a newly retrieved document
documents.append("The Louvre is the world's most visited museum.")

# Removing a document
documents.remove("France is a country in Western Europe.")

print(documents)
print(len(documents))

Expected Output:

['Paris is the capital of France.', 'The Eiffel Tower is located in Paris.', "The Louvre is the world's most visited museum."]
3

🧠 Notice the count: we started with 3 documents, appended one (→ 4), then removed one (→ 3). Tracking additions and removals like this is a good habit to build now — it’s the same mental math you’ll do when managing a growing conversation history or a document buffer later.

How It Works

  • [...] creates the list.
  • .append(x) adds x to the end.
  • .remove(x) deletes the first item equal to x.
  • len(...) counts how many items are currently in the list.

🤖 How Is This Used in AI?

  • Storing retrieved documents in a RAG pipeline
  • Storing tokens produced by a tokenizer
  • Storing conversation history (a list of messages)
  • Storing search results returned from a vector database
conversation = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is Python?"}
]

That’s a list holding dictionaries — you’ll recognize this exact shape the moment you look at any LLM chat API’s request format.

⚠️ Common Beginner Mistakes

documents = ["a", "b", "c"]
print(documents[3])   # IndexError — valid indices are 0, 1, 2

Lists are zero-indexed: the first item is at position 0, not 1.

Key Takeaway: A list is your default tool for “I have many of these, in a specific order, and the collection may grow or shrink.”


2. Tuples

What a List Stores

A tuple is like a list, but unchangeable (immutable) once created.

embedding_shape = (1, 1536)

Why One Variable Is Better Than Hundreds

Sometimes you want to guarantee a value can never accidentally be modified later in the program — e.g., a fixed configuration pair, or coordinates that should never drift.

Picture It as Numbered Boxes

A tuple is a sealed box — once packed, you can look inside, but you can’t swap what’s in it.

Example

model_config = ("gpt-4o-mini", 0.7)   # (model_name, temperature)
model_name, temperature = model_config   # unpacking

print(model_name)
print(temperature)

Expected Output:

gpt-4o-mini
0.7

⚠️ Common Beginner Mistake:

model_config[0] = "gpt-4o"
# TypeError: 'tuple' object does not support item assignment

🤖 How Is This Used in AI?

  • Embedding vector shapes: (batch_size, embedding_dim)
  • Returning multiple fixed values from a function, e.g. return answer, confidence_score
  • Anywhere you want to signal “this data should not change” — safer than a list for fixed structural information.

Key Takeaway: Use a tuple when the collection’s size and content should be locked once created.


3. Sets

What Is It?

A set is an unordered collection of unique values — duplicates are automatically removed.

unique_words = {"python", "ai", "rag", "python"}

🧠 Intuition

A set is a bag with no duplicates allowed — if you try to throw in something that’s already there, nothing changes.

Example

# Deduplicating tokens from two overlapping documents
doc1_words = {"python", "is", "great", "for", "ai"}
doc2_words = {"python", "is", "used", "in", "ai"}

# Union: all unique words across both documents
all_words = doc1_words | doc2_words
print(all_words)

# Intersection: words that appear in both
shared_words = doc1_words & doc2_words
print(shared_words)

Expected Output:

{'python', 'is', 'great', 'for', 'ai', 'used', 'in'}
{'python', 'is', 'ai'}

Note: set printing order isn’t guaranteed — the values matter, not the order they appear in.

🤖 How Is This Used in AI?

  • Deduplicating tokens or keywords
  • Quickly checking membership: if word in stopwords_set — sets check membership far faster than lists on large data
  • Comparing two documents’ vocabularies (union/intersection) — a simple but real technique in classic NLP preprocessing

Key Takeaway: Reach for a set when order doesn’t matter and uniqueness does.


4. Dictionaries

What Is It?

A dictionary stores key–value pairs — you look things up by name, not by position.

metadata = {"source": "wikipedia", "page": 12, "verified": True}

Why Does It Exist?

Lists are great when position matters (“the 3rd item”). But a lot of real-world data is labeled, not ordered — “give me the author” is far more natural than “give me item 4.”

🧠 Intuition

A dictionary is a labeled filing cabinet — each drawer has a label (key), and you pull out exactly the drawer you name.

A Familiar Example

Think of a phone contact list: you look up a person by name (key), and you get their number (value) — not by remembering “contact #47.”

Syntax

my_dict = {"key1": value1, "key2": value2}

Example

# A single message in an LLM API request — this IS a dictionary
message = {
    "role": "user",
    "content": "What is retrieval-augmented generation?"
}

print(message["role"])
print(message["content"])

# Adding a new key
message["timestamp"] = "2026-08-14T10:00:00Z"
print(message)

# Safe lookup with .get() — won't crash if key is missing
print(message.get("model", "no model specified"))

Expected Output:

user
What is retrieval-augmented generation?
{'role': 'user', 'content': 'What is retrieval-augmented generation?', 'timestamp': '2026-08-14T10:00:00Z'}
no model specified

How It Works

  • message["role"] looks up the value stored under the key "role".
  • message["timestamp"] = ... adds a brand-new key–value pair.
  • .get(key, default) looks up a key safely — if the key doesn’t exist, it returns default instead of crashing.

🤖 How Is This Used in AI?

This is arguably the single most important collection type in AI development, because JSON and Python dictionaries are nearly identical in shape.

# This is exactly what an LLM API response looks like once parsed
api_response = {
    "id": "msg_123",
    "model": "gpt-4o-mini",
    "content": [{"type": "text", "text": "Paris is the capital of France."}],
    "usage": {"input_tokens": 24, "output_tokens": 9}
}

answer_text = api_response["content"][0]["text"]
tokens_used = api_response["usage"]["output_tokens"]
print(answer_text)
print(tokens_used)

Expected Output:

Paris is the capital of France.
9

Dictionaries appear everywhere:

  • API requests/responses (JSON ↔ dict is nearly 1-to-1)
  • document metadata (source, page, author, score)
  • model configuration ({"temperature": 0.7, "max_tokens": 500})
  • tool/function arguments passed to an AI agent’s tools

⚠️ Common Beginner Mistake:

message["model"]
# KeyError: 'model'

Accessing a missing key with [...] crashes the program. Use .get(...) when the key might not exist — this is exactly the kind of defensive habit you need once you’re parsing real (sometimes inconsistent) API responses.

Key Takeaway: If you can read and build dictionaries confidently, you can read and build the JSON that every AI API speaks.

[!NOTE] Production Reality: Dictionaries vs. Pydantic Models While dictionaries are great for simple data mapping, in production AI codebases we rarely rely on raw dictionaries. If a model’s API returns a malformed dictionary or is missing a key, your code will crash with a KeyError at runtime.

To solve this, production frameworks (like FastAPI and LangChain) use Pydantic Models (derived from BaseModel) which automatically parse and validate raw dictionaries, raising clean validation errors if fields are missing or have the wrong type. (We will cover this in detail in Module 14, but keep in mind that raw dicts are the raw inputs that Pydantic validates!)


5. Indexing and Slicing

What Is It?

Indexing grabs a single item by position. Slicing grabs a range of items. Here is a visual map showing positive and negative index positions for slicing:

Value“The”“capital”“of”“France”“is”“Paris”
Positive Index012345
Negative Index-6-5-4-3-2-1
tokens = ["The", "capital", "of", "France", "is", "Paris"]

print(tokens[0])       # first item
print(tokens[-1])      # last item
print(tokens[1:4])     # items at positions 1, 2, 3 (4 is excluded)
print(tokens[:3])      # first 3 items
print(tokens[3:])      # everything from position 3 onward

Expected Output:

The
Paris
['capital', 'of', 'France']
['The', 'capital', 'of']
['France', 'is', 'Paris']

🧠 Intuition: Slicing syntax [start:end] means “start here, stop before end” — end is never included.

🤖 How Is This Used in AI?

  • Splitting text into a fixed-size context window: tokens[:512]
  • Grabbing the most recent N messages of a conversation for context: conversation[-5:]
  • Chunking a long document into overlapping pieces for embedding — a core RAG preprocessing step.

6. Nested Collections

What Is It?

Collections can contain other collections — a list of dictionaries, a dictionary containing a list, etc. Real AI data is almost always nested.

Example

# A realistic shape: a list of retrieved documents, each with metadata
search_results = [
    {"text": "Paris is the capital of France.", "score": 0.91, "source": "wiki"},
    {"text": "The Eiffel Tower is in Paris.", "score": 0.85, "source": "wiki"},
    {"text": "France uses the Euro currency.", "score": 0.62, "source": "wiki"},
]

# Get the text of the highest-scoring result (already sorted by score here)
top_result = search_results[0]
print(top_result["text"])

# Loop through and print only results above a relevance threshold
for result in search_results:
    if result["score"] >= 0.8:
        print(f"{result['score']} -> {result['text']}")

Expected Output:

Paris is the capital of France.
0.91 -> Paris is the capital of France.
0.85 -> The Eiffel Tower is in Paris.

We’re using a for loop and an if here slightly ahead of Module 3 — don’t worry about the exact mechanics yet, just notice the shape of the data: a list of dictionaries is the single most common data shape you’ll see in AI code.

🤖 How Is This Used in AI?

  • Vector database search results: a list of dicts, each with text, score, and metadata
  • LLM chat history: a list of dicts, each with role and content
  • Full API responses: dicts containing lists containing dicts

Key Takeaway: Once you’re comfortable reading results[0]["score"], you can read the output of nearly any AI API or vector search call.


7. Mutable vs Immutable Objects

What Is It?

  • Mutable = can be changed after creation (list, dict, set)
  • Immutable = cannot be changed after creation (str, int, float, tuple, bool)

Example

# Mutable: changing a list in place
tokens = ["a", "b", "c"]
tokens.append("d")
print(tokens)   # ['a', 'b', 'c', 'd'] — same list, modified

# Immutable: "changing" a string actually creates a new one
text = "hello"
text_upper = text.upper()
print(text)        # 'hello' — unchanged
print(text_upper)  # 'HELLO' — a brand-new string

Expected Output:

['a', 'b', 'c', 'd']
hello
HELLO

⚠️ Common Beginner Mistake — the shared-reference trap:

history_a = ["hello"]
history_b = history_a          # NOT a copy — both names point to the SAME list
history_b.append("world")
print(history_a)               # ['hello', 'world']  <- surprise!

Visually, both variable names point to the exact same list object in memory. Modifying the list through either variable alters the shared object:

graph LR
    subgraph Memory References
        history_a[history_a] --> listObj[("List Object: ['hello', 'world']")]
        history_b[history_b] --> listObj
    end

If you need an independent copy, use history_a.copy() or list(history_a). This bug is a classic source of confusing behavior in agent code that passes conversation history around between functions.

🤖 How Is This Used in AI?

  • Conversation history is usually a mutable list you deliberately append to as a chat progresses.
  • Configuration you don’t want a function to accidentally alter is often passed as an immutable tuple.
  • Understanding mutability is essential once you write functions that take a list of documents or messages as a parameter — you need to know whether modifying it inside the function affects the original data outside it (it does, for mutable types).

Key Takeaway: Mutable = same box, contents can change. Immutable = sealed box, “changing” it really means getting a new box.


A Shallow Copy Does Not Copy Nested Objects

.copy() creates a new outer list or dictionary, but objects nested inside it can still be shared:

original = {"settings": {"temperature": 0.2}}
copied = original.copy()
copied["settings"]["temperature"] = 0.8

print(original["settings"]["temperature"])  # 0.8

Both outer dictionaries contain a reference to the same inner settings dictionary. Use copy.deepcopy() only when you truly need independent nested objects; deep copying large AI payloads can consume considerable memory.

Choosing the Right Collection

Ask what job the collection must do:

NeedGood choiceExample in an AI application
Keep items in order and allow changeslistConversation messages
Keep a fixed recordtupleAn image size such as (1024, 768)
Remove duplicates or test membershipsetUnique document IDs
Find a value by a meaningful keydictModel settings by name

There is no collection that is best for every job. Choosing one that matches the operation makes the code easier to understand and often faster.

Module Summary

You now know the four core collection types — list (ordered, changeable), tuple (ordered, locked), set (unique, unordered), and dictionary (labeled key–value pairs) — plus how to reach inside them with indexing, slicing, and nesting, and the mutable/immutable distinction that explains a lot of “why did my data change unexpectedly” bugs.

AI Connection

Collections are the actual data structures flowing through every AI pipeline: documents in a list, metadata in a dict, a chat history as a list of dicts, an embedding shape as a tuple, a vocabulary as a set. JSON, the universal language of APIs, maps almost perfectly onto Python’s dict/list combination — which is exactly why this module matters so much for AI work specifically.

Mini Practice

  1. Build a list of 5 document strings, then use slicing to get the first 3.
  2. Build a dictionary representing one chat message (role, content), then safely look up a "timestamp" key that doesn’t exist using .get().
  3. Given two sets of keywords from two documents, find the words unique to each document (hint: look up the - operator on sets).
  4. Build a list of 4 dictionaries, each with text and score keys, then write a loop that prints only the ones with score > 0.7.
  5. Explain, in one or two sentences, why passing a list into a function and modifying it there can affect the original list outside the function — and how you’d avoid that if you didn’t want it to happen.

Next: Module 3 — Control Flow (if/elif/else, loops) — how AI code makes decisions and processes data in batches.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed