Python tutorials  /  Python Dictionaries - Complete Guide with Examples
Chapter 11 · Python

Python Dictionaries - Complete Guide with Examples

A dictionary in Python is an unordered (insertion-ordered since Python 3.7+), mutable collection that stores data as key-value pairs. Each key must be unique and hashable, and it maps directly to a corresponding value, allowing extremely fast lookups, insertions, and deletions based on the key rather than a numeric position.

Think of a dictionary like a real-world phone contact list on your phone. You don't scroll through every contact by position to find someone; you look them up directly by their name (the key), and instantly get their phone number (the value). Each name in your contacts is unique, just like keys in a Python dictionary must be unique.

Consider CompileX's user profile system, where every registered user has attributes like username, email, subscription plan, and total problems solved. Instead of creating four separate lists (usernames, emails, plans, counts) and keeping track of matching positions across all of them, developers store each user as a dictionary like {'username': 'coder123', 'email': 'coder@mail.com', 'plan': 'pro', 'solved': 245}. When the profile page loads, the backend simply looks up user_data['plan'] to instantly display whether to show a 'Pro' badge, without searching through separate parallel lists or risking a mismatch between a username and the wrong email.

Dictionaries are essential because they allow programmers to model real-world relationships between pieces of data (like a name mapping to an age, or a product ID mapping to its price) in a way that is both intuitive and extremely fast to access. They eliminate the need for parallel lists and manual index tracking, provide O(1) average-time lookups by key, and are the foundation for representing structured data like JSON, configuration settings, and database records in Python.

  • Standard Dictionary (dict): The built-in, most commonly used mapping type in Python, created using curly braces {} with key-value pairs, and since Python 3.7 it maintains insertion order automatically.
  • Nested Dictionary: A dictionary where values themselves are other dictionaries, used to represent hierarchical or structured data such as a user profile containing an address dictionary within it.
  • defaultdict: A specialized dictionary from the 'collections' module that automatically assigns a default value to a key when it's accessed for the first time, eliminating the need to manually check if a key exists before using it.
  • OrderedDict: A dictionary subclass from the 'collections' module that explicitly guarantees and gives extra control over insertion order, historically important before Python 3.7 made regular dicts ordered by default, still useful for order-sensitive operations like move_to_end().
  • Counter: A specialized dictionary subclass from the 'collections' module designed specifically for counting hashable objects, automatically initializing missing keys to zero and providing convenient counting-related methods.
