All 57 lessons

Lesson 14 of 57 · Data Structures

Python Dictionaries: Key-Value Data

By Skillancy Editorial Team

Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).

A list finds things by position: "give me item number 3". A dictionary finds things by name: "give me the price of Paneer 200g". Whenever you catch yourself keeping two parallel lists in sync, a dictionary is the better tool. It stores key-value pairs, and looking up a key is almost instant even with millions of entries.

Dictionaries are the backbone of real-world Python. Every JSON document from an API is a dictionary. A row of a CSV read with csv.DictReader is a dictionary. Counting categories, grouping orders by store, mapping a city variant to its standard spelling, caching results: all of these are dictionary jobs. Mastering them is one of the best investments in this module.

What is a dictionary?

product = {
    "product_id": "P012",
    "name": "Paneer 200g",
    "price": 95,
    "in_stock": True,
}

Curly braces hold pairs of key: value. Keys must be unique and hashable (strings, numbers and tuples qualify; lists do not). Values can be anything, including lists and other dictionaries. Since Python 3.7, a dictionary remembers the order in which items were inserted, so looping over it is predictable.

Worked examples

Example 1: Create and read

Question: Represent one FreshBasket product and read its fields.

product = {"product_id": "P012", "name": "Paneer 200g", "price": 95, "in_stock": True}

print(product["name"])
print(product["price"] * 3)
print(len(product))
print(list(product.keys()))

Output

Paneer 200g
285
4
['product_id', 'name', 'price', 'in_stock']

How it works: product["name"] looks up the key and returns its value. len counts pairs, and keys() shows the keys (we wrapped it in list to display them). Reading a key that does not exist raises a KeyError.

Reading the result: The product is Paneer 200g, three of them cost ₹285, and the dictionary has 4 fields. Notice how product["price"] is self-explanatory, whereas product[2] in a list would not be.

Example 2: Safe lookups with get and in

Question: What if the key might not exist?

product = {"product_id": "P012", "name": "Paneer 200g", "price": 95}

print(product.get("price"))
print(product.get("discount"))
print(product.get("discount", 0))
print("discount" in product, "price" in product)

try:
    product["discount"]
except KeyError as e:
    print("KeyError:", e)

Output

95
None
0
False True
KeyError: 'discount'

How it works: get(key) returns None for a missing key instead of raising an error, and get(key, default) returns your fallback. The in operator tests keys (not values). The try/except shows what an unprotected lookup does.

Reading the result: The discount lookups returned None and then the default 0. Use get whenever missing data is possible, which is most of the time with real files and APIs.

Example 3: Adding, updating and deleting

Question: Apply a discount, mark the product out of stock and remove a field.

product = {"product_id": "P012", "name": "Paneer 200g", "price": 95, "in_stock": True}

product["discount"] = 5
product["price"] = 99
product.update({"in_stock": False, "aisle": "Dairy"})

removed = product.pop("discount")
del product["aisle"]

print(product)
print(removed)

Output

{'product_id': 'P012', 'name': 'Paneer 200g', 'price': 99, 'in_stock': False}
5

How it works: Assigning to a new key adds it; assigning to an existing key replaces its value. update applies several changes at once. pop(key) removes the pair and returns its value, while del removes without returning.

Reading the result: The price is now 99 and the product is out of stock (in_stock is False). The popped discount was 5. Unlike the get method, pop raises KeyError for a missing key unless you give a default: pop("x", None).

Example 4: Looping over a dictionary

Question: Print the stock of each item, and the total units.

stock = {"Milk": 120, "Curd": 80, "Paneer": 35, "Eggs": 210}

for item in stock:
    print(item, end=" ")
print()

for item, units in stock.items():
    print(f"{item:<8}{units:>5}")

print(sum(stock.values()))
print(max(stock, key=stock.get))

Output

Milk Curd Paneer Eggs 
Milk      120
Curd       80
Paneer     35
Eggs      210
445
Eggs

How it works: Looping over a dictionary directly gives the keys. items() gives (key, value) pairs which we unpack. values() is useful with sum, max or min. max(stock, key=stock.get) finds the key with the biggest value, because key=stock.get tells max to compare by value.

Reading the result: The total is 445 units, and the best-stocked item is Eggs. The items() loop is the one you will use most.

Example 5: Counting with a dictionary

Question: Count how many orders used each payment method.

payments = ["UPI", "Card", "UPI", "COD", "UPI", "Wallet", "Card", "UPI", "COD", "UPI"]

counts = {}
for method in payments:
    counts[method] = counts.get(method, 0) + 1

print(counts)
print(counts["UPI"] / len(payments))

Output

{'UPI': 5, 'Card': 2, 'COD': 2, 'Wallet': 1}
0.5

How it works: For each method we read the current count (default 0) and add one. This get(key, 0) + 1 idiom is the standard counting pattern in plain Python.

Reading the result: UPI was used 5 times out of 10, a share of 50%. The standard library offers an even shorter way, shown next.

