AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Python Dictionaries and Sets: Organizing Data Smartly

📚 Programming & Coding⏱️ 23 min read🎓 Grade 10
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 23 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

The Problem: Searching a List Takes Forever

Suppose your school keeps two separate lists: one of roll numbers, one of the marks each student scored in a test.

roll_numbers = [101, 102, 103, 104, 105]
scores       = [78,  92,  85,  67,  99]

Now the class teacher asks: "What did roll number 104 score?" To answer that with only lists, you have no shortcut. You must start at position 0, check if roll_numbers[0] equals 104, and if not, move to position 1, then 2, then 3 — only at position 3 do you find a match, and then you look up scores[3] to get 67. Here is that search written out:

def find_score(roll):
    for i in range(len(roll_numbers)):
        if roll_numbers[i] == roll:
            return scores[i]
    return None

print(find_score(104))

Trace it: i=0, roll_numbers[0] is 101, not 104, keep going. i=1, 102, no. i=2, 103, no. i=3, 104 — match! Return scores[3], which is 67. Output: 67.

For 5 students this feels instant, but the checking work grows in direct proportion to how many students there are. If the roll number you want happens to be the very last one in a list of 3,000 students, the computer checks 2,999 wrong answers before finding the right one. Computer scientists call this linear search — the time it takes grows linearly (in a straight line) with the size of the list, written as O(n). Every time the class size doubles, the worst-case search time roughly doubles too.

The deeper problem is not just speed — it is that we are forcing two ideas that clearly go together (a roll number and its score) to live in two unrelated lists, hoping their positions stay lined up. If anyone sorts one list, or inserts a student in the middle of only one of the two lists, the pairing silently breaks. What we actually want is a single structure where each roll number is directly attached to its score. That structure is Python's dictionary.

Meet the Dictionary: Data Stored as Key-Value Pairs

A dictionary stores information as pairs: a key you already know, and a value you want to retrieve using that key — exactly like a real paper dictionary, where the "key" is the word you look up and the "value" is its meaning. In Python, you write a dictionary using curly braces, with each key and its value joined by a colon:

marks = {101: 78, 102: 92, 103: 85, 104: 67, 105: 99}

To retrieve a value, you do not search — you simply ask for it by key, using square brackets:

print(marks[104])

Output: 67. No loop, no scanning position by position. You state the key you have, and Python hands back the value instantly. That one line replaces the entire find_score function from before. Later in this chapter you will see exactly why this lookup does not need to check every entry — it is not magic, it relies on a technique called hashing.

A key detail: unlike a list, a dictionary is not indexed by position (0, 1, 2, ...). It is indexed by whatever keys you choose — numbers, text, even a mix. Keys must be unique: if you create two entries with the same key, the second one silently overwrites the first, because a real-world lookup word cannot have two conflicting meanings stored under one identical entry.

Reading, Adding, Updating, and Removing Entries

Dictionaries commonly hold a full record, not just one number per key. Consider a single student's profile:

student = {"name": "Ananya", "roll": 12, "marks": 88}
print(student["name"])

Output: Ananya. Now let's change this dictionary step by step and track exactly what it contains after each line.

student["grade"] = "8B"          # adds a brand-new key
print(student)

Since "grade" did not exist before, Python adds it as a new entry at the end. Output: {'name': 'Ananya', 'roll': 12, 'marks': 88, 'grade': '8B'}.

student["marks"] = 91            # key already exists -> value updated
print(student)

Because "marks" already exists, this line does not add a new entry — it overwrites the old value 88 with 91, in the same position it already had. Output: {'name': 'Ananya', 'roll': 12, 'marks': 91, 'grade': '8B'}.

removed_value = student.pop("grade")
print(removed_value)
print(student)

pop() deletes a key and also hands you back the value it held, which is useful when you need that value for something else. Output: 8B, then {'name': 'Ananya', 'roll': 12, 'marks': 91}. If you only want to delete without needing the value back, del student["grade"] does the same removal.

Now, what happens if you ask for a key that was never there?

print(student["attendance"])

Output: KeyError: 'attendance' — the program crashes right there, because Python cannot silently guess a value for a key it has never seen. This is a genuinely useful crash: it tells you immediately that your assumption about the data was wrong, rather than letting a bug hide. When you are not sure a key exists and want a safe fallback instead of a crash, use .get():

print(student.get("attendance", "Not recorded"))

Output: Not recorded. .get(key, default) returns the value if the key exists, and the default you supplied if it does not — no crash either way.

Why Is Dictionary Lookup So Fast? A Peek Inside