# Creating a dictionary my_dict = {"key1": value1, "key2": value2} my_dict = dict(key1=value1, key2=value2) # Accessing and modifying my_dict["key1"] my_dict.get("key1", default_value) my_dict["new_key"] = new_value my_dict.update({"key1": updated_value}) # Removing my_dict.pop("key1") del my_dict["key1"] # Iterating for key, value in my_dict.items(): pass
Imagine you are building an inventory management system for CompileX's merchandise store, where you need to track product names, their current stock quantity, and their price, and quickly update stock levels whenever an order is placed. Using three separate lists for names, quantities, and prices would require you to manually keep track of matching indices, and any small error could sell out-of-stock items or attach the wrong price to a product. How do you structure this data so each product's details stay tightly linked together and can be looked up instantly by name?
Creating and Accessing a Dictionary
A basic example creating a dictionary of a user's profile and accessing specific values using keys.
Python
user_profile = { "username": "coder123", "email": "coder@mail.com", "plan": "pro", "solved": 245 } print(user_profile["username"]) print(user_profile.get("plan")) print(user_profile.get("country", "Not specified"))
coder123 pro Not specified
Square bracket access retrieves the value for an existing key directly. The .get() method also retrieves a value by key, but safely returns a default value ("Not specified") instead of raising an error when the key 'country' doesn't exist in the dictionary.
Updating and Adding Key-Value Pairs
Demonstrates modifying an inventory dictionary by updating stock quantities and adding new products, solving the inventory tracking problem.
Python
inventory = {"T-Shirt": 50, "Mug": 120, "Sticker Pack": 200} inventory["T-Shirt"] -= 3 inventory["Notebook"] = 75 print(inventory)
{'T-Shirt': 47, 'Mug': 120, 'Sticker Pack': 200, 'Notebook': 75}
inventory["T-Shirt"] -= 3 directly modifies the existing value tied to that key, simulating 3 units being sold. inventory["Notebook"] = 75 adds a brand-new key-value pair to the dictionary since 'Notebook' didn't previously exist.
Iterating Over a Dictionary with items()
Shows how to loop through both keys and values simultaneously to generate a formatted inventory report.
Python
inventory = {"T-Shirt": 47, "Mug": 120, "Sticker Pack": 200} for product, quantity in inventory.items(): status = "Low Stock" if quantity < 50 else "In Stock" print(f"{product}: {quantity} units ({status})")
T-Shirt: 47 units (Low Stock) Mug: 120 units (In Stock) Sticker Pack: 200 units (In Stock)
.items() returns each key-value pair as a tuple, which is unpacked into 'product' and 'quantity' in the for loop. This allows both pieces of information to be used together in each iteration to build a conditional status report.
Using defaultdict for Automatic Counting
Demonstrates using defaultdict from the collections module to count how many times each tag appears across coding problems without manually checking for key existence.
Python
from collections import defaultdict problem_tags = ["arrays", "recursion", "arrays", "sorting", "recursion", "arrays"] tag_count = defaultdict(int) for tag in problem_tags: tag_count[tag] += 1 print(dict(tag_count))
{'arrays': 3, 'recursion': 2, 'sorting': 1}
defaultdict(int) automatically initializes any new, unseen key with a default value of 0 (since int() returns 0), so 'tag_count[tag] += 1' works safely on the very first occurrence of a tag without raising a KeyError, unlike a standard dictionary.
  • Using Square Brackets Instead of .get() for Uncertain Keys: Accessing a dictionary with 'my_dict["missing_key"]' when the key doesn't exist raises a KeyError and crashes the program. When a key's existence is uncertain, using .get("missing_key", default_value) safely returns a fallback value instead of causing a runtime error.
  • Using Mutable Objects as Dictionary Keys: Attempting to use a list as a dictionary key, such as 'my_dict[[1, 2]] = "value"', raises a TypeError because lists are mutable and therefore unhashable. Only immutable, hashable types like strings, numbers, or tuples can be used as valid dictionary keys.
  • Modifying a Dictionary's Keys While Iterating Over It: Adding or removing keys directly from a dictionary while looping through it with 'for key in my_dict:' raises a RuntimeError ('dictionary changed size during iteration'). The safe approach is to iterate over a copy of the keys, such as 'for key in list(my_dict.keys()):', when modification during iteration is necessary.
  • Assuming Dictionary Order Doesn't Matter in Older Code or Comparisons: While Python 3.7+ guarantees insertion order is preserved, developers coming from older Python versions or other languages sometimes incorrectly assume dictionaries are entirely unordered like sets, leading to confusion when relying on or ignoring predictable iteration order in modern code.
  • Overwriting Existing Keys Accidentally: Assigning a value to a key that already exists, such as 'inventory["T-Shirt"] = 50' when the intention was to add to the existing stock rather than replace it, silently overwrites the previous value without any warning, which can lead to data loss if not handled with '+=' or explicit checks.
  • Use .get() with a Default Value for Safer Access: Instead of directly indexing a dictionary and risking a KeyError, use my_dict.get(key, default_value) whenever a key might not exist, especially when processing external or user-supplied data like API responses or form inputs.
  • Use Dictionary Comprehensions for Concise Transformations: Instead of writing multi-line loops to build a new dictionary from an existing one, use dictionary comprehensions like '{k: v*2 for k, v in prices.items()}' for cleaner, more Pythonic, and often faster code.
  • Use collections.defaultdict for Counting and Grouping Tasks: When building dictionaries that count occurrences or group items into lists, use defaultdict(int) or defaultdict(list) instead of manually checking 'if key not in my_dict' before every update, resulting in cleaner and less error-prone code.
  • Use Meaningful, Descriptive Keys: Choose dictionary keys that clearly describe the data they hold, such as 'total_price' instead of 'tp' or 'x', to make the dictionary self-documenting and easier for other developers to understand without needing extra comments.
  • Use in to Check Key Existence Before Risky Operations: Before performing an operation that assumes a key exists, use 'if key in my_dict:' to explicitly check first, which is both readable and avoids unnecessary exception handling for logic that can be simply validated upfront.
