Standard Library Essentials
Master the powerful tools that come built-in with Python.
Introduction
Python's standard library is often called "batteries included", it comes with powerful modules for common tasks that you'll use in almost every project. From handling dates and times to working with JSON data, managing files, and using specialized data structures, these built-in tools save you from reinventing the wheel. Learning the standard library makes you immediately more productive and prepares you for real-world Python development. No installation required, these tools are ready to use right now!
The datetime Module
The datetime module provides classes for working with dates and times. It's essential for scheduling, logging, data analysis, and any application that needs to track when things happen.
About the examples below: anything built on datetime.now() prints a different answer every second, which makes it useless for checking your work against. So after the first example, these snippets pin a fixed moment (Monday, March 16 2026 at 14:30:45) and the output shown is exactly what you will get. Swap in datetime.now() or date.today() when you write the real thing.
Getting Current Date and Time
from datetime import datetime, date
# The current moment. These two lines print whenever you run them,
# so your values will not match the ones shown below.
now = datetime.now()
print(now)
print(date.today())
# Every other example in this section pins a fixed moment instead, so
# the printed values are the same for you as they are here.
moment = datetime(2026, 3, 16, 14, 30, 45, 123456)
print(moment)
# Access individual components
print(moment.year)
print(moment.month)
print(moment.day)
print(moment.hour)
print(moment.minute)
print(moment.second)
# Get day of week (0 = Monday, 6 = Sunday)
print(moment.weekday())
# Get day name
print(moment.strftime("%A"))Expected Output:
2026-09-08 10:48:55.061544 2026-09-08 2026-03-16 14:30:45.123456 2026 3 16 14 30 45 0 Monday
Creating Specific Dates and Times
from datetime import datetime, date, time # Create specific date birthday = date(1990, 5, 15) # May 15, 1990 print(birthday) # Create specific datetime meeting = datetime(2024, 12, 25, 14, 30) # Dec 25, 2024 at 2:30 PM print(meeting) # Create specific time alarm = time(7, 30, 0) # 7:30:00 AM print(alarm) # Create time with microseconds precise_time = time(12, 30, 45, 123456) print(precise_time)
Expected Output:
1990-05-15 2024-12-25 14:30:00 07:30:00 12:30:45.123456
Formatting Dates and Times
Use strftime() (string format time) to convert dates to readable strings.
from datetime import datetime
# A fixed moment, so the formatted results below are exact.
# In real code this would be datetime.now().
moment = datetime(2026, 3, 16, 14, 30, 45)
# Common formats
print(moment.strftime("%Y-%m-%d"))
print(moment.strftime("%m/%d/%Y"))
print(moment.strftime("%B %d, %Y"))
print(moment.strftime("%A, %B %d, %Y"))
# Time formats
print(moment.strftime("%H:%M:%S")) # 24-hour
print(moment.strftime("%I:%M %p")) # 12-hour
# Combined
print(moment.strftime("%Y-%m-%d %H:%M:%S"))
print(moment.strftime("%B %d, %Y at %I:%M %p"))
# Common format codes:
# %Y - Year (4 digits) %m - Month (01-12)
# %d - Day (01-31) %H - Hour 24h (00-23)
# %I - Hour 12h (01-12) %M - Minute (00-59)
# %S - Second (00-59) %p - AM/PM
# %A - Weekday full %B - Month full
# %a - Weekday short %b - Month shortExpected Output:
2026-03-16 03/16/2026 March 16, 2026 Monday, March 16, 2026 14:30:45 02:30 PM 2026-03-16 14:30:45 March 16, 2026 at 02:30 PM
Parsing Date Strings
Use strptime() (string parse time) to convert strings to datetime objects.
from datetime import datetime
# Parse different date formats
date_str1 = "2024-01-15"
dt1 = datetime.strptime(date_str1, "%Y-%m-%d")
print(dt1)
date_str2 = "01/15/2024"
dt2 = datetime.strptime(date_str2, "%m/%d/%Y")
print(dt2)
date_str3 = "January 15, 2024"
dt3 = datetime.strptime(date_str3, "%B %d, %Y")
print(dt3)
# Parse datetime with time
datetime_str = "2024-01-15 14:30:45"
dt4 = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
print(dt4)
# Handle different formats
formats = ["%Y-%m-%d", "%m/%d/%Y", "%d-%m-%Y"]
date_string = "15-01-2024"
for fmt in formats:
try:
parsed = datetime.strptime(date_string, fmt)
print(f"Success: {parsed}")
break
except ValueError:
continueExpected Output:
2024-01-15 00:00:00 2024-01-15 00:00:00 2024-01-15 00:00:00 2024-01-15 14:30:45 Success: 2024-01-15 00:00:00
Date Arithmetic
Use timedelta to perform arithmetic with dates.
from datetime import timedelta, date
# Create timedelta
one_day = timedelta(days=1)
one_week = timedelta(weeks=1)
mixed = timedelta(days=7, hours=2, minutes=30)
print(one_day, one_week, mixed)
# Add/subtract time. Real code would use date.today() here; this pins a
# fixed day so every result below stays the same whenever you run it.
today = date(2026, 3, 16)
tomorrow = today + timedelta(days=1)
yesterday = today - timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Today: {today}")
print(f"Tomorrow: {tomorrow}")
print(f"Yesterday: {yesterday}")
print(f"Next week: {next_week}")
# Calculate age
birthday = date(1990, 5, 15)
age_delta = today - birthday
age_years = age_delta.days // 365
print(f"Age: approximately {age_years} years")
# Calculate days until event
event_date = date(2026, 12, 25)
days_until = (event_date - today).days
print(f"Days until Christmas: {days_until}")Expected Output:
1 day, 0:00:00 7 days, 0:00:00 7 days, 2:30:00 Today: 2026-03-16 Tomorrow: 2026-03-17 Yesterday: 2026-03-15 Next week: 2026-03-23 Age: approximately 35 years Days until Christmas: 284
Practical datetime Examples
from datetime import date, datetime, timedelta
# Calculate project deadline
start_date = datetime(2026, 1, 15)
duration_days = 30
deadline = start_date + timedelta(days=duration_days)
print(f"Project deadline: {deadline.strftime('%B %d, %Y')}")
# Check if a date is a weekend
def is_weekend(d):
return d.weekday() >= 5 # 5 = Saturday, 6 = Sunday
# Passing the date in (rather than calling date.today() inside) keeps the
# function testable, and lets us show both answers
for d in [date(2026, 3, 16), date(2026, 3, 21)]:
label = "the weekend" if is_weekend(d) else "a weekday"
print(f"{d} ({d.strftime('%A')}) is {label}")
# Calculate business days between dates
def business_days_between(start, end):
days = 0
current = start
while current <= end:
if current.weekday() < 5: # Monday-Friday
days += 1
current += timedelta(days=1)
return days
start = date(2026, 3, 16) # Monday
end = date(2026, 3, 20) # Friday
print(f"Business days: {business_days_between(start, end)}")
# Format timestamps for logs. Real code would default to datetime.now()
def log_message(message, when):
timestamp = when.strftime("%Y-%m-%d %H:%M:%S")
return f"[{timestamp}] {message}"
moment = datetime(2026, 3, 16, 14, 30, 45)
print(log_message("Application started", moment))
# Calculate time remaining until a deadline
deadline = datetime(2026, 12, 31, 23, 59, 59)
remaining = deadline - moment
print(f"Time remaining: {remaining.days} days, {remaining.seconds // 3600} hours")Expected Output:
Project deadline: February 14, 2026 2026-03-16 (Monday) is a weekday 2026-03-21 (Saturday) is the weekend Business days: 5 [2026-03-16 14:30:45] Application started Time remaining: 290 days, 9 hours
Timezone Note: The examples above use naive datetime objects (no timezone info). For production apps with multiple timezones, use the standard library's zoneinfo module (built in since Python 3.9) for timezone-aware datetimes. You may still see the third-party pytz package in older codebases, but it is no longer recommended for new code.
The json Module
JSON (JavaScript Object Notation) is the most common data format for APIs and configuration files. The json module makes it easy to convert between Python objects and JSON strings.
Converting Python to JSON (Serialization)
Use json.dumps() (dump string) to convert Python objects to JSON strings.
import json
# Python dictionary to JSON
person = {
"name": "Alice",
"age": 30,
"city": "NYC",
"is_student": False,
"grades": [85, 90, 92]
}
json_string = json.dumps(person)
print(json_string)
# Pretty print with indentation. Note that indent puts every list element
# on its own line too, not just the dictionary keys.
json_pretty = json.dumps(person, indent=4)
print(json_pretty)
# Convert list to JSON
numbers = [1, 2, 3, 4, 5]
json_list = json.dumps(numbers)
print(json_list)
# Nested structures
data = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"total": 2
}
json_nested = json.dumps(data, indent=2)
print(json_nested)Expected Output:
{"name": "Alice", "age": 30, "city": "NYC", "is_student": false, "grades": [85, 90, 92]}
{
"name": "Alice",
"age": 30,
"city": "NYC",
"is_student": false,
"grades": [
85,
90,
92
]
}
[1, 2, 3, 4, 5]
{
"users": [
{
"id": 1,
"name": "Alice"
},
{
"id": 2,
"name": "Bob"
}
],
"total": 2
}Converting JSON to Python (Deserialization)
Use json.loads() (load string) to convert JSON strings to Python objects.
import json
# JSON string to Python
json_string = '{"name": "Alice", "age": 30, "city": "NYC"}'
person = json.loads(json_string)
print(person)
print(type(person))
# Access the data
print(person["name"])
print(person["age"])
# JSON array to Python list
json_array = '[1, 2, 3, 4, 5]'
numbers = json.loads(json_array)
print(numbers)
print(type(numbers))
# Complex nested structure
json_data = '''
{
"users": [
{"id": 1, "name": "Alice", "active": true},
{"id": 2, "name": "Bob", "active": false}
],
"total": 2,
"page": 1
}
'''
data = json.loads(json_data)
print(data["users"][0]["name"])
print(len(data["users"]))Expected Output:
{'name': 'Alice', 'age': 30, 'city': 'NYC'}
<class 'dict'>
Alice
30
[1, 2, 3, 4, 5]
<class 'list'>
Alice
2Reading and Writing JSON Files
import json
# Write Python object to JSON file
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding", "hiking"]
}
with open("person.json", "w") as file:
json.dump(data, file, indent=4) # dump (no 's') for files
# Read JSON file to Python object
with open("person.json", "r") as file:
loaded_data = json.load(file) # load (no 's') for files
print(loaded_data["name"])
print(loaded_data["hobbies"])
# Append to existing JSON file (read, modify, write)
with open("person.json", "r") as file:
data = json.load(file)
data["city"] = "NYC" # Add new field
data["age"] = 31 # Update field
with open("person.json", "w") as file:
json.dump(data, file, indent=4)
# Read the file back to see the result. Note that the new key lands at
# the end: json.dump writes the dictionary in insertion order.
with open("person.json") as file:
print(file.read())Expected Output:
Alice
['reading', 'coding', 'hiking']
{
"name": "Alice",
"age": 31,
"hobbies": [
"reading",
"coding",
"hiking"
],
"city": "NYC"
}Practical JSON Examples
import json
# Configuration file
config = {
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"debug": True,
"max_connections": 100
}
# Save configuration
with open("config.json", "w") as f:
json.dump(config, f, indent=4)
# Load configuration
with open("config.json", "r") as f:
loaded_config = json.load(f)
db_host = loaded_config["database"]["host"]
print(f"Database host: {db_host}")
# API response simulation
api_response = {
"status": "success",
"data": {
"users": [
{"id": 1, "username": "alice", "email": "alice@example.com"},
{"id": 2, "username": "bob", "email": "bob@example.com"}
]
},
"timestamp": "2026-03-16T14:30:00Z"
}
# Convert to JSON string (as API would return)
json_response = json.dumps(api_response)
# Parse JSON response (as client would receive)
parsed = json.loads(json_response)
for user in parsed["data"]["users"]:
print(f"{user['username']}: {user['email']}")
# Handle JSON errors
invalid_json = '{"name": "Alice", "age": }' # Missing value
try:
data = json.loads(invalid_json)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")Expected Output:
Database host: localhost alice: alice@example.com bob: bob@example.com Invalid JSON: Expecting value: line 1 column 26 (char 25)
JSON Type Mapping: Python's dict ↔ JSON object, list/tuple ↔ JSON array, str ↔ JSON string, int/float ↔ JSON number, True/False ↔ JSON true/false, None ↔ JSON null
The collections Module
The collections module provides specialized container data types that extend Python's built-in containers (dict, list, set, tuple) with additional functionality.
Counter - Count Occurrences
Counter is a dict subclass for counting hashable objects. Perfect for frequency analysis.
from collections import Counter # Count items in a list fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"] counter = Counter(fruits) print(counter) # Access counts print(counter["apple"]) print(counter["cherry"]) print(counter["grape"]) # 0, not a KeyError # Count characters in a string text = "hello world" char_count = Counter(text) print(char_count) # Most common elements print(counter.most_common(2)) # Count words in text. Counter prints in count order, and items with equal # counts keep the order they were first seen in. sentence = "the quick brown fox jumps over the lazy dog" words = sentence.split() word_count = Counter(words) print(word_count.most_common(3))
Expected Output:
Counter({'apple': 3, 'banana': 2, 'cherry': 1})
3
1
0
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
[('apple', 3), ('banana', 2)]
[('the', 2), ('quick', 1), ('brown', 1)]Counter Operations
from collections import Counter
# Create Counter from different sources
c1 = Counter([1, 2, 2, 3, 3, 3])
c2 = Counter({"a": 3, "b": 2})
c3 = Counter(a=3, b=2) # keyword arguments
# Arithmetic operations
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
# Addition
combined = c1 + c2
print(combined)
# Subtraction (keeps positive counts only)
diff = c1 - c2
print(diff)
# Intersection (minimum)
intersection = c1 & c2
print(intersection)
# Union (maximum)
union = c1 | c2
print(union)
# Update (add counts)
c1 = Counter(a=3, b=1)
c1.update({"a": 1, "b": 2, "c": 3})
print(c1)
# Get total count
total = sum(c1.values())
print(f"Total items: {total}")Expected Output:
Counter({'a': 4, 'b': 3})
Counter({'a': 2})
Counter({'a': 1, 'b': 1})
Counter({'a': 3, 'b': 2})
Counter({'a': 4, 'b': 3, 'c': 3})
Total items: 10Working with Files and Paths: os and pathlib
The os module provides operating system functionality, while pathlib offers an object-oriented approach to file paths. Both are essential for file and directory operations.
Basic os Module Operations
import os
# Build a small throwaway tree so the calls below have something real to
# look at, and nothing outside this folder is touched
os.makedirs("demo/folder1", exist_ok=True)
with open("demo/file1.txt", "w") as f:
f.write("some content")
with open("demo/file2.py", "w") as f:
f.write("print('hi')")
# Get current working directory (yours will be wherever you ran this)
print(f"Current directory: {os.getcwd()}")
# Change directory
os.chdir("demo")
print(f"Now in: {os.path.basename(os.getcwd())}")
# List files in directory. listdir() returns them in arbitrary order,
# so sort before printing if you want a predictable result
print(sorted(os.listdir(".")))
# Check if path exists
print(os.path.exists("file1.txt"))
print(os.path.exists("nonexistent"))
# Check if it's a file or directory
print(os.path.isfile("file1.txt"))
print(os.path.isdir("folder1"))
# Get file size (in bytes)
print(f"Size: {os.path.getsize('file1.txt')} bytes")
# Create directory
os.mkdir("new_folder") # Single directory
os.makedirs("parent/child/grandchild") # Nested directories
print(sorted(os.listdir(".")))
# Remove directory (empty ones only; use shutil.rmtree for non-empty)
os.rmdir("new_folder")
print(sorted(os.listdir(".")))Expected Output:
Current directory: /tmp/stdlib-demo Now in: demo ['file1.txt', 'file2.py', 'folder1'] True False True True Size: 12 bytes ['file1.txt', 'file2.py', 'folder1', 'new_folder', 'parent'] ['file1.txt', 'file2.py', 'folder1', 'parent']
Working with Paths (os.path)
import os
# Join paths (handles OS differences).
# On Windows this same call produces folder\subfolder\file.txt
print(os.path.join("folder", "subfolder", "file.txt"))
# Split path into directory and filename
full_path = "/home/user/documents/report.pdf"
directory, filename = os.path.split(full_path)
print(f"Directory: {directory}")
print(f"Filename: {filename}")
# Get filename without path
print(os.path.basename(full_path))
# Get directory without filename
print(os.path.dirname(full_path))
# Split filename and extension
name, ext = os.path.splitext("report.pdf")
print(f"Name: {name}")
print(f"Extension: {ext}")
# os.path.abspath("data/file.txt") resolves against the current working
# directory, so it depends on where you run the script
# Check if path is absolute
print(os.path.isabs("/home/user"))
print(os.path.isabs("data/file.txt"))Expected Output:
folder/subfolder/file.txt Directory: /home/user/documents Filename: report.pdf report.pdf /home/user/documents Name: report Extension: .pdf True False
Modern Path Handling with pathlib
pathlib provides an object-oriented interface that's more intuitive and Pythonic than os.path.
from pathlib import Path
# Create Path object
path = Path("folder/subfolder/file.txt")
print(path)
# Current directory and home directory. Both depend on your machine,
# so these two lines are the only ones here that will differ for you.
print(Path.cwd())
print(Path.home())
# Join paths with / operator
data_dir = Path("data")
file_path = data_dir / "reports" / "2026" / "january.csv"
print(file_path)
# Check existence (nothing was created, so all three are False)
print(file_path.exists())
print(file_path.is_file())
print(file_path.is_dir())
# Get parts of path
full_path = Path("/home/user/documents/report.pdf")
print(full_path.parent)
print(full_path.name)
print(full_path.stem)
print(full_path.suffix)
# Get all parent directories
print(full_path.parents[0])
print(full_path.parents[1])
print(full_path.parents[2])Expected Output:
folder/subfolder/file.txt /tmp/stdlib-demo /home/user data/reports/2026/january.csv False False False /home/user/documents report.pdf report .pdf /home/user/documents /home/user /home
File Operations with pathlib
from pathlib import Path
# Write then read a file
file_path = Path("data.txt")
file_path.write_text("Hello, World!")
print(file_path.read_text())
# Read/write bytes
file_path.write_bytes(b"Binary content")
print(file_path.read_bytes())
# Create directory
new_dir = Path("new_folder")
new_dir.mkdir(exist_ok=True) # No error if it already exists
print(new_dir.is_dir())
# Create nested directories
nested = Path("parent/child/grandchild")
nested.mkdir(parents=True, exist_ok=True)
print(nested.is_dir())
# Build a small data directory to list
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
(data_dir / "notes.txt").write_text("notes")
(data_dir / "report.txt").write_text("report")
(data_dir / "scripts").mkdir(exist_ok=True)
(data_dir / "scripts" / "run.py").write_text("print('run')")
# List everything directly inside a directory. iterdir() yields entries in
# whatever order the filesystem returns, so sort for a stable listing
for entry in sorted(data_dir.iterdir()):
print(entry)
# Find files by pattern (glob)
print(sorted(data_dir.glob("*.txt")))
# Find files recursively
print(sorted(data_dir.rglob("*.py")))
# Get file stats
stats = (data_dir / "notes.txt").stat()
print(f"Size: {stats.st_size} bytes")
# stats.st_mtime is the modification time, a float that changes per run
# Rename/move file
old_path = data_dir / "notes.txt"
new_path = data_dir / "notes_renamed.txt"
old_path.rename(new_path)
print(new_path.exists(), old_path.exists())
# Delete file
new_path.unlink()
print(new_path.exists())Expected Output:
Hello, World!
b'Binary content'
True
True
data/notes.txt
data/report.txt
data/scripts
[PosixPath('data/notes.txt'), PosixPath('data/report.txt')]
[PosixPath('data/scripts/run.py')]
Size: 5 bytes
True False
FalsePractical File System Examples
from pathlib import Path
from datetime import datetime
# Build a small sample project so the searches below have a known answer
Path("project/src/utils").mkdir(parents=True, exist_ok=True)
Path("project/tests").mkdir(parents=True, exist_ok=True)
Path("project/main.py").write_text("print('main')\n")
Path("project/src/app.py").write_text("x" * 500)
Path("project/src/utils/helpers.py").write_text("y" * 2000)
Path("project/tests/test_app.py").write_text("z" * 100)
Path("project/README.md").write_text("# Project\n")
# Example 1: Find all Python files in a project
def find_python_files(directory):
"""Find all .py files recursively"""
path = Path(directory)
return sorted(path.rglob("*.py"))
py_files = find_python_files("project")
print(f"Found {len(py_files)} Python files")
for file in py_files:
print(f" {file}")
# Example 2: Get total size of a directory
def get_directory_size(directory):
"""Calculate total size of all files in directory"""
path = Path(directory)
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
print(f"Directory size: {get_directory_size('project')} bytes")
# Example 3: Find largest files
def find_largest_files(directory, n=5):
"""Find the N largest files in directory"""
path = Path(directory)
files_with_size = [
(f, f.stat().st_size) for f in path.rglob("*") if f.is_file()
]
# Sort by size descending, then by path so ties are not left to chance
files_with_size.sort(key=lambda item: (-item[1], str(item[0])))
return files_with_size[:n]
print("Largest files:")
for file, size in find_largest_files("project", n=3):
print(f" {size} bytes - {file}")
# Example 4: Backup a file with a timestamp in its name
def backup_file(file_path, when):
"""Create a timestamped backup of a file"""
path = Path(file_path)
timestamp = when.strftime("%Y%m%d_%H%M%S")
backup_path = path.parent / "backups" / f"{path.stem}_{timestamp}{path.suffix}"
backup_path.parent.mkdir(exist_ok=True)
backup_path.write_bytes(path.read_bytes())
return backup_path
# Real code would pass datetime.now(); a fixed moment keeps the name stable
print(backup_file("project/main.py", datetime(2026, 3, 16, 14, 30, 45)))Expected Output:
Found 4 Python files project/main.py project/src/app.py project/src/utils/helpers.py project/tests/test_app.py Directory size: 2624 bytes Largest files: 2000 bytes - project/src/utils/helpers.py 500 bytes - project/src/app.py 100 bytes - project/tests/test_app.py project/backups/main_20260316_143045.py
Best practice: Use pathlibfor new code, it's more modern, readable, and cross-platform. Use os when you need specific system-level operations or when working with legacy code.
Quick Reference: When to Use Each Module
datetime
Use when you need to:
- Track timestamps
- Schedule events
- Calculate time differences
- Format dates for display
- Parse date strings
- Log with timestamps
json
Use when you need to:
- Work with APIs
- Save configuration files
- Store structured data
- Exchange data between systems
- Serialize Python objects
- Parse JSON responses
collections
Use when you need to:
- Counter: Count frequencies
- Group data efficiently
- deque: Implement queues/stacks (Intermediate)
- defaultdict: Auto-initialize values (Intermediate)
os & pathlib
Use when you need to:
- Navigate file systems
- Create/delete directories
- Check if files exist
- Find files by pattern
- Get file metadata
- Build cross-platform paths
Key Takeaways
datetime Module
datetime.now()for current timestrftime()to format datesstrptime()to parse datestimedeltafor date arithmetic- Use for timestamps, scheduling, logging
json Module
json.dumps()Python → JSON stringjson.loads()JSON string → Pythonjson.dump()write to filejson.load()read from file- Use for APIs, configs, data exchange
collections Module
Counterfor frequency countingdefaultdictfor default values (Intermediate)dequefor fast both-end ops (Intermediate)most_common()for top items- Better than plain dict/list for special cases
os & pathlib
Pathfor modern path handling/operator to join paths.glob()to find files by pattern.exists(),.is_file(),.is_dir()- Prefer pathlib for new code
What's Next?
Congratulations! You've learned essential standard library modules - datetime, json, collections, os, and pathlib - that you'll use in virtually every Python project. In the next lesson, we'll explore:
- Debugging basics - Learn print debugging, using Python's debugger (pdb), and techniques to find and fix bugs efficiently
- Common error patterns - Understand typical mistakes and how to avoid them
- Python Intermediate - When ready, explore testing, regular expressions, advanced file formats, and professional development patterns