Go back to marks = {101: 78, 102: 92, 103: 85, 104: 67, 105: 99}. When you write marks[104], Python does not compare 104 against 101, then 102, then 103 the way our earlier list search did. Instead, it runs the key through a mathematical function called a hash function, which converts the key into a number telling Python exactly which storage slot to check — directly, in one step, regardless of how many other entries exist.

A simplified (but genuinely accurate, for small whole numbers in Python) version of this idea: imagine an internal storage array with 8 slots, numbered 0 to 7. Python computes hash(key) % 8 to decide the slot. For plain positive integers, Python's hash of a number is simply the number itself, so the slot is just key % 8:

# 101 % 8 = 5   -> slot 5 stores 78
# 102 % 8 = 6   -> slot 6 stores 92
# 103 % 8 = 7   -> slot 7 stores 85
# 104 % 8 = 0   -> slot 0 stores 67
# 105 % 8 = 1   -> slot 1 stores 99

When you later ask for marks[104], Python computes 104 % 8 = 0 and jumps straight to slot 0 — one calculation, one lookup, done. It never had to look at slots 1, 5, 6, or 7 at all. This is why dictionary lookup is described as O(1), meaning "roughly constant time" — it does not matter whether the dictionary holds 5 keys or 5 million; computing the slot from the key still takes the same one step. (Real dictionaries use far more sophisticated hash functions and resize their internal storage as they grow, and two different keys can occasionally land on the same slot, a "collision," which Python resolves automatically behind the scenes — but the core idea, jump-to-slot instead of scan-everything, is exactly this.)

How a Dictionary Finds a Value Instantly key=101 101 % 8 = 5 slot 5 value = 78 key=102 102 % 8 = 6 slot 6 value = 92 key=103 103 % 8 = 7 slot 7 value = 85 key=104 104 % 8 = 0 slot 0 value = 67 key=105 105 % 8 = 1 slot 1 value = 99 Internal storage (simplified) — 8 slots, indices 0 to 7: 0 67 1 99 2 empty 3 empty 4 empty 5 78 6 92 7 85 marks[104] recomputes 104 % 8 = 0 and reads slot 0 directly — no scanning through the other four keys.

Looping Through a Dictionary

Sometimes you need every entry, not just one. Dictionaries give you three views for this: .keys() for just the keys, .values() for just the values, and .items() for key-value pairs together.

marks = {101: 78, 102: 92, 103: 85, 104: 67, 105: 99}
print(list(marks.keys()))
print(list(marks.values()))
for roll, score in marks.items():
    print(f"Roll {roll} scored {score}")

Output:

[101, 102, 103, 104, 105]
[78, 92, 85, 67, 99]
Roll 101 scored 78
Roll 102 scored 92
Roll 103 scored 85
Roll 104 scored 67
Roll 105 scored 99

Notice the order matches exactly how the dictionary was written — 101 before 102 before 103, and so on — even though internally the values are stored at slots 5, 6, 7, 0, 1 (out of sequence, as the diagram above shows). Since Python 3.7, dictionaries always remember and report entries in the order they were inserted, separately from how they are physically stored for fast lookup. Keep those two ideas apart: insertion order (what you see) and hash-slot order (how lookup works internally) are not the same thing.

A Real Algorithm: Counting with a Dictionary

Dictionaries are not just lookup tables you fill in by hand — they are a tool for building algorithms. A classic task: count how many times each letter appears in a word. Try the Malayalam palindrome "MALAYALAM":

word = "MALAYALAM"
freq = {}
for letter in word:
    if letter in freq:
        freq[letter] += 1
    else:
        freq[letter] = 1
print(freq)

Trace it letter by letter. freq starts empty. Letter M: not in freq, so freq["M"] = 1. Letter A: not in freq, so freq["A"] = 1. Letter L: not in freq, so freq["L"] = 1. Letter A again: already in freq, so freq["A"] becomes 2. Letter Y: new, freq["Y"] = 1. Letter A again: freq["A"] becomes 3. Letter L again: freq["L"] becomes 2. Letter A again: freq["A"] becomes 4. Letter M again: freq["M"] becomes 2. Final output: {'M': 2, 'A': 4, 'L': 2, 'Y': 1} — and 2 + 4 + 2 + 1 = 9, matching the 9 letters in "MALAYALAM", confirming nothing was miscounted. This "check in, then add-or-update" pattern is one of the most useful patterns in programming — it is how spell-checkers count word frequency, how analytics tools count clicks per page, and how you would count votes per candidate in a class election, all using the same three lines of logic.