Example 6: Counter and defaultdict

Question: Use the standard library helpers for counting and grouping.

from collections import Counter, defaultdict

c = Counter(payments)
print(c)
print(c.most_common(2))

by_city = defaultdict(list)
for city, order_id in [("Pune", "O1"), ("Delhi", "O2"), ("Pune", "O3")]:
    by_city[city].append(order_id)

print(dict(by_city))

Output

Counter({'UPI': 5, 'Card': 2, 'COD': 2, 'Wallet': 1})
[('UPI', 5), ('Card', 2)]
{'Pune': ['O1', 'O3'], 'Delhi': ['O2']}

How it works: Counter counts items in one call and offers most_common(n). A defaultdict(list) creates an empty list automatically the first time a key is used, so you can append without checking whether the key exists. Both are still dictionaries underneath.

Reading the result: The two most common methods are [('UPI', 5), ('Card', 2)], and the grouping gave Pune 2 orders and Delhi 1. In pandas, value_counts() and groupby are the equivalents.

Example 7: Lookup tables for cleaning

Question: Standardise inconsistent city spellings using a mapping dictionary.

city_map = {
    "bangalore": "Bengaluru", "bengaluru": "Bengaluru", "bengalore": "Bengaluru",
    "bombay": "Mumbai", "mumbai": "Mumbai",
    "new delhi": "Delhi", "delhi": "Delhi",
}

raw_cities = [" Bangalore", "BOMBAY", "new delhi ", "Chennai", "Mumbai"]

clean = [city_map.get(c.strip().lower(), c.strip().title()) for c in raw_cities]
print(clean)

Output

['Bengaluru', 'Mumbai', 'Delhi', 'Chennai', 'Mumbai']

How it works: We normalise each value (strip, lowercase), look it up in the dictionary, and fall back to the title-cased original when it is unknown. The loop is written as a list comprehension, covered in two lessons' time. A mapping dictionary is the cleanest way to fix known variants.

Reading the result: Four of the five values were standardised using the map, and Chennai passed through unchanged, giving ['Bengaluru', 'Mumbai', 'Delhi', 'Chennai', 'Mumbai']. In pandas, the same idea is df["city"].str.strip().str.lower().map(city_map).

Example 8: Nested dictionaries and lists of dictionaries

Question: Hold several stores, each with details, and compute a total.

stores = {
    "S01": {"city": "Bengaluru", "orders": 1301, "payments": {"UPI": 740, "Card": 250}},
    "S03": {"city": "Mumbai", "orders": 945, "payments": {"UPI": 520, "Card": 190}},
}

print(stores["S01"]["city"])
print(stores["S03"]["payments"]["UPI"])
stores["S03"]["orders"] += 10

total_orders = sum(info["orders"] for info in stores.values())
print(total_orders)

records = [
    {"order_id": "O1", "amount": 450, "city": "Pune"},
    {"order_id": "O2", "amount": 1200, "city": "Delhi"},
]
print(sum(r["amount"] for r in records))

Output

Bengaluru
520
2256
1650

How it works: Square brackets can be chained to dive into nested dictionaries. A list of dictionaries (the records variable) is the standard shape for tabular data: each dictionary is a row and each key is a column name. This is also exactly what a JSON API response looks like.

Reading the result: After adding 10 orders to Mumbai, the two stores total 2256 orders, and the two records sum to ₹1650. Pandas can convert a list of dictionaries directly into a DataFrame with pd.DataFrame(records).

Example 9: Building, merging and inverting

Question: Build a dictionary from two lists, merge two dictionaries, and swap keys with values.

codes = ["P001", "P002", "P003"]
names = ["Banana", "Tomato", "Onion"]

catalog = dict(zip(codes, names))
print(catalog)

extra = {"P004": "Potato", "P001": "Banana Robusta"}
merged = catalog | extra
print(merged)

inverted = {name: code for code, name in merged.items()}
print(inverted["Potato"])

Output

{'P001': 'Banana', 'P002': 'Tomato', 'P003': 'Onion'}
{'P001': 'Banana Robusta', 'P002': 'Tomato', 'P003': 'Onion', 'P004': 'Potato'}
P004

How it works: zip pairs the two lists and dict turns the pairs into entries. The | operator (Python 3.9+) merges dictionaries, and on a clash the right-hand value wins. The last line is a dictionary comprehension that flips keys and values.

Reading the result: The merged dictionary has 4 products, and P001 was overwritten with Banana Robusta by the extra dictionary. Inversion works only when values are unique and hashable.

Example 10: Sorting a dictionary by value

Question: Rank the stores by orders, highest first.

orders_by_store = {"S01": 1301, "S03": 955, "S05": 1291, "S08": 611}

ranked = sorted(orders_by_store.items(), key=lambda pair: pair[1], reverse=True)
print(ranked)

top_two = dict(ranked[:2])
print(top_two)

Output

