DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
Getting Started
01
What is Python and Why Learn It in 2026
02
Installing Python on Windows, Mac, and Linux
03
Setting up VS Code / PyCharm for Python
04
Understanding the Python Interpreter and REPL
05
Writing and Running Your First Python Program
06
Python Syntax and Indentation Rules
Variables and Data Types
01
Variables and Naming Conventions in Python
02
Numbers in Python (int, float, complex)
03
Strings and String Methods in Python
04
Booleans in Python
05
Type Conversion / Casting in Python
06
Understanding the None Type in Python
Operators
01
Arithmetic Operators in Python
02
Comparison Operators in Python
03
Logical Operators in Python
04
Assignment Operators in Python
05
Bitwise Operators in Python
06
Identity (is) and Membership (in) Operators
07
Walrus Operator (:=) in Python
Input, Output & Strings
01
input() and print() Functions in Python
02
f-strings and String Formatting in Python
03
String Slicing and Indexing in Python
04
Common String Methods (split, join, strip, replace)
05
Escape Characters in Python Strings
Control Flow
01
if, elif, else Statements in Python
02
Nested Conditionals in Python
03
for Loops in Python
04
while Loops in Python
05
break, continue, and pass Statements
06
Loop with else Clause in Python
07
match-case (Structural Pattern Matching)
Data Structures
01
Python Lists: Creation, Indexing, Slicing, Methods
02
Python Tuples and Immutability
03
Python Sets and Set Operations
04
Python Dictionaries: Keys, Values, and Methods
05
List Comprehensions in Python
06
Dictionary & Set Comprehensions in Python
07
Working with Nested Lists and Dictionaries
Functions
01
Defining and Calling Functions in Python
02
Function Arguments: Positional, Keyword, Default
03
*args and **kwargs in Python
04
Return Statements in Python Functions
05
Lambda Functions in Python
06
Recursion in Python
07
Variable Scope: Local, Global, Nonlocal
08
Docstrings and Function Annotations
Functional Programming Basics
01
map(), filter(), and reduce() in Python
02
Closures in Python Explained
03
Python Decorators: Basic to Advanced
04
Generators and yield in Python
Object-Oriented Programming (OOP)
01
Classes and Objects in Python
02
The init Constructor in Python
03
Instance vs Class Variables in Python
04
Inheritance in Python (Single, Multiple, Multilevel)
05
Polymorphism in Python
06
Encapsulation in Python
07
Abstraction in Python with the abc Module
08
Magic/Dunder Methods in Python
09
Static and Class Methods in Python
10
The @property Decorator in Python
11
Python Dataclasses Explained
Error Handling
01
try, except, finally in Python
02
Handling Multiple Exceptions in Python
03
Raising Custom Exceptions in Python
04
Understanding Python's Exception Hierarchy
05
Assertions in Python
File Handling
01
Reading and Writing Text Files in Python
02
Working with the with Statement in Python
03
Working with CSV Files in Python
04
Working with JSON Files in Python
05
Working with Directories (os, pathlib) in Python
Modules and Packages
01
Importing Built-in Modules in Python
02
Creating Your Own Modules in Python
03
Understanding Packages and init.py
04
Python Standard Library Overview
05
Installing Packages with pip
Advanced Core Concepts
01
Iterators and Iterables in Python
02
Context Managers in Python (with, contextlib)
03
Multithreading Basics in Python
04
Multiprocessing Basics in Python
05
Asyncio Basics: async/await in Python
06
Memory Management and Garbage Collection in Python
07
Type Hints in Python (typing module)

Python Dictionaries: Keys, Values, and Methods

python dictionaries are arguably the single most useful data structure in the language — a collection built specifically for looking things up by a meaningful name instead of a numeric position. This article covers how they work, the essential python dictionary methods, and the comprehension syntax that lets you build them in a single line.

What is a Dictionary?

A dictionary is a collection that maps unique keys to values. Unlike a list, where you access items by numeric position, a dictionary lets you access items by a meaningful key you choose yourself.

c {"name": "Alex", "phone": "555-0142"}
Real-world framing

Think of it like a contact list on your phone: you don't scroll through numbered slots to find someone — you look up their name and get their phone number back. That's exactly the relationship a dictionary models: a key (the name) mapping to a value (the number).

Ordering

Dictionaries are ordered by insertion — as of Python 3.7, this behavior is officially guaranteed, not just an implementation detail. Items appear in the order you added them when you iterate over the dictionary. That said, dictionaries aren't ordered in the sense that lists are — you still access values by key, not by numeric position, so "ordered" here refers specifically to iteration order, not indexed access.