Dictionaries Inside Dictionaries: Modeling Real Records

A value inside a dictionary can itself be another dictionary. This lets you model something closer to a real class result sheet, where each roll number maps to a full record rather than a single number:

results = {
    101: {"name": "Rahul", "marks": 78},
    102: {"name": "Sneha", "marks": 92},
    103: {"name": "Imran", "marks": 85}
}
print(results[102]["name"])
results[102]["marks"] += 5
print(results[102])

Read results[102]["name"] from the inside out: first results[102] gives you the inner dictionary {"name": "Sneha", "marks": 92}, then ["name"] on that inner dictionary gives "Sneha". Output: Sneha. The next line adds 5 to the nested marks value: 92 becomes 97. Final output: {'name': 'Sneha', 'marks': 97}.

Misconception Check: "Dictionaries Are Sorted By Key"

A very common mistake is assuming that because a dictionary "remembers order," it must be sorting keys numerically or alphabetically. It is not. It preserves insertion order — the order you typed things in — nothing more.

backwards = {105: "e", 101: "a", 103: "c"}
print(backwards)

Output: {105: 'e', 101: 'a', 103: 'c'} — 105 stays first, exactly as typed, even though 101 is numerically smaller. If you actually want sorted output, you must ask for it explicitly, for example with sorted(backwards.items()). Never assume a dictionary will "sort itself."

Meet the Set: Unique Values, No Duplicates, No Position

Now consider a different problem. A housing society's gate log records every car number plate each time a car enters, so the same car appears many times in one day:

gate_log = ["DL01AB1234", "DL01AB1234", "HR26CD5678",
            "DL01AB1234", "HR26CD5678"]
distinct_cars = set(gate_log)
print(distinct_cars)
print(len(distinct_cars))

A set is a collection that automatically throws away duplicates and keeps only unique values — there is no key-value pairing here, only membership: either a value is in the set, or it is not. Converting the 5-entry list into a set collapses it down to the two distinct plates. Output: a set containing 'DL01AB1234' and 'HR26CD5678' in some order, and then 2 for the count. Note that the printed order of set elements is not something you should rely on at all — unlike dictionaries, Python sets give no promise about the order elements appear in.

You create a set with curly braces around comma-separated values, similar to a dictionary, but without any colons:

cricket_players = {"Rahul", "Sneha", "Imran", "Zara"}

Misconception check: if curly braces make sets, what does x = {} create? Test it:

x = {}
print(type(x))
y = set()
print(type(y))

Output: <class 'dict'>, then <class 'set'>. Empty curly braces are reserved for an empty dictionary, for historical reasons — dictionaries came first in Python's design. To make an empty set, you must call set() explicitly. This trips up almost everyone the first time.

Checking whether something belongs to a set is extremely fast, for the same hashing reason a dictionary lookup is fast — Python does not scan every element, it jumps to where that value would be stored:

print("Zara" in cricket_players)
print("Kabir" in cricket_players)

Output: True, then False. Compare this to checking membership in a list of 4 names — for a small list the difference is invisible, but for a school-wide list of 3,000 names, list membership checking (O(n)) has to potentially examine all 3,000 entries, while set membership checking (O(1) on average) jumps straight there regardless of size.

Set Operations: Union, Intersection, Difference

Sets support the same operations you may already know from Venn diagrams in Maths. Suppose you also track a football team:

cricket_players = {"Rahul", "Sneha", "Imran", "Zara"}
football_players = {"Sneha", "Zara", "Kabir"}

print(sorted(cricket_players | football_players))
print(sorted(cricket_players & football_players))
print(sorted(cricket_players - football_players))
print(sorted(cricket_players ^ football_players))

(sorted() is used only so the printed output has a fixed, predictable order for us to check — sets themselves still have no built-in order.) The | operator is union: everyone who plays at least one of the two sports. The & operator is intersection: only students in both sets. The - operator is difference: in the first set but not the second. The ^ operator is symmetric difference: in exactly one of the two sets, not both.

Trace each line. Union of all four cricket names and three football names, with "Sneha" and "Zara" appearing in both lists but counted only once each: ['Imran', 'Kabir', 'Rahul', 'Sneha', 'Zara'] — 5 names total, not 7, because sets never store a value twice. Intersection, students in both teams: ['Sneha', 'Zara']. Difference, cricket only: ['Imran', 'Rahul']. Symmetric difference, students who play exactly one sport: ['Imran', 'Kabir', 'Rahul'] — Sneha and Zara are excluded because they play both.

