How to use a hashmap in Python
Learn how to use hashmaps in Python. This guide covers different methods, tips, real-world applications, and common error debugging.

Hashmaps, known as dictionaries or dict in Python, are essential data structures that store key-value pairs. They provide a powerful way to organize and quickly retrieve information for many tasks.
In this article, you'll learn key techniques and tips for effective dict use. You'll explore real-world applications and common debugging advice to help you master this fundamental data structure.
Basic dictionary creation and usage
student = {"name": "Alice", "age": 21, "major": "Computer Science"}
print(student)
print(f"Student name: {student['name']}")--OUTPUT--{'name': 'Alice', 'age': 21, 'major': 'Computer Science'}
Student name: Alice
The student dictionary uses literal syntax with curly braces {} to group related data about a single entity. Each piece of information, like "name" or "age", is a key that maps directly to its value. This structure is ideal when the order of items doesn't matter, but quick access to specific attributes is crucial. Learn more about creating dictionaries in Python for various use cases.
Accessing a value with student['name'] demonstrates the primary advantage of dictionaries: fast, key-based lookups. Unlike lists where you need an index, dictionaries let you retrieve data using a descriptive key. This makes your code more readable and efficient for managing structured data.
Essential dictionary operations
Key-based lookups are just the beginning; mastering dictionaries also means knowing how to modify their data and create them efficiently using more advanced techniques.
Accessing and modifying dictionary elements with [] syntax
grades = {"math": 95, "history": 88, "science": 92}
grades["english"] = 90 # Add a new key-value pair
grades["math"] = 97 # Modify an existing value
del grades["history"] # Remove a key-value pair
print(grades)--OUTPUT--{'math': 97, 'science': 92, 'english': 90}
The square bracket [] syntax is a versatile tool for managing dictionary contents. You can use it to add new entries or update existing ones with a simple assignment. This makes dictionaries dynamic and easy to change as your program runs. Understanding accessing dictionary elements is fundamental to effective dictionary manipulation.
- Adding: Assigning a value to a new key, like
grades["english"] = 90, adds the pair to the dictionary. - Updating: If the key already exists, the same syntax—
grades["math"] = 97—updates its value. - Deleting: To remove a key-value pair, use the
delkeyword, as indel grades["history"].
Using dictionary methods for data manipulation
user_info = {"username": "john_doe", "email": "john@example.com"}
print(user_info.keys())
print(user_info.values())
print(user_info.items())
user_info.update({"phone": "123-456-7890", "email": "john.doe@example.com"})
print(user_info)--OUTPUT--dict_keys(['username', 'email'])
dict_values(['john_doe', 'john@example.com'])
dict_items([('username', 'john_doe'), ('email', 'john@example.com')])
{'username': 'john_doe', 'email': 'john.doe@example.com', 'phone': '123-456-7890'}
Beyond direct assignment, dictionaries have methods for accessing and modifying data. You can get dynamic "view" objects of a dictionary's contents, which are useful for iteration.
- The
keys(),values(), anditems()methods return views of the keys, values, and key-value pairs, respectively. - The
update()method efficiently merges another dictionary into the current one. It adds new pairs like"phone"and overwrites existing values for matching keys, such as updating the"email".
Creating dictionaries with dictionary comprehensions
numbers = [1, 2, 3, 4, 5]
squared = {num: num**2 for num in numbers}
print(squared)
# Filtering with dictionary comprehension
print({k: v for k, v in squared.items() if v > 10})--OUTPUT--{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{4: 16, 5: 25}
Dictionary comprehensions offer a concise, one-line syntax for creating dictionaries from iterables. The expression {num: num**2 for num in numbers} builds the squared dictionary by applying a transformation to each item in the numbers list, making your code more readable and efficient than a traditional for loop.
- This technique is not just for creation; you can also embed logic directly within the comprehension.
- By adding an
ifcondition, as inif v > 10, you can filter the source iterable and create a new dictionary containing only the key-value pairs that meet your criteria.
Advanced dictionary techniques
Building on the fundamentals, Python's collections module offers specialized dictionaries that streamline tasks like managing default values, preserving order, and counting frequencies.
Using defaultdict for automatic initialization
from collections import defaultdict
word_count = defaultdict(int)
for word in ["apple", "banana", "apple", "orange", "banana", "apple"]:
word_count[word] += 1
print(dict(word_count))--OUTPUT--{'apple': 3, 'banana': 2, 'orange': 1}
A defaultdict is a lifesaver for tasks like counting items, as it prevents a KeyError by automatically creating a default value for any key that doesn't exist yet. In this example, defaultdict(int) tells the dictionary to use 0 as the default for new keys.
- This lets you simplify the counting logic. You can directly use
word_count[word] += 1without first checking if the word is already in the dictionary. - It's perfect for grouping or accumulating data without extra boilerplate code.
Working with OrderedDict for insertion order control
from collections import OrderedDict
ordered = OrderedDict()
ordered["first"] = 1
ordered["second"] = 2
ordered["third"] = 3
ordered.move_to_end("first")
print(list(ordered.items()))--OUTPUT--[('second', 2), ('third', 3), ('first', 1)]
While standard Python dictionaries now preserve insertion order, OrderedDict offers extra control for reordering elements. It's designed specifically for situations where you need to manage the sequence of items after they've been added.
- The
move_to_end()method is a key feature, letting you shift a specific key to the end of the sequence. - In the example,
ordered.move_to_end("first")moves the"first"key and its value to the back, demonstrating how you can manipulate the dictionary's order on the fly.
Using Counter for frequency analysis
from collections import Counter
fruits = ["apple", "banana", "apple", "orange", "banana", "apple"]
fruit_count = Counter(fruits)
print(fruit_count)
print(fruit_count.most_common(2)) # Top 2 most common items--OUTPUT--Counter({'apple': 3, 'banana': 2, 'orange': 1})
[('apple', 3), ('banana', 2)]
The Counter object is a specialized dictionary that's perfect for frequency analysis. When you pass it an iterable like the fruits list, it automatically tallies the occurrences of each item, creating a dictionary-like object where keys map to their counts.
- A key feature is the
most_common()method. It provides a quick way to get a list of the most frequent items, which is ideal for tasks like finding top-selling products or trending topics.
Move faster with Replit
Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly.
Instead of piecing together techniques, you can use Agent 4 to take an idea straight to a working product:
- A word frequency dashboard that analyzes text from a URL and displays the most common keywords, built using a
Counter. - A sales data aggregator that processes a list of transactions and groups them into a summary report by product category, powered by
defaultdict. - A user profile builder that merges a default template with specific user inputs to create a complete profile, demonstrating the
update()method.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
Navigating common dictionary pitfalls, like KeyError and mutation errors, is key to writing robust and predictable Python code.
A KeyError is one of the most frequent issues you'll encounter. It occurs when you try to access a key that doesn't exist, which immediately stops your program. While a defaultdict is great for initializing new keys when counting or grouping, a more general solution is the get() method. It safely returns None or a default value you specify, preventing the error altogether.
Another common trap is modifying a dictionary while iterating over it. Adding or removing keys during a for loop can raise a RuntimeError because the dictionary's size changes mid-iteration. The correct approach is to loop over a copy. You can create a temporary list from the keys, like for key in list(my_dict):, to safely make changes to the original dictionary within the loop.
Finally, be cautious when using dictionaries as default arguments in functions. A mutable default like def my_func(data={}): creates the dictionary only once. Every subsequent call to that function without an argument will reuse and modify that same dictionary, leading to unexpected shared state. The standard practice is to use None as the default and create a new dictionary inside the function—for example, if data is None: data = {}. This ensures each call operates on a fresh, independent dictionary.
Handling KeyError when accessing non-existent keys
A KeyError is a common roadblock that stops your program when you try to access a key that doesn't exist. For example, requesting user_data["phone"] when the dictionary only contains a name and email will cause a crash. The following code demonstrates this exact scenario.
user_data = {"name": "Alice", "email": "alice@example.com"}
phone = user_data["phone"] # This will raise KeyError
print(f"User's phone: {phone}")
The code attempts a direct lookup for the "phone" key, which isn't present in the user_data dictionary, causing the program to halt. See how you can prevent this crash with a more robust method.
user_data = {"name": "Alice", "email": "alice@example.com"}
phone = user_data.get("phone", "Not provided")
print(f"User's phone: {phone}")
The get() method provides a safe way to access dictionary keys. Instead of causing a KeyError, it returns a default value if the key is missing. In the example, user_data.get("phone", "Not provided") looks for the "phone" key. Since it doesn't exist, the code returns "Not provided" without stopping the program. This is especially useful when working with data where you can't guarantee every key will be present, like API responses. For comprehensive strategies on solving KeyError issues, explore additional error handling techniques.
Avoiding mutation during dictionary iteration
Modifying a dictionary while you're looping over it is a common pitfall that can cause a RuntimeError. This happens because changing the dictionary's size during iteration confuses the loop. The following code demonstrates what happens when you try this.
scores = {"math": 85, "english": 60, "history": 72, "science": 55}
for subject, score in scores.items():
if score < 70:
del scores[subject] # Error: modifying during iteration
print(scores)
The del scores[subject] operation inside the loop tries to remove a key from the dictionary you're currently iterating over. This dynamic change isn't allowed and causes the program to fail. Understanding safe techniques for looping through dictionaries helps avoid these common pitfalls. Check out the corrected approach below.
scores = {"math": 85, "english": 60, "history": 72, "science": 55}
passing_scores = {subject: score for subject, score in scores.items() if score >= 70}
print(passing_scores)
Instead of modifying the dictionary in place, the solution builds a new one. A dictionary comprehension cleanly filters the original scores dictionary, creating passing_scores with only the subjects that meet the condition. This approach is safer and more readable because it avoids the RuntimeError associated with changing a dictionary's size mid-loop. For more complex scenarios, understanding techniques for copying dictionaries becomes essential. It's the go-to method for filtering or transforming dictionaries without causing side effects.
Preventing unexpected behavior with mutable default parameters
Using a mutable default argument, like an empty dictionary in a function definition, can cause sneaky bugs. Python creates this default object only once, so every subsequent function call reuses and modifies that same dictionary, leading to unexpected shared state. The following code demonstrates this problem.
def add_user(name, age, user_db={}):
user_db[name] = age
return user_db
print(add_user("Alice", 25))
print(add_user("Bob", 30)) # Unexpected: Alice is still in the dict
The second call to add_user doesn't start fresh. It reuses the same user_db dictionary from the first call, which still contains "Alice." The following code shows how to ensure each call is independent.
def add_user(name, age, user_db=None):
if user_db is None:
user_db = {}
user_db[name] = age
return user_db
print(add_user("Alice", 25))
print(add_user("Bob", 30)) # Now Bob is alone in a new dict
The corrected add_user function solves the problem by setting the default argument to None. Inside the function, it checks if user_db is None: and creates a new empty dictionary only when needed. This simple pattern guarantees that each function call operates on its own independent dictionary, preventing data from one call from accidentally affecting another. It's a crucial practice whenever you use mutable types like dictionaries or lists as default arguments.
Real-world applications
From managing errors to building features, dictionaries are essential for creating practical applications like user setting managers and simple in-memory caches, especially when using AI coding with Python.
Managing user settings with the dict data structure
Dictionaries are a natural fit for managing user settings, as they let you store and update individual preferences like "theme" or "language" with simple key-value assignments.
user_settings = {"theme": "light", "notifications": True, "language": "en"}
print(f"Default settings: {user_settings}")
# Update settings based on user preference
user_settings["theme"] = "dark"
user_settings["language"] = "fr"
print(f"Updated settings: {user_settings}")
The user_settings dictionary is initialized with a baseline configuration, a common pattern for managing application state. You can easily modify these settings as user preferences change.
- The code uses simple key assignment, like
user_settings["theme"] = "dark", to overwrite existing values. - This demonstrates how dictionaries are mutable, allowing you to update specific pieces of information without rebuilding the entire structure. It’s an efficient way to handle configurations that need to adapt on the fly.
Building a simple cache with the dict data structure
You can also use a dictionary to create a simple cache, which boosts performance by storing the results of expensive operations so they don't have to be re-calculated.
import time
cache = {}
def expensive_operation(n):
if n not in cache:
print(f"Computing result for {n}...")
time.sleep(1) # Simulate an expensive operation
cache[n] = n * n
return cache[n]
print(expensive_operation(5)) # First call - will compute
print(expensive_operation(5)) # Second call - will use cached value
The expensive_operation function uses the cache dictionary to avoid repeating calculations. It first checks if a result for the input n already exists with the if n not in cache condition. This simple lookup is the key to the optimization.
- If the key
nisn't found, the function runs a simulated slow task and stores the result in thecache. - On the second call, the
ifcondition is false, so the function instantly returns the stored value, making it significantly faster.
Get started with Replit
Put your new dictionary skills into practice. Ask Replit Agent to build "a script that groups sales data by category using a defaultdict" or "a simple cache for API responses."
Replit Agent writes the code, tests for errors, and deploys your application. You just focus on the idea. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.