Creating dictionaries
# Literal syntax
person = {"name": "Alex", "age": 30}

# The dict() constructor
person = dict(name="Alex", age=30)

# Dictionary comprehension — covered in more depth in Section 4
squares = {n: n ** 2 for n in range(5)}
Why keys must be immutable, but values can be anything

As covered in the earlier tuples and sets articles, dictionary keys need to be hashable — which generally means immutable: strings, numbers, and tuples all work fine as keys. Values, on the other hand, have no such restriction — a value can be a string, a number, a list, another dictionary, anything at all:

person = {
    "name": "Alex",
    "hobbies": ["reading", "hiking"],   # a list as a value — perfectly fine
    "address": {"city": "Nagpur", "zip": "440001"}   # a nested dict as a value — also fine
}

Accessing and Modifying Data

Retrieving values: [] vs. .get()
person = {"name": "Alex", "age": 30}

print(person["name"])   # Alex

Square-bracket access works fine — right up until you request a key that doesn't exist:

print(person["email"])
# KeyError: 'email'

A KeyError is python's way of saying "this key doesn't exist," and it will crash your program if left unhandled. The safer alternative is .get(), which returns None (or a default value you specify) instead of raising an error:

print(person.get("email"))              # None — no error
print(person.get("email", "N/A"))       # N/A — a custom fallback value

.get() is generally the preferred way to access a dictionary value whenever there's any chance the key might not be present — which, in real-world code working with external data, is genuinely common.

Adding and updating entries
person = {"name": "Alex"}

# Add or update a single key directly
person["age"] = 30
print(person)   # {'name': 'Alex', 'age': 30}

person["age"] = 31   # updating an existing key works the exact same way
print(person)   # {'name': 'Alex', 'age': 31}

# Add or update multiple keys at once
person.update({"city": "Nagpur", "age": 32})
print(person)   # {'name': 'Alex', 'age': 32, 'city': 'Nagpur'}

Notice that assigning to an existing key and adding a new key use identical syntax — Python figures out which one you mean based on whether the key already exists.

Deleting entries
person = {"name": "Alex", "age": 30, "city": "Nagpur"}

# del — removes a key, doesn't return anything
del person["city"]
print(person)   # {'name': 'Alex', 'age': 30}

# pop() — removes a key AND returns its value
age = person.pop("age")
print(age, person)   # 30 {'name': 'Alex'}

.pop() also accepts a default value, mirroring .get()'s safety, letting you avoid a KeyError if the key might not exist: person.pop("email", "not found").

setdefault(): insert only if missing

.setdefault() is a genuinely useful, slightly less obvious method: it returns a key's value if it exists, but if it doesn't, it inserts the key with a default value and returns that default — all in one call.

person = {"name": "Alex"}

age = person.setdefault("age", 0)
print(age)      # 0 — key didn't exist, so it was added with the default
print(person)   # {'name': 'Alex', 'age': 0}

age = person.setdefault("age", 99)
print(age)      # 0 — key already existed, so the default was ignored

This is particularly useful for building nested structures incrementally — for example, grouping items into lists keyed by category, where you're not sure yet whether a given category's list already exists:

groups = {}

items = [("fruit", "apple"), ("vegetable", "carrot"), ("fruit", "banana")]

for category, item in items:
    groups.setdefault(category, []).append(item)

print(groups)   # {'fruit': ['apple', 'banana'], 'vegetable': ['carrot']}

Without .setdefault(), this pattern would require an extra if category not in groups: check before every append.

Core Methods for Traversing a Dictionary

keys(), values(), and items()
person = {"name": "Alex", "age": 30}

print(person.keys())     # dict_keys(['name', 'age'])
print(person.values())   # dict_values(['Alex', 30])
print(person.items())    # dict_items([('name', 'Alex'), ('age', 30)])
  • .keys() — every key in the dictionary.

  • .values() — every value, without their corresponding keys.

  • .items() — key-value pairs together, as tuples.

Looping patterns
person = {"name": "Alex", "age": 30}

# Iterating a dictionary directly gives you the keys (same as calling .keys())
for key in person:
    print(key)
# name
# age

# Iterating values directly
for value in person.values():
    print(value)
# Alex
# 30

# Unpacking key-value pairs together — the most commonly used pattern
for key, value in person.items():
    print(f"{key}: {value}")
# name: Alex
# age: 30

That last pattern — for key, value in person.items(): — is the one you'll write constantly once you're working with dictionaries in real code.

Practical example: word-frequency counting

A genuinely common real-world task, combining .get() with a loop:

text = "the quick brown fox jumps over the lazy dog the fox runs"
words = text.split()

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

print(counts)
# {'the': 3, 'quick': 1, 'brown': 1, 'fox': 2, 'jumps': 1, 'over': 1, 'lazy': 1, 'dog': 1, 'runs': 1}

counts.get(word, 0) returns the word's current count if it's already been seen, or 0 if it's brand new — either way, adding 1 and reassigning handles both cases in a single line, without a separate if word in counts: check.

Dictionary Comprehensions and Merging

Building dictionaries in one line

Dictionary comprehensions mirror the list comprehension syntax covered in an earlier article, but produce key-value pairs instead of single values:

squares = {n: n ** 2 for n in range(5)}
print(squares)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

You can add a filtering condition, just like with list comprehensions:

even_squares = {n: n ** 2 for n in range(10) if n % 2 == 0}
print(even_squares)   # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
Classic patterns

Inverting a dictionary — swapping keys and values:

original = {"a": 1, "b": 2, "c": 3}
inverted = {value: key for key, value in original.items()}
print(inverted)   # {1: 'a', 2: 'b', 3: 'c'}

(This only works cleanly if the original values are themselves unique and hashable — duplicate values would silently overwrite each other in the inverted result.)

Grouping data by a shared attribute:

people = [
    {"name": "Alex", "dept": "Engineering"},
    {"name": "Sam", "dept": "Sales"},
    {"name": "Jordan", "dept": "Engineering"},
]

by_dept = {}
for person in people:
    by_dept.setdefault(person["dept"], []).append(person["name"])

print(by_dept)   # {'Engineering': ['Alex', 'Jordan'], 'Sales': ['Sam']}
Merging dictionaries

Python has accumulated a few different ways to merge dictionaries over the years, and it's worth knowing all three:

a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}

# Modern approach — the | merge operator (Python 3.9+)
merged = a | b
print(merged)   # {'x': 1, 'y': 3, 'z': 4}

# Older approach — dictionary unpacking
merged = {**a, **b}
print(merged)   # {'x': 1, 'y': 3, 'z': 4}

# Method approach — mutates 'a' directly, rather than creating a new dict
a.update(b)
print(a)   # {'x': 1, 'y': 3, 'z': 4}

In every case, when both dictionaries share a key (like "y" here), the value from the second dictionary (or the argument passed to .update()) wins. The | operator is the current recommended approach for creating a new merged dictionary if you're on Python 3.9 or later; {**a, **b} remains a solid, widely compatible alternative for older versions; .update() is specifically for when you want to modify one of the dictionaries in place rather than creating a new one.

Why Dictionaries Are Fast (and Practical Use Cases)

Under the hood: hash tables

Like sets, dictionaries are backed by a hash table. This gives them average O(1) lookup time — checking or retrieving a value by key takes roughly the same amount of time no matter how large the dictionary grows. Compare that to searching for a matching item in a list, which is O(n) — the larger the list, the longer a search potentially takes, since Python may need to check every single item. This is precisely why dictionaries (and sets) are the right structure any time you need fast lookups by some identifying key, rather than a plain list.

Real-world use cases
  • Parsing JSON and API responses — nearly every API response you'll work with in Python arrives as nested dictionaries and lists; understanding dictionary access and nested structures directly translates to working comfortably with real-world data.

  • Configuration management — application settings are a natural fit for key-value storage.

  • Caching and memoization — storing previously computed results keyed by their input, so expensive calculations don't need to be repeated.

  • Grouping and counting data — as shown in the word-frequency and department-grouping examples above.

A note on nested dictionaries

Because values can be anything — including other dictionaries — you'll frequently encounter nested structures, especially when working with real-world data like API responses:

user = {
    "name": "Alex",
    "address": {
        "city": "Nagpur",
        "zip": "440001"
    }
}

print(user["address"]["city"])   # Nagpur

Accessing nested values just means chaining the bracket access — user["address"]["city"] reaches into the outer dictionary, then into the nested one.

Common pitfall: mutable keys

Just like sets, dictionary keys must be hashable, which effectively means immutable. Trying to use a list as a key raises an error:

data = {}
data[[1, 2]] = "invalid"
# TypeError: unhashable type: 'list'

If you need a compound key made of multiple values, use a tuple instead — tuples are hashable (assuming their own contents are), and this is one of their most common practical uses, as covered in the earlier tuples article:

data = {}
data[(1, 2)] = "valid"   # a tuple works fine as a key
print(data)   # {(1, 2): 'valid'}

PREVIOUSNEXT LESSON