Set Operations: Two Overlapping Circles Cricket Team Football Team Rahul Imran Sneha Zara Kabir Overlap = intersection (&) · Everything inside either circle = union (|)

Misconception Check: "Anything Can Be a Dictionary Key or Set Element"

Not quite. Keys and set elements must be hashable, which in practice means immutable: numbers, strings, and tuples work fine, but lists, dictionaries, and other sets do not — because their contents (and therefore their hash) could change after being stored, which would break the whole jump-to-slot mechanism from earlier.

try:
    bad = {[1, 2]: "pair"}
except TypeError as e:
    print("Error:", e)

Output: Error: unhashable type: 'list'. If you need a list-like grouping as a key, use a tuple instead — (1, 2) is immutable and hashable, so {(1, 2): "pair"} works perfectly.

Choosing the Right Tool: List, Dictionary, or Set

  • List — use when order matters and duplicates are meaningful, and you naturally think in positions: a day's temperature readings, the order of questions in an exam, students standing in a queue.
  • Dictionary — use when you need to look something up by a meaningful label rather than a position: roll number to marks, word to meaning, product code to price. Every key is unique.
  • Set — use when you only care whether something exists at all, duplicates must be eliminated, or you need mathematical union/intersection/difference logic: unique visitors to a page, common subjects between two students, distinct car plates in a log.

All three share one strength over plain variables: they let you organize many related pieces of data under one name, and all three are built into Python with no extra setup required.

Where This Fits in Your Exams

Dictionaries and sets are formally examined in CBSE Computer Science and Informatics Practices at the senior level, where you are typically asked to trace dictionary operations line by line (exactly as we did above), write short programs using .get(), .items(), and set operators, or predict output involving KeyError and hashability. The counting pattern you learned here — check membership, then add-or-update — is also one of the most frequently reused patterns in early competitive programming, appearing anywhere you need frequency counts, deduplication, or "find common elements between two groups." Getting the mental model right now, rather than memorizing syntax later, is what makes those later questions feel obvious instead of new.

Check Your Understanding

  1. Given inventory = {"pen": 10, "pencil": 25, "eraser": 15}, what is inventory after running inventory["pen"] -= 3 followed by inventory["sharpener"] = 8?
  2. What happens when you run print(inventory["gluestick"]) on the dictionary above? Rewrite the line so it prints 0 instead of crashing.
  3. Given subjects_a = {"Maths", "Science", "English"} and subjects_b = {"Science", "Hindi", "English"}, what does subjects_a & subjects_b evaluate to? What about subjects_a - subjects_b?
  4. Why does x = {} create a dictionary rather than a set, and what is the correct way to create an empty set?
  5. True or False: printing a Python set will always show its elements in the order they were added.
  6. A gate log lists car plates with repeats: ["DL01AB1234", "DL01AB1234", "HR26CD5678", "DL01AB1234", "HR26CD5678"]. Write one expression that returns the number of distinct cars.

Answers: (1) {'pen': 7, 'pencil': 25, 'eraser': 15, 'sharpener': 8} — "pen" already existed so it updates in place at position 1, "sharpener" is new so it is added at the end. (2) It raises KeyError: 'gluestick'; fix with inventory.get("gluestick", 0). (3) Intersection is {"Science", "English"}; difference is {"Maths"}. (4) Curly braces without a colon are reserved for the empty dictionary literal; an empty set must be written as set(). (5) False — sets guarantee no particular order at all; only dictionaries (from Python 3.7 onward) preserve insertion order. (6) len(set(gate_log)), which evaluates to 2.

Summary

A dictionary stores key-value pairs and retrieves a value by its key in roughly constant time, because Python hashes the key to jump straight to its storage slot rather than scanning every entry — this is what makes it fundamentally faster than searching parallel lists. Dictionaries preserve insertion order but never sort automatically; keys must be unique and hashable, which excludes mutable types like lists. Core operations are direct access with [], safe access with .get(), adding or updating with assignment, and removing with del or .pop(), while .keys(), .values(), and .items() let you loop over everything stored. A set stores only unique, unordered values using the same hashing idea, making membership testing fast and giving you mathematical union (|), intersection (&), difference (-), and symmetric difference (^) directly as operators — useful anywhere you need to deduplicate data or compare two groups. Choosing between list, dictionary, and set is really a question about what your data needs: sequence and position, meaningful lookup by label, or uniqueness and set logic.

← Introduction to Machine Learning with PythonFile Handling in Python: Reading and Writing Data →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn