Free Study Material
Lists and tuples locate a value by its numeric position. A dictionary locates a value by a name you choose yourself — a far more natural fit for data like a student's marks in different subjects.
A dictionary stores data as key-value pairs, written inside curly braces {}. Each key maps to exactly one value, and every key in a dictionary must be unique. A dictionary is mutable — pairs can be added, changed, or removed after creation.
student = {"name": "Aisha", "marks": 91}
Keys must be an immutable type — commonly a string or a number — since Python needs a key's value to never change in order to reliably locate it again. Values can be absolutely anything, including another list or dictionary.
marks = {"Physics": 85, "Chemistry": 78, "Maths": 92}
print(marks)
print(type(marks))
print(marks["Physics"])
marks maps each subject name (the key) to a score (the value). Printing the whole dictionary shows all three pairs together. type(marks) prints <class 'dict'>. marks["Physics"] reaches the value paired with the key "Physics", giving 85 — by key name, not by numeric position.
Note:{}on its own creates an empty dictionary, not an empty set — even though sets also use curly braces. This is a common point of confusion, since{}is genuinely ambiguous-looking, but Python always treats bare{}as a dictionary.
A dictionary is the right structure specifically when data is naturally described by a label rather than a position — marks["Physics"] is more direct and more readable than needing to remember that Physics happens to be stored at index 0 of some separate list, which is exactly the kind of lookup a list or tuple would force you into instead.
A dictionary stores key-value pairs in curly braces {}, with every key unique and immutable, and values of any type. A value is reached by its key (my_dict["key"]), not by numeric position, and the dictionary itself is mutable after creation.
A Dictionary is an unordered collection of items where each item consists of a Key and a Value. It is enclosed in Curly Braces { }.
# 1. Empty Dictionary
empty = {}
# 2. Student Record (Strings as Keys)
student = {
"Name": "Rahul",
"Age": 16,
"Stream": "CS"
}
print("Student Data:", student)
# 3. Number Mapping (Integers as Keys)
rankings = {
1: "Gold",
2: "Silver",
3: "Bronze"
}
print("Rank 1 Prize:", rankings[1])
# Rule 1: Duplicate Keys overwrite previous ones
data = {
"Score": 100,
"Score": 95 # This overwrites 100!
}
print("Duplicate Test:", data)
# Output: {'Score': 95}
# Rule 2: Mutable Keys are Forbidden
# Uncommenting the next line causes an Error
# invalid = { ["A", "B"] : "List Key" }
# TypeError: unhashable type: 'list'
This topic covers reading a value safely, adding a brand-new pair, and changing an existing one — the everyday operations a dictionary is used for.
key. Raises KeyError if key doesn't exist.key, or None (instead of an error) if it doesn't exist.key doesn't already exist, or overwrites the existing value if it does — the same syntax handles both.marks = {"Physics": 85, "Chemistry": 78}
print(marks["Physics"])
print(marks.get("Maths"))
marks["Maths"] = 92
print(marks)
marks["Physics"] = 90
print(marks)
marks["Physics"] gives 85. marks.get("Maths") returns None, since "Maths" isn't a key yet — no error is raised. marks["Maths"] = 92 adds a brand-new pair, since "Maths" wasn't present before: {'Physics': 85, 'Chemistry': 78, 'Maths': 92}. marks["Physics"] = 90 then overwrites the existing value for "Physics", since that key already exists: {'Physics': 90, 'Chemistry': 78, 'Maths': 92}.
Warning:marks["Maths"](using[ ]instead of.get()) on a dictionary that doesn't yet have that key raisesKeyError: 'Maths'and stops the program — use.get()whenever a key's absence is a normal possibility you need to check for, not an error.
Whether my_dict[key] = value adds or overwrites depends entirely on one thing — whether key was already present — and that single rule is exactly what makes updating a dictionary and adding to one use the identical line of code, unlike a list, which needs separate operations (append() vs. index assignment) for the two cases.
my_dict[key] reads a value and raises KeyError if the key is missing; my_dict.get(key) reads a value and returns None instead if it's missing. my_dict[key] = value both adds a new pair and overwrites an existing one, depending only on whether that key was already present.
⚠️ Crashes (KeyError) if key not found.
✅ Returns 'None' if key not found.
user = {
"username": "coder123",
"level": 5
}
# 1. Successful Access
print("User:", user["username"]) # Output: coder123
# 2. The Danger of Brackets
# print(user["score"]) <-- ERROR! KeyError: 'score'
# 3. The Safety of .get()
print("Score:", user.get("score")) # Output: None
print("Level:", user.get("level")) # Output: 5
prices = {"Apple": 100, "Banana": 40}
print("Original:", prices)
# Case A: Modifying (Key 'Apple' exists)
prices["Apple"] = 120
print("After Update:", prices)
# Result: Apple is now 120
# Case B: Adding (Key 'Mango' does not exist)
prices["Mango"] = 80
print("After Add:", prices)
# Result: Mango is added to the end
# Deleting a key
del prices["Banana"]
print("After Delete:", prices)
# Result: Banana is gone. Warning: Crashes if key isn't there!
You've already used get() to look up a value safely. This topic covers two more methods that go a step further — supplying a fallback value, and updating several pairs at once.
get(key), but returns default instead of None when key is missing, instead of just leaving you with nothing to work with.key if it exists; if it doesn't, it adds key with default as its value, and returns that.marks = {"Physics": 85, "Chemistry": 78}
print(marks.get("Maths", 0))
print(marks)
marks.update({"Maths": 92, "Physics": 88})
print(marks)
result = marks.setdefault("Biology", 0)
print(result)
print(marks)
marks.get("Maths", 0) returns 0 — the fallback — since "Maths" isn't a key; marks itself is left completely unchanged by get(). marks.update({"Maths": 92, "Physics": 88}) adds the new key "Maths" and overwrites the existing "Physics" value in a single call: {'Physics': 88, 'Chemistry': 78, 'Maths': 92}. marks.setdefault("Biology", 0) finds no "Biology" key, so it adds 'Biology': 0 to marks and returns 0 — unlike get(), setdefault() genuinely changes the dictionary when the key is missing.
Note:get(key, default)never modifies the dictionary, even when the key is missing.setdefault(key, default)does modify it, adding the key, whenever it was missing. This is the one real difference between the two.
update() is the dictionary equivalent of a list's extend() — both merge one collection's contents into another in a single call, rather than requiring a separate line for each individual pair.
get(key, default) returns a fallback value without changing the dictionary if key is missing; update(other_dict) merges another dictionary's pairs in, overwriting existing keys and adding new ones; setdefault(key, default) returns an existing value, or adds key with default and returns that, actually changing the dictionary when the key was missing.
dict1.update(dict2)
This modifies dict1 in-place. It does not return a new dictionary.
# 1. Using update()
profile = {"Name": "Amit", "Age": 16}
contact = {"Email": "amit@mail.com", "Age": 17} # Note: Age is different!
print("Before Update:", profile)
# Merge 'contact' into 'profile'
# 'Email' is added. 'Age' is overwritten (16 -> 17).
profile.update(contact)
print("After Update:", profile)
# Output: {'Name': 'Amit', 'Age': 17, 'Email': 'amit@mail.com'}
# 2. Using pop() and popitem()
data = {
"A": 10,
"B": 20,
"C": 30
}
# pop(key) - Targeted Removal
val = data.pop("B")
print("Removed Value:", val) # 20
print("Dict Now:", data) # {'A': 10, 'C': 30}
# popitem() - Last Item Removal
last_pair = data.popitem()
print("Removed Pair:", last_pair) # ('C', 30) <-- Returns a Tuple!
print("Dict Now:", data) # {'A': 10}
# 3. Using clear() vs del
cart = {"Apple": 5, "Banana": 12}
# Method A: clear()
cart.clear()
print("Cart is now:", cart) # Output: {} (Empty, but exists)
# Method B: del
del cart
# print(cart) <-- This would cause NameError because 'cart' no longer exists!
So far you've reached values one key at a time. This topic covers the three methods that hand back a dictionary's keys, values, or pairs all at once — and a genuinely surprising fact about what they actually return.
All three don't return a plain list — they return a special view object, which stays live: if the dictionary changes afterwards, the same view automatically reflects that change, with no need to call the method again.
marks = {"Physics": 85, "Chemistry": 78}
keys_view = marks.keys()
print(keys_view)
marks["Maths"] = 92
print(keys_view)
print(list(marks.values()))
print(list(marks.items()))
marks.keys() returns dict_keys(['Physics', 'Chemistry']), stored in keys_view. Then marks["Maths"] = 92 adds a new pair to marks — after keys_view was already created. Printing keys_view again shows dict_keys(['Physics', 'Chemistry', 'Maths']) — "Maths" now appears, even though keys_view was never reassigned. list(marks.values()) converts the values view into an ordinary list, [85, 78, 92]; list(marks.items()) gives [('Physics', 85), ('Chemistry', 78), ('Maths', 92)] — a list of key-value tuples.
Note: A view object is "live" — it's not a snapshot taken at the momentkeys()was called. Wrapping it inlist(), as shown above, does take a genuine, fixed snapshot at that instant; the view itself never stops tracking the dictionary.
This live-view behaviour is easy to miss, because in ordinary code it rarely causes a visible problem — but it's an exam-tested fact precisely because it's the one place a dictionary's built-in accessors behave noticeably differently from a list's or tuple's built-in functions, none of which ever "follow" the original collection after being called.
keys(), values(), and items() each return a live view object linked to the dictionary — one that automatically reflects any later change, unlike a plain list. Wrap any of them in list() to get a fixed, ordinary list instead.
dictionary.keys()
Returns: A dict_keys object containing all the keys.
Usage: useful when you only need to check what data exists, not the values.
# 1. Using keys()
student = {"Name": "Rohan", "Age": 17, "Class": 11}
all_keys = student.keys()
print(all_keys)
# Output: dict_keys(['Name', 'Age', 'Class'])
# Iterating over keys
for k in student.keys():
print("Label:", k)
dictionary.values()
Returns: A dict_values object containing all the values.
Note: Since values don't need to be unique, this list might contain duplicates (e.g., two students with the same score).
# 2. Using values()
scores = {"Math": 90, "Physics": 90, "English": 85}
# Notice '90' appears twice
print(scores.values())
# Output: dict_values([90, 90, 85])
dictionary.items()
Returns: A dict_items object containing tuples: (key, value).
Usage: The best way to loop through a dictionary if you need both the label and the data.
# 3. Using items()
stock = {"Pen": 10, "Pencil": 50}
print(stock.items())
# Output: dict_items([('Pen', 10), ('Pencil', 50)])
| Method | Returns Object | Content Type | Duplicates? |
|---|---|---|---|
| .keys() | dict_keys | Keys only | No (Unique) |
| .values() | dict_values | Values only | Yes (Allowed) |
| .items() | dict_items | Tuples (Key, Value) | No (Unique pairs) |
# Dynamic View Demonstration
my_dict = {"A": 1}
view = my_dict.values()
print("Original View:", list(view)) # [1]
# Now, we add an item to the dictionary
my_dict["B"] = 2
# We do NOT run .values() again, but the view updates!
print("Updated View:", list(view)) # [1, 2]
This closing topic covers looping through a dictionary's contents, and the remaining methods that remove data from one.
A plain for key in my_dict: loop visits every key, one at a time — to reach the matching value inside the loop, use my_dict[key]. Looping over my_dict.items() instead gives both the key and the value together, unpacked directly into two loop variables.
key and returns its value. Raises KeyError if key is missing.key (a statement, doesn't return anything).marks = {"Physics": 85, "Chemistry": 78, "Maths": 92}
for key in marks:
print(key, "->", marks[key])
for subject, score in marks.items():
print(subject, "scored", score)
removed = marks.pop("Chemistry")
print(removed, marks)
last = marks.popitem()
print(last, marks)
The first loop visits each key ("Physics", "Chemistry", "Maths") and looks up its value with marks[key]. The second loop unpacks marks.items() directly into subject and score, reaching both at once with no separate lookup needed. marks.pop("Chemistry") removes that pair and returns 78; marks becomes {'Physics': 85, 'Maths': 92}. marks.popitem() then removes and returns the most recently added pair — ('Maths', 92) — leaving marks as {'Physics': 85}.
Note: Looping directly over a dictionary (for key in my_dict:) visits keys only, never values — this is the dictionary's default iteration behaviour, and exactly why.items()exists for whenever both are needed together.
pop(key) on a dictionary mirrors pop(index) on a list — both remove by identifying a specific location (a key, or an index) and hand back what was removed — while popitem() has no real list equivalent, since a dictionary's "most recently added" pair isn't something a list's plain position-based pop() needs a separate method for.
for key in my_dict: traverses keys only; for key, value in my_dict.items(): traverses both together. pop(key) removes by key and returns the value; popitem() removes and returns the most recently added pair; del my_dict[key] removes without returning anything; clear() empties the dictionary; copy() returns a separate, independent copy.
Python allows you to iterate over different parts of the dictionary depending on your need:
for k in d: - Useful for lookups.for v in d.values(): - Useful for calculations (e.g., sum of prices).for k, v in d.items(): - Most robust method; gives you full access.# Traversal 1: Using Keys
capitals = {"India": "New Delhi", "USA": "Washington DC"}
print("--- Country List ---")
for country in capitals:
# 'country' holds the Key (India, USA)
print(country)
# Output:
# India
# USA
# Traversal 3: Using Items (Key, Value)
menu = {"Pizza": 200, "Burger": 100}
print("--- Menu ---")
# 'item' gets the Key, 'price' gets the Value
for item, price in menu.items():
print(item, "costs Rs.", price)
# Output:
# Pizza costs Rs. 200
# Burger costs Rs. 100
sorted(dictionary)
Returns: A sorted List of the dictionary's Keys.
To iterate in order, you loop through this sorted list and use the keys to look up values.
# Sorting
data = {"C": 30, "A": 10, "B": 20}
# 1. Get Sorted Keys
sorted_keys = sorted(data)
print("Sorted Keys:", sorted_keys) # ['A', 'B', 'C']
# 2. Loop in Sorted Order
print("--- Ordered Data ---")
for k in sorted(data):
print(k, ":", data[k])
dict.setdefault(key, default_value)
Useful for initializing counters or lists inside a dictionary without checking if key in d: every time.
# setdefault() Example
settings = {"theme": "Dark"}
# Case 1: Key exists -> Returns existing value ("Dark")
val1 = settings.setdefault("theme", "Light")
print("Theme is:", val1) # Output: Dark (No change)
# Case 2: Key missing -> Adds Key with Default Value (50)
val2 = settings.setdefault("volume", 50)
print("Volume is:", val2) # Output: 50
print("Final Dict:", settings)
# Create a blank attendance sheet
students = ["Amit", "Neha", "Rahul"]
# Set everyone to 'Present' by default
attendance = dict.fromkeys(students, "Present")
print(attendance)
# Output: {'Amit': 'Present', 'Neha': 'Present', 'Rahul': 'Present'}
This topic puts the whole chapter to work in two small, complete programs — the kind of question this chapter is tested with.
words = ["red", "blue", "red", "green", "blue", "red"]
frequency = {}
for word in words:
if word in frequency:
frequency[word] = frequency[word] + 1
else:
frequency[word] = 1
print(frequency)
frequency starts empty. For each word, the if word in frequency: check (from the Logical & Membership Operators topic) decides whether this word has been seen before: if it has, its count is increased by 1; if it hasn't, it's added with a starting count of 1. Tracing words in order: red → {'red': 1}, blue → {'red': 1, 'blue': 1}, red again → {'red': 2, 'blue': 1}, green → adds a new key, blue again → count becomes 2, red a third time → count becomes 3. The final result is {'red': 3, 'blue': 2, 'green': 1}.
marks = {"Physics": 85, "Chemistry": 78, "Maths": 92}
best_subject = None
best_score = -1
for subject, score in marks.items():
if score > best_score:
best_subject = subject
best_score = score
print("Highest scorer:", best_subject, "with", best_score)
best_score starts at -1, lower than any real mark, so the very first pair checked is guaranteed to replace it. The loop then compares every subject's score against the best one found so far, updating best_subject and best_score together whenever a higher score is found — the same running-comparison pattern used to find the highest score in a nested list earlier in this unit, adapted here to .items() pairs. The final result is Highest scorer: Maths with 92.
Tip: Startingbest_scoreat-1only works because marks are never negative in this problem. A more general version would instead startbest_subject/best_scorefrom the loop's very first pair, exactly like the nested-list topper program did.
Both programs reuse a pattern from earlier in this unit — a dictionary being built up one key at a time inside a loop (Program 1), and a running "best so far" comparison (Program 2) — showing that a dictionary slots into the same loop-based problem-solving approach as lists and tuples, just keyed by name instead of position.
A word-frequency counter builds up a dictionary one word at a time, using in to decide whether to start a new count or increase an existing one. Finding the highest-scoring subject traverses .items() while tracking the best pair seen so far — the same running-comparison technique used earlier for lists, applied here to key-value pairs.
counts = {}for char in text:char exists in counts → counts[char] = counts[char] + 1counts[char] = 1# Program: Count Character Frequency
text = "banana"
counts = {}
for char in text:
if char in counts:
# We have seen this before, add 1
counts[char] = counts[char] + 1
else:
# First time seeing this, set to 1
counts[char] = 1
print("Frequency Table:")
for key, val in counts.items():
print(f"'{key}' appears {val} times")
If we used two Lists (one for Names, one for Salaries), we would have to search the first list to find the index, then use that index on the second list.
With a Dictionary, we just ask db["Name"] and get the answer instantly. This is much faster.
# Program: Employee Salary Lookup
employees = {
"Amit": 50000,
"Neha": 60000,
"Raj": 45000
}
# 1. Display all data
print("--- Current Employees ---")
for name in employees:
print(f"- {name}")
# 2. Search Functionality
search_name = input("\nEnter name to check salary: ")
# Use .get() to handle missing names safely
salary = employees.get(search_name)
if salary is None:
print("Sorry, employee not found.")
else:
print(f"{search_name}'s Salary is: Rs. {salary}")
A working recap of this chapter, tying together creation, safe access, updating, view objects, and traversal in one continuous program.
{key: value, ...} — mutable, unique immutable keys, any-type values.my_dict[key] (raises KeyError if missing) or my_dict.get(key, default) (returns default instead).my_dict[key] = value — adds if new, overwrites if the key already exists; update(other_dict) merges many pairs at once.pop(key), popitem(), del my_dict[key], clear().keys(), values(), items() — live view objects that reflect later changes.for key in my_dict: (keys only) or for key, value in my_dict.items(): (both together).student = {"name": "Aisha", "Physics": 85, "Chemistry": 78}
print(student.get("Maths", "Not Enrolled"))
student["Maths"] = 92
print(student)
subjects = []
for key in student.keys():
if key != "name":
subjects.append(key)
print(subjects)
for subject, value in student.items():
if subject != "name":
print(subject, ":", value)
student.get("Maths", "Not Enrolled") returns the fallback, since "Maths" isn't a key yet. student["Maths"] = 92 then adds it. The next loop builds subjects by traversing .keys() and appending every key except "name" — giving ['Physics', 'Chemistry', 'Maths']. The final loop then traverses .items(), printing each subject and its score, skipping the "name" pair the same way.
Tip: key != "name" is the same "not equal to" relational operator from the very first operators topic in this unit — dictionaries don't introduce any new comparison logic, only a new way to organise the data being compared.
A dictionary is created with { }, read safely with get() or directly with [key], updated with the same [key] = syntax used to add new pairs, inspected with the live keys()/values()/items() views, and traversed with for key in my_dict: or for key, value in my_dict.items(): — the full toolkit this chapter covered, working together on one dictionary.
A Dictionary is a collection of items stored as Key-Value Pairs enclosed in Curly Braces { }. Unlike Lists which use numbered indices (0, 1, 2...), Dictionaries use unique labels (Keys) to access data.
You can add, delete, or modify items at any time.
Items are not stored by position. (Note: Python 3.7+ preserves insertion order, but logic remains Key-based).
| KEYS (The Label) | VALUES (The Data) |
|---|---|
|
|
| Category | Method / Syntax | Action |
|---|---|---|
| Access | d["Key"] |
Get value. Crashes if missing. |
d.get("Key") |
Get value. Returns None if missing (Safe). | |
| Modify | d["K"] = V |
Adds new pair OR Updates existing pair. |
d.update(d2) |
Merges d2 into d. Overwrites duplicates. | |
d.setdefault(k, v) |
Returns value if exists; else adds k with v. | |
| Delete | d.pop("K") |
Removes key "K" and returns value. |
d.popitem() |
Removes LAST inserted item. Returns Tuple. | |
del d["K"] |
Deletes key "K" (No return). | |
d.clear() |
Empties the dictionary (Result: {}). |
|
| Views | d.keys() |
Returns list of Keys. |
d.values() |
Returns list of Values. | |
d.items() |
Returns list of Tuples (Key, Value). |
data = {"Name": "Ali", "Age": 17}
for k in data:
print(k) # Prints 'Name', 'Age'
data = {"Name": "Ali", "Age": 17}
for v in data.values():
print(v) # Prints 'Ali', 17
data = {"Name": "Ali", "Age": 17}
for k, v in data.items():
print(k, "-->", v) # Prints 'Name --> Ali', etc.
| Feature | List [ ] | Dictionary { } |
|---|---|---|
| Access |
By Index (Integer)L[0]
|
By Key (Label)D["Name"]
|
| Order |
Ordered Position 0, 1, 2 is strictly maintained. |
Unordered* Keys are hashed. (Insertion order preserved in Python 3.7+). |
| Lookup Speed |
Slower (Linear Search) Must scan the list to find an item. |
Very Fast (Hash Map) Instant access regardless of size. |
| Best For... |
Sequences of same-type items. (e.g., Daily Temperatures, To-Do List) |
Labeled data records. (e.g., Student Profile, Product Details) |