Python Basics series — Part 3. This post builds on Part 1: What Every Beginner Should Know and Part 2: Variables and Data Types. Each post in the series takes one topic and covers it in depth. This one is all about lists and dictionaries.
If you write Python for more than a few days, you’ll use lists and dictionaries constantly. A list of pages on a website, the products in an online store, the rows of a client’s spreadsheet, the response from an API — almost every piece of real data you work with ends up in a list, a dictionary, or a combination of both.
Part 2 introduced both types: how to create them, read a value, and make a few changes. This post goes much further. You’ll learn how to add, remove, search, and sort items; loop over them the Pythonic way; build new lists and dictionaries in a single line with comprehensions; and work with nested data — the lists of dictionaries that real-world data almost always looks like.
As before, open the Python shell (python3 in your terminal) and try each example as you read.
Quick Recap
A list is an ordered, changeable collection of values in square brackets. You get items by their position, starting at 0:
pages = ["Home", "About", "Services", "Contact"]
print(pages[0]) # Home
A dictionary stores values under keys in curly braces. You get items by their key:
client = {"name": "Maria", "business": "Bright Bakery"}
print(client["name"]) # Maria
A simple way to choose between them: use a list when the order of items matters and you’ll go through them one by one; use a dictionary when you need to look things up by name.
Lists in Depth
Creating Lists
You can create a list with values, or start with an empty one and fill it later:
colors = ["#5B21B6", "#F59E0B", "#10B981"]
prices = [19.99, 49.00, 99.50]
tasks = [] # an empty list
print(len(colors)) # 3
print(len(tasks)) # 0
A list can hold any type of value — even a mix — but in practice, lists usually hold items of the same kind. That makes them much easier to work with.
Accessing Items and Slicing
Indexes start at 0, and negative indexes count from the end:
pages = ["Home", "About", "Services", "Portfolio", "Contact"]
print(pages[1]) # About
print(pages[-1]) # Contact
print(pages[-2]) # Portfolio
Asking for an index that doesn’t exist raises an IndexError. With five items, the last valid index is 4:
pages[5] # IndexError: list index out of range
To get several items at once, use a slice: list[start:stop]. The slice includes start but stops before stop:
print(pages[1:3]) # ['About', 'Services']
print(pages[:2]) # ['Home', 'About'] (from the start)
print(pages[2:]) # ['Services', 'Portfolio', 'Contact'] (to the end)
print(pages[-2:]) # ['Portfolio', 'Contact'] (the last two)
A slice can also take a third number, the step. A step of -1 walks backward, which is a quick way to reverse a list:
numbers = [1, 2, 3, 4, 5, 6]
print(numbers[::2]) # [1, 3, 5] (every second item)
print(numbers[::-1]) # [6, 5, 4, 3, 2, 1]
Adding Items
There are three main ways to add items, and each one does something slightly different:
pages = ["Home", "About"]
pages.append("Contact") # add one item to the end
print(pages) # ['Home', 'About', 'Contact']
pages.insert(1, "Services") # add one item at a position
print(pages) # ['Home', 'Services', 'About', 'Contact']
pages.extend(["Blog", "FAQ"]) # add every item from another list
print(pages) # ['Home', 'Services', 'About', 'Contact', 'Blog', 'FAQ']
Be careful with append() and lists: append() adds its argument as one item, so appending a list puts a list inside your list. Use extend() when you want to add the individual items:
pages = ["Home"]
pages.append(["Blog", "FAQ"])
print(pages) # ['Home', ['Blog', 'FAQ']] ← probably not what you wanted
You can also join two lists into a new list with +:
main_pages = ["Home", "About"]
extra_pages = ["Blog", "FAQ"]
all_pages = main_pages + extra_pages
print(all_pages) # ['Home', 'About', 'Blog', 'FAQ']
Removing Items
Again, there are a few options depending on what you know about the item:
pages = ["Home", "Services", "About", "Contact", "Blog", "FAQ"]
pages.remove("FAQ") # remove by value (the first match)
last = pages.pop() # remove the last item and return it
second = pages.pop(1) # remove the item at index 1 and return it
del pages[0] # remove by index, without returning it
print(last) # Blog
print(second) # Services
print(pages) # ['About', 'Contact']
pop() is handy when you want to use the item you removed — for example, taking the next task off a to-do list. To empty a list completely, use pages.clear().
remove() raises a ValueError if the value isn’t in the list, so check first with in when you’re not sure:
if "FAQ" in pages:
pages.remove("FAQ")
Searching a List
tags = ["python", "web", "seo", "python", "data"]
print("seo" in tags) # True (is it in the list?)
print("java" not in tags) # True
print(tags.index("seo")) # 2 (position of the first match)
print(tags.count("python")) # 2 (how many times it appears)
Sorting Lists
Python gives you two ways to sort, and the difference matters:
list.sort()sorts the list in place — it changes the original list and returnsNone.sorted(list)returns a new sorted list and leaves the original alone.
prices = [49, 19, 99, 5]
sorted_prices = sorted(prices)
print(sorted_prices) # [5, 19, 49, 99]
print(prices) # [49, 19, 99, 5] (unchanged)
prices.sort()
print(prices) # [5, 19, 49, 99] (changed in place)
prices.sort(reverse=True)
print(prices) # [99, 49, 19, 5]
Both accept a key — a function Python calls on each item to decide the order. For example, sort words by length, or alphabetically while ignoring capital letters:
services = ["SEO", "web design", "Hosting", "logo design"]
print(sorted(services)) # ['Hosting', 'SEO', 'logo design', 'web design']
print(sorted(services, key=str.lower)) # ['Hosting', 'logo design', 'SEO', 'web design']
print(sorted(services, key=len)) # ['SEO', 'Hosting', 'web design', 'logo design']
Notice the first result: by default, uppercase letters sort before lowercase ones. key=str.lower fixes that.
To reverse a list without sorting it, use list.reverse() (in place) or a [::-1] slice (new list).
Looping Over a List
The simplest loop goes through every item:
pages = ["Home", "About", "Contact"]
for page in pages:
print(page)
When you also need each item’s position, use enumerate() instead of counting by hand. Passing start=1 makes the count begin at 1, which is friendlier for people:
for number, page in enumerate(pages, start=1):
print(f"{number}. {page}")
# 1. Home
# 2. About
# 3. Contact
To loop over two lists side by side, use zip():
pages = ["Home", "About", "Contact"]
visits = [1200, 340, 210]
for page, count in zip(pages, visits):
print(f"{page}: {count} visits")
Useful Built-in Functions
These work on any list of numbers:
visits = [1200, 340, 210, 890]
print(len(visits)) # 4
print(sum(visits)) # 2640
print(min(visits)) # 210
print(max(visits)) # 1200
print(sum(visits) / len(visits)) # 660.0 (the average)
List Comprehensions
A list comprehension builds a new list from an existing one in a single line. It’s one of Python’s most loved features. Compare these two ways of making every page name uppercase:
pages = ["home", "about", "contact"]
# With a regular loop
upper_pages = []
for page in pages:
upper_pages.append(page.upper())
# With a list comprehension
upper_pages = [page.upper() for page in pages]
print(upper_pages) # ['HOME', 'ABOUT', 'CONTACT']
Read it as: “give me page.upper() for each page in pages.”
Add an if at the end to filter items:
prices = [5, 19, 49, 99, 150]
affordable = [p for p in prices if p < 50]
print(affordable) # [5, 19, 49]
with_tax = [round(p * 1.0775, 2) for p in prices]
print(with_tax) # [5.39, 20.47, 52.8, 106.67, 161.62]
Comprehensions are great for short transformations and filters. If one starts getting long or hard to read, a regular for loop is the better choice.
Nested Lists
A list can contain other lists. That’s a natural way to store a table, where each inner list is a row:
schedule = [
["Mon", "Design mockups"],
["Tue", "Build homepage"],
["Wed", "Client review"],
]
print(schedule[1]) # ['Tue', 'Build homepage']
print(schedule[1][1]) # Build homepage
for day, task in schedule:
print(f"{day}: {task}")
schedule[1][1] reads from left to right: get row 1, then item 1 of that row.
Dictionaries in Depth
Creating Dictionaries
client = {
"name": "Maria",
"business": "Bright Bakery",
"budget": 2500,
"is_active": True,
}
settings = {} # an empty dictionary
Keys are usually strings, but they can be any immutable value — numbers and tuples work too. A list can’t be a key, because it can change:
prices_by_size = {1: 9.99, 2: 17.99} # number keys work
bad = {["a", "b"]: 1} # TypeError: unhashable type: 'list'
Each key can appear only once. If you assign to a key that already exists, the old value is replaced.
Reading Values Safely
Square brackets raise a KeyError when the key is missing. .get() returns None — or a default you choose — instead:
print(client["name"]) # Maria
print(client.get("phone")) # None
print(client.get("phone", "N/A")) # N/A
Use square brackets when the key must be there (a missing key means a real bug), and .get() when it’s optional.
To check whether a key exists, use in. Note that in checks keys, not values:
print("budget" in client) # True
print("Maria" in client) # False ("Maria" is a value, not a key)
Adding, Updating, and Removing
client["email"] = "[email protected]" # add a new key
client["budget"] = 3000 # update an existing key
client.update({"budget": 3500, "phone": "555-0100"}) # update several at once
removed = client.pop("phone") # remove a key and return its value
print(removed) # 555-0100
del client["is_active"] # remove a key without returning it
print(client)
# {'name': 'Maria', 'business': 'Bright Bakery', 'budget': 3500, 'email': '[email protected]'}
Like .get(), .pop() accepts a default so it won’t crash when the key is missing: client.pop("fax", None).
Since Python 3.7, dictionaries remember the order in which keys were added, so they print and loop in that order.
Keys, Values, and Items
Three methods let you look at a dictionary from different angles:
plan = {"Basic": 499, "Business": 1499, "Premium": 2999}
print(list(plan.keys())) # ['Basic', 'Business', 'Premium']
print(list(plan.values())) # [499, 1499, 2999]
print(list(plan.items())) # [('Basic', 499), ('Business', 1499), ('Premium', 2999)]
Looping Over a Dictionary
Looping over a dictionary directly gives you its keys. Most of the time, you’ll want .items(), which gives you each key and value together:
for name, price in plan.items():
print(f"{name} plan: ${price:,}")
# Basic plan: $499
# Business plan: $1,499
# Premium plan: $2,999
You can also use .values() with the built-in functions from earlier:
print(sum(plan.values())) # 4997
print(max(plan.values())) # 2999
Counting with a Dictionary
Counting how often something appears is one of the most common jobs for a dictionary. The trick is .get(key, 0), which starts each new key at zero:
statuses = ["paid", "pending", "paid", "overdue", "paid", "pending"]
counts = {}
for status in statuses:
counts[status] = counts.get(status, 0) + 1
print(counts) # {'paid': 3, 'pending': 2, 'overdue': 1}
Tip: Python’s standard library has a shortcut for exactly this job:
from collections import Counter, thenCounter(statuses). It’s worth knowing the manual version first, though — the same pattern works for totals, groups, and much more.
Sorting a Dictionary by Its Values
Dictionaries don’t have a .sort() method, but sorted() works on them. To rank keys by their values, pass the dictionary’s own .get method as the key:
visits = {"Home": 1200, "Contact": 210, "About": 340, "Blog": 890}
ranked = sorted(visits, key=visits.get, reverse=True)
print(ranked) # ['Home', 'Blog', 'About', 'Contact']
for page in ranked:
print(f"{page}: {visits[page]}")
Dictionary Comprehensions
Just like lists, dictionaries have comprehensions. Use curly braces and a key: value pair:
prices = {"Basic": 499, "Business": 1499, "Premium": 2999}
discounted = {name: round(price * 0.9) for name, price in prices.items()}
print(discounted) # {'Basic': 449, 'Business': 1349, 'Premium': 2699}
premium_only = {name: price for name, price in prices.items() if price > 1000}
print(premium_only) # {'Business': 1499, 'Premium': 2999}
A common use is building a dictionary from two lists with zip():
pages = ["Home", "About", "Contact"]
visits = [1200, 340, 210]
page_visits = dict(zip(pages, visits))
print(page_visits) # {'Home': 1200, 'About': 340, 'Contact': 210}
Nested Dictionaries
A dictionary’s values can be other dictionaries (or lists). Chain the keys to reach the data you want:
site = {
"name": "Bright Bakery",
"contact": {"email": "[email protected]", "phone": "555-0100"},
"pages": ["Home", "Menu", "Contact"],
}
print(site["contact"]["email"]) # [email protected]
print(site["pages"][1]) # Menu
print(len(site["pages"])) # 3
Lists of Dictionaries: Working with Real Data
Here’s where lists and dictionaries really shine together. A list of dictionaries is the most common shape for real data: each dictionary is one record, and the list holds all the records. It’s exactly how rows of a spreadsheet, results from a database, and most API responses look in Python.
projects = [
{"client": "Bright Bakery", "type": "Website", "budget": 2500, "paid": True},
{"client": "Lopez Landscaping", "type": "SEO", "budget": 900, "paid": False},
{"client": "Calexico Coffee", "type": "Website", "budget": 3200, "paid": True},
{"client": "Valley Dental", "type": "Hosting", "budget": 300, "paid": False},
]
With the tools from this post, you can answer real questions about this data:
# How many projects are there?
print(len(projects)) # 4
# What's the total value of all projects?
total = sum(p["budget"] for p in projects)
print(total) # 6900
# Which clients haven't paid yet?
unpaid = [p["client"] for p in projects if not p["paid"]]
print(unpaid) # ['Lopez Landscaping', 'Valley Dental']
# Only the website projects
websites = [p for p in projects if p["type"] == "Website"]
print(len(websites)) # 2
# The biggest project
biggest = max(projects, key=lambda p: p["budget"])
print(biggest["client"]) # Calexico Coffee
# All projects, largest budget first
by_budget = sorted(projects, key=lambda p: p["budget"], reverse=True)
for p in by_budget:
print(f"{p['client']}: ${p['budget']:,}")
The last two examples use lambda, a short way to write a tiny function in one line. lambda p: p["budget"] simply means “given a project p, use its budget.” You’ll learn about functions properly in Part 6: Functions; for now, think of it as telling max() and sorted() which value to compare.
From JSON to Python and Back
When you get data from an API or a .json file, Python’s built-in json module turns it into lists and dictionaries — the same structures you’ve just been working with:
import json
response = '[{"page": "Home", "visits": 1200}, {"page": "Blog", "visits": 890}]'
data = json.loads(response) # JSON text → Python list of dictionaries
print(data[0]["page"]) # Home
print(json.dumps(data[1])) # {"page": "Blog", "visits": 890} (Python → JSON text)
That’s why lists and dictionaries are such important skills: once you’re comfortable with them, data from almost any source is just more of the same. To learn how to load data from files, APIs, and databases, see Python for Data Analysis: Accessing Your Data.
Putting It All Together: A Monthly Project Report
This script combines everything from this post into a short report:
projects = [
{"client": "Bright Bakery", "type": "Website", "budget": 2500, "paid": True},
{"client": "Lopez Landscaping", "type": "SEO", "budget": 900, "paid": False},
{"client": "Calexico Coffee", "type": "Website", "budget": 3200, "paid": True},
{"client": "Valley Dental", "type": "Hosting", "budget": 300, "paid": False},
]
# Total budget per project type
totals_by_type = {}
for project in projects:
kind = project["type"]
totals_by_type[kind] = totals_by_type.get(kind, 0) + project["budget"]
# Money received and money still owed
received = sum(p["budget"] for p in projects if p["paid"])
owed = sum(p["budget"] for p in projects if not p["paid"])
unpaid_clients = sorted(p["client"] for p in projects if not p["paid"])
print("Revenue by type:")
for kind in sorted(totals_by_type, key=totals_by_type.get, reverse=True):
print(f" {kind}: ${totals_by_type[kind]:,}")
print(f"Received: ${received:,}")
print(f"Still owed: ${owed:,}")
print("Follow up with:")
for number, client in enumerate(unpaid_clients, start=1):
print(f" {number}. {client}")
Running it prints:
Revenue by type:
Website: $5,700
SEO: $900
Hosting: $300
Received: $5,700
Still owed: $1,200
Follow up with:
1. Lopez Landscaping
2. Valley Dental
Try adding another project to the list and running it again — the report updates itself.
Common Beginner Mistakes
Assigning the result of .sort(). .sort() changes the list and returns None, so this loses your data:
prices = [49, 19, 99]
prices = prices.sort()
print(prices) # None
Either call prices.sort() on its own line, or use prices = sorted(prices).
Removing items from a list while looping over it. Python skips items when the list shrinks under the loop:
tags = ["old", "old", "new", "old"]
for tag in tags:
if tag == "old":
tags.remove(tag)
print(tags) # ['new', 'old'] ← one "old" was skipped
Build a new list with a comprehension instead: tags = [t for t in tags if t != "old"].
Using append() when you mean extend(). append() adds a whole list as a single item. Use extend() to add each item.
Going one past the end. A list of 5 items has indexes 0 to 4, so items[5] raises an IndexError. Use items[-1] for the last item.
Assuming a key exists. client["phone"] raises a KeyError if the key isn’t there. Use client.get("phone") for optional data.
Expecting in to search dictionary values. "Maria" in client checks the keys. To search values, use "Maria" in client.values().
Copying nested data with .copy(). .copy() makes a shallow copy: the outer list or dictionary is new, but the lists and dictionaries inside it are still shared with the original. To fully copy nested data, use copy.deepcopy():
import copy
original = {"pages": ["Home", "About"]}
shallow = original.copy()
deep = copy.deepcopy(original)
original["pages"].append("Blog")
print(shallow["pages"]) # ['Home', 'About', 'Blog'] ← shared with the original
print(deep["pages"]) # ['Home', 'About'] ← fully independent
Practice Exercises
- Start with
pages = ["Home", "About"]. Add"Contact"to the end, insert"Services"at position 1, then remove"About". Print the final list. - Given
scores = [88, 92, 79, 95, 61], print the highest score, the lowest score, and the average rounded to one decimal place. - Use a list comprehension to get only the even numbers from
range(1, 21). - Sort
["banana", "Apple", "cherry"]alphabetically, ignoring capital letters. - Count how many times each letter appears in the word
"mississippi"using a dictionary. - Create a list of three dictionaries describing products (name and price). Print the names of products under $50, then print the most expensive product’s name.
For more practice with real data, Data Cleaning with Python Pandas: From Raw Data to Analysis shows what you can do once your data outgrows plain lists and dictionaries.
Conclusion
Lists and dictionaries are Python’s workhorses. Lists keep items in order and let you add, remove, sort, and slice them; dictionaries let you store and look up values by name. Comprehensions let you transform both in a single readable line, and combining them — especially as a list of dictionaries — gives you a way to represent almost any real-world data, from spreadsheet rows to API responses.
The best way to make these skills stick is to use them on data you care about: your website’s pages, a list of clients, your monthly expenses. Start with a list, then a dictionary, then a list of dictionaries — and see how many questions you can answer about your own data.
Next in the Python Basics series: If Statement, how to make your programs decide what to do.
Python Basics series: Part 1: What Every Beginner Should Know · Part 2: Variables and Data Types · Part 3: Lists and Dictionaries · Part 4: If Statement · Part 5: User Input and While Loops · Part 6: Functions · Part 7: Classes · Part 8: Files and Exceptions · Part 9: Testing Your Code