What is the difference between accessing a dictionary value using square brackets versus the .get() method?
Using square brackets like my_dict['key'] raises a KeyError if the specified key does not exist in the dictionary, which can crash the program if not handled with a try-except block. The .get('key', default) method instead returns None (or a specified default value) if the key is missing, allowing the program to continue running safely without an exception.
Can you use a list as a dictionary key in Python? Why or why not?
No, you cannot use a list as a dictionary key because dictionary keys must be hashable, and lists are mutable, meaning their content (and thus their hash value) could change after being used as a key, which would break the internal hash table structure. Only immutable types like strings, numbers, tuples (containing only immutable elements), and frozensets can be used as dictionary keys.
Since Python 3.7, dictionaries maintain insertion order. Does this mean dictionaries and OrderedDict are now functionally identical?
While regular dictionaries now maintain insertion order like OrderedDict, they are not entirely identical. OrderedDict provides additional order-specific methods like move_to_end() for explicitly reordering elements, and equality comparisons between two OrderedDicts consider order significant (two OrderedDicts with the same items in different orders are not equal), whereas equality comparisons between two regular dicts ignore order entirely.
How does a Python dictionary achieve average O(1) time complexity for lookups internally?
A dictionary is implemented internally as a hash table, where each key is passed through a hash function to compute a hash value, which determines the specific memory 'bucket' or slot where the corresponding key-value pair is stored. When looking up a key, Python recomputes its hash and jumps directly to the corresponding bucket rather than searching sequentially, giving average constant-time O(1) performance, though worst-case performance can degrade to O(n) in rare cases involving significant hash collisions.
What happens if you try to modify a dictionary's size (add or remove keys) while iterating over it directly, and how do you safely work around this?
Directly adding or removing keys from a dictionary while iterating over it with a for loop raises a RuntimeError stating that the dictionary changed size during iteration, because the internal iterator becomes invalidated by the structural change. To safely modify a dictionary during what is conceptually an iteration, you should iterate over a static copy of the keys or items first, such as 'for key in list(my_dict.keys()):', which allows the original dictionary to be safely modified inside the loop.
Write a function 'invert_dictionary' that takes a dictionary and returns a new dictionary with keys and values swapped.
def invert_dictionary(d): return {value: key for key, value in d.items()} print(invert_dictionary({"a": 1, "b": 2, "c": 3})) # {1: 'a', 2: 'b', 3: 'c'}
Write a function 'merge_dictionaries' that takes two dictionaries and merges them, where if a key exists in both, the values from the second dictionary should overwrite the first.
def merge_dictionaries(dict1, dict2): merged = dict1.copy() merged.update(dict2) return merged print(merge_dictionaries({"a": 1, "b": 2}, {"b": 20, "c": 3})) # {'a': 1, 'b': 20, 'c': 3}
Write a function 'group_by_first_letter' that takes a list of words and returns a dictionary where keys are the first letters of the words, and values are lists of words starting with that letter.
from collections import defaultdict def group_by_first_letter(words): grouped = defaultdict(list) for word in words: grouped[word[0].lower()].append(word) return dict(grouped) print(group_by_first_letter(["apple", "banana", "avocado", "blueberry", "cherry"])) # {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

Python dictionaries are mutable, insertion-ordered collections of key-value pairs that provide fast, direct lookups by key rather than requiring sequential searches. They are foundational for modeling structured, real-world data relationships, from user profiles to inventory systems to API responses, and are used extensively throughout professional Python development. Understanding safe access patterns with .get(), hashability requirements for keys, and specialized variants like defaultdict is essential for writing efficient, bug-free, production-quality code.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.