[('S01', 1301), ('S05', 1291), ('S03', 955), ('S08', 611)]
{'S01': 1301, 'S05': 1291}

How it works: A dictionary itself is not sorted, but sorted(d.items(), key=...) returns a list of pairs ordered however you like. The lambda pair: pair[1] picks the value as the sort key, and reverse=True puts the biggest first. Wrapping a slice in dict() rebuilds a smaller dictionary.

Reading the result: The top store is S01 with 1301 orders, followed by S05. In pandas this is sort_values(ascending=False).

Common mistakes

1. Reading a missing key directly. Use get or check with in.

prices = {"Milk": 68}
print(prices["Curd"])

Output

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    print(prices["Curd"])
          ~~~~~~^^^^^^^^
KeyError: 'Curd'

2. Using a list as a key. Keys must be hashable; use a tuple instead.

3. Modifying a dictionary while looping over it. Loop over list(d.items()) or build a new dictionary.

4. Assuming in checks values. "UPI" in counts tests keys. For values use "UPI" in counts.values().

5. Duplicate keys in a literal. {"a": 1, "a": 2} silently keeps the last one.

print({"a": 1, "a": 2})

Output

{'a': 2}

Practice questions

Practice 1 (Easy): Build a price list

Create a dictionary prices for Milk (68), Curd (45) and Eggs (96). Print the price of Curd, then add Butter at 62 and print how many items there are.

Show solution
prices = {"Milk": 68, "Curd": 45, "Eggs": 96}
print(prices["Curd"])
prices["Butter"] = 62
print(len(prices))

Output

45
4

Reading uses the key; adding a new key is a simple assignment. The dictionary now has 4 items.

Practice 2 (Medium): Revenue by city

From the records below, compute the total amount for each city and print the cities from highest to lowest.

[("Pune", 450), ("Delhi", 1200), ("Pune", 300), ("Mumbai", 800), ("Delhi", 150)]

Show solution
sales = [("Pune", 450), ("Delhi", 1200), ("Pune", 300), ("Mumbai", 800), ("Delhi", 150)]

totals = {}
for city, amount in sales:
    totals[city] = totals.get(city, 0) + amount

for city, amount in sorted(totals.items(), key=lambda p: p[1], reverse=True):
    print(city, amount)

Output

Delhi 1350
Mumbai 800
Pune 750

The grouping pattern accumulates a sum under each key, and the sorted items() list gives the ranking. Delhi leads with ₹1350.

Practice 3 (Hard): Invert and group

Given grades = {"Aarav": "A", "Diya": "B", "Rohan": "A", "Isha": "C", "Kavya": "B"}, build a dictionary mapping each grade to the sorted list of students who earned it.

Show solution
grades = {"Aarav": "A", "Diya": "B", "Rohan": "A", "Isha": "C", "Kavya": "B"}

by_grade = {}
for student, grade in grades.items():
    by_grade.setdefault(grade, []).append(student)

for g in by_grade:
    by_grade[g].sort()

print(by_grade)

Output

{'A': ['Aarav', 'Rohan'], 'B': ['Diya', 'Kavya'], 'C': ['Isha']}

setdefault(key, []) returns the existing list or creates an empty one, so we can append in a single line. Sorting each list afterwards gives the alphabetical order. Grade A has 2 students.

Key takeaways

  • A dictionary stores unique, hashable keys with values; lookups by key are fast.
  • Use get(key, default) and in to avoid KeyError.
  • items() is the loop you will use most; keys() and values() give the parts.
  • Counting is d[k] = d.get(k, 0) + 1 (or Counter), and grouping is defaultdict(list) or setdefault.
  • A list of dictionaries mirrors a table, and nested dictionaries mirror JSON.
  • Merge with |, build with dict(zip(...)), and sort by value with sorted(d.items(), key=...).

Frequently asked questions

Are dictionaries ordered?

Yes, from Python 3.7 onwards they remember insertion order. They are not sorted by key or value, though; to sort, use sorted() on the items.

What is the difference between a dictionary and a JSON object?

They look almost identical. JSON is a text format; the json module converts between JSON text and Python dictionaries, lists, strings, numbers, booleans and None (written null in JSON).

Why is dictionary lookup so fast?

Python computes a number (the hash) from the key and jumps straight to the slot where that key's value is stored, instead of scanning every entry. That is why lookups take about the same time for ten items or ten million.

When should I use a dictionary instead of a pandas DataFrame?

Use a dictionary for small lookups and configuration, and for building results incrementally. Use a DataFrame when you need to filter, group and aggregate whole tables of data.

Can a dictionary value be a function?

Yes, functions are values like any other, which allows "dispatch tables" such as {"sum": sum, "max": max}name.

What does dict.fromkeys(keys, value) do?

It creates a dictionary where every key in the list gets the same starting value. It is commonly used to initialise counters, or with an empty value to remove duplicates from a list while preserving order.

Newsletter

Keep learning Python

One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Ready to practise? Try the Python interview questions