🛠️ Build Real Python Programs

Python Adventure Projects

Every level teaches concepts — projects put them together. Kids build 13 real Python programs, from a first Name Greeter to a full Capstone Showcase, and code freely in the live Playground.

P1
🤝
Project 1 · Explorer Zone
Name Greeter
Your very first Python program

The classic first project every programmer builds. A program that asks for your name and says hello — but you write every line yourself! Simple, real, and satisfying.

What you build

  • Ask the user to enter their name with input()
  • Store the answer in a variable
  • Print a personalized greeting with print()
  • Works for any name — it's interactive!

Skills needed first

💬 print() 📦 Variables 👂 input()
# P1 — Name Greeter name = input("What is your name? ") print("Hello, " + name + "! Welcome to Python!")
🔓 Unlocks after completing Explorer Zone levels 1, 2, and 3
P2
🎯
Project 2 · Explorer Zone
Quiz Game
Logic, loops, and score tracking

Build a real quiz game that asks questions, checks answers, and keeps score. The program makes decisions with if/else and repeats with a for loop — just like a real app.

What you build

  • A list of questions and correct answers
  • Loop through each question with for
  • Check the player's answer with if/else
  • Add 1 to the score for each correct answer
  • Show the final score at the end

Skills needed first

🚦 if / else 🔄 for loop 📦 Variables
# P2 — Quiz Game (simplified) score = 0 for question in questions: answer = input(question) if answer == correct: score += 1 print("Score: " + str(score))
🔓 Unlocks after completing Explorer Zone levels 4 (if/else) and 5 (for loop)
P3
🔤
Project 3 · Adventure Zone
Word Counter
Functions, lists, and dictionaries

The most powerful project — a text analysis tool! Type any sentence and the program counts words, finds the longest one, and stores frequency data in a dictionary.

What you build

  • A def function that splits text into a list of words
  • Count words with len()
  • Find the longest word with a for loop
  • Count word frequency using a dictionary
  • Format and display results with f-strings

Skills needed first

⚙️ def functions 📋 Lists 🗂️ Dictionaries 🔤 String methods
# P3 — Word Counter (simplified) def count_words(text): words = text.split() return len(words) text = input("Type a sentence: ") print(f"Word count: {count_words(text)}")
🔓 Unlocks after completing Adventure Zone — functions, lists, and dictionaries
P4
📔
Project 4 · Files & Errors Zone
Personal Log System
A safe, menu-driven diary that saves to disk

🎯 Goal: Build a command-line diary program. Users can add new entries and view everything they've written so far, with every entry safely saved to a file between runs.

Skills needed first

📁 File I/O 🛡️ try/except 🔁 while loop ⏱️ ~1.5 hrs

✅ Requirements

  • Use a while loop to show a menu with Add Entry, View Entries, and Quit
  • Add Entry appends the new text to diary.txt with a newline
  • View Entries opens diary.txt and prints every saved line
  • A missing diary.txt is handled safely with try/except instead of crashing
  • Every file operation uses with open(...)

💻 Starter Code

# P4 — Personal Log System def add_entry(text): # TODO: open "diary.txt" in append mode ("a") and write the # entry text followed by a newline. Use with open(...) as f: pass def view_entries(): # TODO: try to open "diary.txt" for reading and print every # line. If the file doesn't exist yet, catch FileNotFoundError # and print "No entries yet!" instead of crashing. pass def show_menu(): print("\n1) Add entry") print("2) View entries") print("3) Quit") return input("Choose an option: ") running = True while running: choice = show_menu() if choice == "1": entry = input("What happened today? ") add_entry(entry) print("Saved!") elif choice == "2": view_entries() elif choice == "3": running = False print("Goodbye!") else: print("Please choose 1, 2, or 3.")
💡 Hint
Inside add_entry, open the file in append mode: with open("diary.txt", "a") as f: then f.write(text + "\n"). Inside view_entries, put the file-reading code inside a try block, and catch FileNotFoundError in the except block to print a friendly message instead of crashing.

▶ Expected Output

1) Add entry 2) View entries 3) Quit Choose an option: 2 No entries yet! 1) Add entry 2) View entries 3) Quit Choose an option: 1 What happened today? Learned about file handling Saved! 1) Add entry 2) View entries 3) Quit Choose an option: 2 Learned about file handling 1) Add entry 2) View entries 3) Quit Choose an option: 3 Goodbye!

This is an example run — your own entries and menu choices will differ.

🔍 Self-Check

  • Running the program twice keeps old entries instead of overwriting them
  • Deleting diary.txt and choosing View Entries prints a friendly message instead of crashing
  • The menu keeps looping until Quit is chosen
  • Every file operation uses with open()
🔓 Unlocks after completing the Files & Errors Zone — file writing and try/except basics
P5
📊
Project 5 · Algorithms Zone
Sorting Visualizer
Watch bubble sort draw itself as a bar chart

🎯 Goal: Combine the bubble sort algorithm with string formatting to literally watch the sort happen — each pass prints the list as a bar chart made of asterisks, one bar per number.

Skills needed first

🫧 Bubble sort 🔤 String formatting 🔁 Nested loops ⏱️ ~1.5 hrs

✅ Requirements

  • Write bar_chart(numbers) that turns each number into a string of that many asterisks
  • Write bubble_sort_visual(numbers) that performs a full bubble sort
  • Print the bar chart again after every completed pass
  • Print the final, fully sorted list at the end

💻 Starter Code

# P5 — Sorting Visualizer def bar_chart(numbers): # TODO: return a list of strings, one per number, where each # string is "*" repeated that many times. # Example: turn 5 into "*****" (five asterisks). bars = [] for n in numbers: pass # <-- build the bar for n and append it to bars return bars def print_chart(numbers): for bar in bar_chart(numbers): print(bar) def bubble_sort_visual(numbers): n = len(numbers) for pass_num in range(n - 1): for i in range(n - 1 - pass_num): # TODO: compare numbers[i] and numbers[i + 1]; swap them # if numbers[i] is the bigger one, just like in bubble sort. pass print(f"--- Pass {pass_num + 1} ---") print_chart(numbers) return numbers heights = [5, 2, 8, 1, 6] bubble_sort_visual(heights) print("Final order:", heights)
💡 Hint
The bar for a number n is just the asterisk character repeated n times using the multiplication operator: "*" * n . For the swap step, reuse the exact bubble sort pattern: compare the numbers at index i and i + 1, and swap them if the first one is bigger.

▶ Expected Output

--- Pass 1 --- ** ***** * ****** ******** --- Pass 2 --- ** * ***** ****** ******** --- Pass 3 --- * ** ***** ****** ******** --- Pass 4 --- * ** ***** ****** ******** Final order: [1, 2, 5, 6, 8]

🔍 Self-Check

  • bar_chart(5) produces a string of exactly 5 asterisks, no more, no less
  • Each pass prints a chart a little closer to sorted than the last
  • The final printed list is fully sorted from smallest to largest
  • Changing heights to a brand new list of 5 numbers still visualizes correctly
🔓 Unlocks after completing the Algorithms & Data Structures Zone — sorting and string formatting
P6
🧰
Project 6 · Modules & Testing Zone
Custom Toolkit Module
Build your own importable module of utilities

🎯 Goal: Instead of writing one big script, split reusable logic into your own module. Build 3 utility functions, prove each one works with an assert test, then import the whole module into a separate demo script.

Skills needed first

📦 Modules 🧪 assert tests ⚙️ def functions ⏱️ ~1.5 hrs

✅ Requirements

  • Create at least 3 utility functions in their own module file, toolkit.py
  • Write at least one assert test for each function, inside the module itself
  • Create a second file that imports the module and calls each function
  • Running the module by itself prints a self-tests-passed confirmation

💻 Starter Code

Save as toolkit.py:

# toolkit.py — your own utility module def is_even(n): # TODO: return True if n is even, False otherwise pass def reverse_string(s): # TODO: return s reversed (hint: slicing s[::-1]) pass def average(numbers): # TODO: return the average (mean) of a list of numbers pass # Prove each function works before anyone imports this module assert is_even(4) == True assert is_even(7) == False assert reverse_string("python") == "nohtyp" assert average([2, 4, 6]) == 4 print("toolkit.py: all self-tests passed!")

Save as demo.py, in the same folder:

# demo.py import toolkit print(toolkit.is_even(10)) print(toolkit.reverse_string("hello")) print(toolkit.average([10, 20, 30]))
💡 Hint
is_even can use the modulo operator to check for a remainder of zero. reverse_string can use slicing to reverse a string. average is simply the sum of the numbers divided by how many there are. In demo.py , call functions through the module name, exactly like toolkit.is_even(10).

▶ Expected Output

toolkit.py: all self-tests passed! True olleh 20.0

Run toolkit.py by itself first to confirm the self-tests pass, then run demo.py — it prints the same self-test line again because importing a module runs its code once.

🔍 Self-Check

  • toolkit.py runs on its own with no AssertionError
  • demo.py successfully imports toolkit and calls all 3 functions
  • Every function has at least one assert test proving it works
  • A 4th utility function could be added following the exact same pattern
🔓 Unlocks after completing the Modules, Decorators & Testing Zone — writing and importing your own module
P7
💬
Project 7 · Data & APIs Zone
Quote of the Day API Reader
Fetch a live quote, with a safe offline fallback

🎯 Goal: Fetch a random inspirational quote from a live web API using the requests library and JSON parsing — and if the request fails for any reason, fall back to a local list so the program never crashes.

Skills needed first

🌐 requests 🔗 JSON parsing 🛡️ try/except ⏱️ ~1.5 hrs

✅ Requirements

  • Write get_quote_from_api() that calls a web API and returns a formatted quote string
  • Keep a FALLBACK_QUOTES list with at least 3 quotes
  • Write get_daily_quote() that tries the API first and falls back safely if anything goes wrong
  • The program never crashes, even with no internet connection

💻 Starter Code

# P7 — Quote of the Day API Reader import random FALLBACK_QUOTES = [ "Code is like humor. When you have to explain it, it's bad. — Cory House", "First, solve the problem. Then, write the code. — John Johnson", "Simplicity is the soul of efficiency. — Austin Freeman", ] def get_quote_from_api(): import requests response = requests.get("https://api.quotable.io/random", timeout=5) response.raise_for_status() data = response.json() return f'{data["content"]}{data["author"]}' def get_daily_quote(): # TODO: try calling get_quote_from_api() and return its result. # If anything goes wrong (no internet, requests not installed, # bad response), catch Exception and return a random pick from # FALLBACK_QUOTES using random.choice() instead. pass print(get_daily_quote())
💡 Hint
Wrap the call to get_quote_from_api() in a try block, and return its result if it works. In the except block, catch the general Exception type (since many different things could go wrong — no internet, a missing library, or a bad response) and return random.choice(FALLBACK_QUOTES) instead.

▶ Expected Output

Simplicity is the soul of efficiency. — Austin Freeman

The exact quote differs if your computer is online and the requests package is installed — the example above shows the offline fallback path.

🔍 Self-Check

  • Turning off Wi-Fi (or breaking the requests import on purpose) still produces a quote instead of crashing
  • get_daily_quote() never lets an exception escape to whatever calls it
  • The fallback list has at least 3 different quotes
  • The API version combines the response's content and author fields into one readable string
🔓 Unlocks after completing the Data & APIs Zone — requests, JSON, and error handling
P8
📇
Project 8 · Capstone Zone
Contact Book
A menu-driven address book that remembers everything

🎯 Goal: The classic capstone project — build the full working version of the Contact Book concept from the game. A menu lets the user add, list, and delete contacts, and everything is saved to a JSON file so contacts survive between runs.

Skills needed first

🗂️ JSON files 🔁 Menu loop 📇 Dictionaries ⏱️ ~2 hrs

✅ Requirements

  • Load existing contacts from a JSON file when the program starts
  • Menu options to add, list, and delete a contact
  • Save contacts back to the JSON file after every add or delete
  • Listing with zero contacts prints a friendly message instead of nothing
  • Deleting a name that isn't in the contact book doesn't crash the program

💻 Starter Code

# P8 — Contact Book import json import os FILENAME = "contacts.json" def load_contacts(): # TODO: if FILENAME exists, open it and return json.load(f). # If it doesn't exist yet, return an empty dictionary instead. if os.path.exists(FILENAME): pass return {} def save_contacts(contacts): # TODO: write contacts to FILENAME as JSON using json.dump(contacts, f) pass def add_contact(contacts, name, phone): contacts[name] = phone save_contacts(contacts) def list_contacts(contacts): if not contacts: print("No contacts saved yet.") return for name, phone in contacts.items(): print(f"{name}: {phone}") def delete_contact(contacts, name): # TODO: remove name from contacts if it exists, then call # save_contacts(contacts). If it doesn't exist, print a # friendly "No contact named ..." message instead. pass contacts = load_contacts() running = True while running: print("\n1) Add 2) List 3) Delete 4) Quit") choice = input("Choose: ") if choice == "1": name = input("Name: ") phone = input("Phone: ") add_contact(contacts, name, phone) elif choice == "2": list_contacts(contacts) elif choice == "3": name = input("Name to delete: ") delete_contact(contacts, name) elif choice == "4": running = False print("Bye!") else: print("Please choose 1-4.")
💡 Hint
load_contacts should open the file only if it already exists, then use json.load(f) to turn its contents back into a dictionary. save_contacts should use json.dump(contacts, f) to write the whole dictionary back out. delete_contact should check if the name is a key in the dictionary before trying to remove it.

▶ Expected Output

1) Add 2) List 3) Delete 4) Quit Choose: 2 No contacts saved yet. 1) Add 2) List 3) Delete 4) Quit Choose: 1 Name: Ada Phone: 555-1234 1) Add 2) List 3) Delete 4) Quit Choose: 2 Ada: 555-1234 1) Add 2) List 3) Delete 4) Quit Choose: 4 Bye!

🔍 Self-Check

  • Adding a contact, quitting, and restarting the program still shows that contact (real file persistence)
  • Listing with zero contacts prints a friendly message instead of nothing
  • Deleting a name that doesn't exist doesn't crash the program
  • contacts.json contains valid JSON you could open in a text editor
🔓 Unlocks after completing the Capstone Builder Zone — combining files, dictionaries, and a menu loop
P9
🗒️
Project 9 · Files & Errors Zone
To-Do List with Deadlines
Save tasks to disk, sorted by what matters most

🎯 Goal: Combine file save and load with a sorting algorithm: save tasks with a priority number to a text file, then sort them with bubble sort so the most urgent task always prints first.

Skills needed first

📁 File I/O 🫧 Bubble sort 🔢 Tuples ⏱️ ~2 hrs

✅ Requirements

  • Load previously saved tasks from a text file when the program starts
  • Save every task, with its priority number, back to the text file
  • Sort tasks by priority using bubble sort before printing them
  • The most urgent task (lowest priority number) prints first

💻 Starter Code

# P9 — To-Do List with Deadlines FILENAME = "todo.txt" def load_tasks(): tasks = [] try: with open(FILENAME) as f: for line in f: text, priority = line.strip().split("|") tasks.append((text, int(priority))) except FileNotFoundError: pass return tasks def save_tasks(tasks): with open(FILENAME, "w") as f: for text, priority in tasks: f.write(f"{text}|{priority}\n") def sort_by_priority(tasks): # TODO: bubble-sort tasks so the lowest priority number (most # urgent) comes first. Compare tasks[i][1] to tasks[i + 1][1], # just like the Sorting Visualizer project. n = len(tasks) for pass_num in range(n - 1): for i in range(n - 1 - pass_num): pass # <-- compare and swap here return tasks tasks = load_tasks() tasks.append(("Finish homework", 2)) tasks.append(("Walk the dog", 1)) tasks.append(("Read a book", 3)) save_tasks(tasks) tasks = sort_by_priority(tasks) for text, priority in tasks: print(f"[Priority {priority}] {text}")
💡 Hint
Reuse the bubble sort pattern from the Sorting Visualizer project: compare tasks[i] and tasks[i + 1] by their priority number (the second item in each tuple), and swap the two tuples if the first one's priority is bigger.

▶ Expected Output

[Priority 1] Walk the dog [Priority 2] Finish homework [Priority 3] Read a book

🔍 Self-Check

  • Tasks print in priority order, lowest number first
  • todo.txt survives being closed and reopened in the same format
  • Deleting todo.txt and running the program again doesn't crash
  • The sort uses the same swap pattern as bubble sort, just comparing priority numbers instead
🔓 Unlocks after completing the Files & Errors and Algorithms Zones — file persistence plus sorting
P10
🗺️
Project 10 · Algorithms Zone
Text Adventure Game
Rooms as data, moves tracked by a decorator

🎯 Goal: Build a tiny text adventure where rooms are stored as a dictionary of dictionaries. Every time the player moves, a custom decorator logs the direction before the move actually happens.

Skills needed first

🗂️ Dict of dicts 🎀 Decorators 🔁 for loop ⏱️ ~2 hrs

✅ Requirements

  • Store rooms as a dictionary of dictionaries, each with a description and possible directions
  • Write a log_move decorator that prints the direction before every move
  • Apply the decorator to the move() function with @log_move
  • Moving in a direction that doesn't exist prints a friendly message instead of crashing

💻 Starter Code

# P10 — Text Adventure Game def log_move(func): # TODO: write a decorator that prints "Moving: <direction>" every # time the wrapped function runs, then still calls the original # function and returns its result. See the Timing Decorator # assignment for the pattern. def wrapper(*args, **kwargs): pass return wrapper rooms = { "hall": {"description": "A dusty entrance hall.", "north": "library", "east": "kitchen"}, "library": {"description": "Shelves of old spellbooks.", "south": "hall"}, "kitchen": {"description": "A cold, empty kitchen.", "west": "hall"}, } @log_move def move(current_room, direction): room = rooms[current_room] if direction in room: return room[direction] print("You can't go that way.") return current_room current = "hall" print(rooms[current]["description"]) for direction in ["north", "south", "east"]: current = move(current, direction) print(rooms[current]["description"])
💡 Hint
Inside wrapper , print a message that includes the direction — remember direction is the second argument, so it's args[1] . Then call the original function with func(*args, **kwargs) and return whatever it returns, so the decorator doesn't change how move() behaves.

▶ Expected Output

A dusty entrance hall. Moving: north Shelves of old spellbooks. Moving: south A dusty entrance hall. Moving: east A cold, empty kitchen.

🔍 Self-Check

  • Every call to move() prints which direction the player is moving in before showing the room
  • Walking toward a direction that isn't listed for the current room prints a friendly message instead of crashing
  • A new room can be added to the rooms dictionary without changing any function code
  • The decorator still returns the correct next room name to the rest of the program
🔓 Unlocks after completing the Algorithms and Modules & Testing Zones — nested dictionaries and decorators
P11
🔒
Project 11 · Modules & Testing Zone
Password Strength Checker
Score passwords with regex rules, proven by tests

🎯 Goal: Write a password strength checker that uses regular expressions to check for length, uppercase and lowercase letters, digits, and symbols — then prove your checker is correct with a set of assert-based test cases.

Skills needed first

🔍 re module 🧪 assert tests 🔢 Scoring logic ⏱️ ~2 hrs

✅ Requirements

  • Score a password based on length, uppercase, lowercase, digits, and symbols
  • Use the re module and regular expressions for every check
  • Return "Weak", "Medium", or "Strong" based on the total score
  • Prove the checker works with at least 4 assert statements before trusting it

💻 Starter Code

# P11 — Password Strength Checker import re def check_strength(password): score = 0 if len(password) >= 8: score += 1 if re.search(r"[A-Z]", password): score += 1 if re.search(r"[a-z]", password): score += 1 # TODO: add 1 to score if the password contains at least one # digit. Use re.search(r"\d", password). # TODO: add 1 to score if the password contains at least one # symbol from !@#$%^&* . Use re.search() with a character class. if score <= 2: return "Weak" elif score <= 4: return "Medium" else: return "Strong" # Prove the checker works before trusting it assert check_strength("abc") == "Weak" assert check_strength("abcdefgh") == "Weak" assert check_strength("Abcdefg1") == "Medium" assert check_strength("Abcdef1!") == "Strong" print("All strength tests passed!") print(check_strength(input("Enter a password to check: ")))
💡 Hint
Add two more if blocks using re.search() — one that looks for a digit with the pattern r"\d", and one that looks for a symbol using a character class containing the symbols you want to accept, like r"[!@#$%^&*]" . Each match should add 1 to score, exactly like the checks already written for uppercase and lowercase letters.

▶ Expected Output

All strength tests passed! Enter a password to check: Abcdef1! Strong

🔍 Self-Check

  • All 4 assert statements pass with no AssertionError
  • A password with only lowercase letters scores "Weak"
  • Adding an uppercase letter, a digit, and a symbol can raise a password all the way to "Strong"
  • Every regex pattern is written as a raw string starting with r
🔓 Unlocks after completing the Modules & Testing and Data & APIs Zones — regex plus assert-based testing
P12
🌤️
Project 12 · Data & APIs Zone
Mini Weather Dashboard
Parse and format weather data for 3 cities

🎯 Goal: Parse JSON weather data for three different cities and print a clean, formatted report for each one — the same pattern you'd use to display data pulled from a real weather API.

Skills needed first

🔗 JSON parsing 🔤 f-strings 🌐 requests (optional) ⏱️ ~1.5 hrs

✅ Requirements

  • Write parse_city() that turns one JSON string into a dictionary
  • Write print_report() that prints a one-line formatted summary for a city
  • Loop through all 3 cities in CITY_DATA and print a report for each
  • Output format matches: city name, temperature, and condition

💻 Starter Code

# P12 — Mini Weather Dashboard import json # Pretend these are 3 JSON responses saved from a weather API. # (To use a real API instead, install the requests package and # call requests.get(url).json() to get a dictionary the same way.) CITY_DATA = [ '{"city": "Kuala Lumpur", "temp_c": 31, "condition": "Partly Cloudy"}', '{"city": "London", "temp_c": 14, "condition": "Rainy"}', '{"city": "Tokyo", "temp_c": 22, "condition": "Clear"}', ] def parse_city(json_text): # TODO: use json.loads() to turn json_text into a dictionary # and return it. pass def print_report(city_dict): # TODO: print one line formatted like: # "Kuala Lumpur: 31C, Partly Cloudy" # using city_dict["city"], city_dict["temp_c"], city_dict["condition"] pass print("=== 3-City Weather Dashboard ===") for entry in CITY_DATA: city = parse_city(entry) print_report(city)
💡 Hint
parse_city() is a one-line function: it just returns json.loads(json_text). print_report() builds one f-string using the three dictionary keys — city, temp_c, and condition — and prints it.

▶ Expected Output

=== 3-City Weather Dashboard === Kuala Lumpur: 31C, Partly Cloudy London: 14C, Rainy Tokyo: 22C, Clear

🔍 Self-Check

  • parse_city() returns a real dictionary you can index with square brackets
  • print_report()'s output format matches exactly: city name, temperature, comma, condition
  • All 3 cities print without errors
  • You can explain where a real requests.get(...).json() call would replace the pretend CITY_DATA list
🔓 Unlocks after completing the Data & APIs Zone — json.loads() and formatted output
P13
🏆
Project 13 · Capstone Zone
FINAL Capstone Showcase
Your own bigger build, combining everything you've learned

🎯 Goal: Pick your own idea and build something bigger. This is a template, not a fixed assignment: follow the requirements checklist to combine file handling with at least one of an API call, a search/sort algorithm, or a class, to build the most ambitious program of the whole course.

Skills needed first

🏗️ Your choice 📁 Files 🧩 API / Algorithm / OOP ⏱️ ~3+ hrs

✅ Requirements

  • At least one file operation (read or write) using with open()
  • At least one try/except block that prevents a real crash
  • At least one of: a live API call with requests and json, a search/sort algorithm, or a class (OOP)
  • At least 2 custom functions or methods that work together
  • At least 1 assert test proving a piece of your project works
  • The whole program runs start to finish with no errors

💻 Starter Code

# P13 — FINAL Capstone Showcase # Pick your own idea! Some starting points: # - A budget tracker that saves entries to a file and reports totals # - A high-score leaderboard that sorts scores and saves them to disk # - A simple class-based library system (Book and Library classes) # - A trivia game that pulls questions from a local JSON file class Project: def __init__(self, name): self.name = name self.log = [] def do_something(self, item): # TODO: implement one core piece of your project's logic here. self.log.append(item) def save(self, filename): # TODO: use with open(filename, "w") as f: to save self.log, # one item per line. pass def load(self, filename): # TODO: use try/except FileNotFoundError to safely load # self.log back in from filename. pass if __name__ == "__main__": project = Project("My Capstone") # TODO: call your project's methods here, print the results, # and prove at least one piece works with an assert statement.
💡 Hint
Don't try to build everything at once. Get ONE feature fully working first — usually saving something to a file — before adding the next piece, like sorting the data you loaded back in or wrapping part of it in a class. Reuse code you already wrote in earlier projects (P4, P6, P9, or P11 are good starting points) instead of starting from zero.

▶ Expected Output

There is no single expected output for P13 — your program's output should match whatever your Requirements checklist promises it does.

🔍 Self-Check

  • At least one file operation using with open()
  • At least one try/except block that actually prevents a crash
  • At least one of: an API call, a search/sort algorithm, or a class
  • At least 2 custom functions or methods, connected together
  • At least 1 assert test
  • The whole program runs start to finish with no errors or crashes
🔓 Unlocks after completing every zone — Explorer through Capstone Builder

🖥️ Python Playground

A live in-browser Python editor powered by Skulpt. No installation needed — write and run Python code instantly.

✅ Syntax highlighting ▶ Run button 📋 Example programs 🌐 No install needed 📱 Works on mobile
▶ Open Playground
What projects do kids build in Python Adventure?
Python Adventure has 13 mini-projects. P1 Name Greeter (print + variables + input), P2 Quiz Game (if/else + for loop), and P3 Word Counter (functions + lists + dictionaries) use the original Explorer and Adventure zones. P4-P13 combine skills from the newer Files & Errors, Algorithms, Modules & Testing, Data & APIs, and Capstone zones — building things like a Personal Log System, a Contact Book, a Text Adventure Game, and a final Capstone Showcase. There is also a live Playground for free coding.
How hard are the Python Adventure projects?
P1 is beginner level — just 3 lines of code. P2 is intermediate — requires understanding if/else and for loops. P3 is advanced — needs def functions, lists, and dictionaries. P4-P13 get progressively bigger, combining multiple zones at once — P6 needs modules and testing, P10 combines algorithms with decorators, and P13 is a fully open-ended capstone that pulls in everything. Each project is only unlocked after the learner has completed the prerequisite levels.
Does Python Adventure have a live code editor?
Yes! The Playground tab has a live Python editor powered by Skulpt. Kids type and run Python instantly in the browser — no downloads or installations. The editor has syntax highlighting and example starter programs.
Can I run the project code outside the browser?
Yes! All code taught in Python Adventure is standard Python 3. After completing a project in the Playground, copy the code and run it in IDLE or VS Code on any computer. The game's win screen for P1 even encourages downloading Python from python.org.

🐍 Start Building Today!

Complete the Explorer Zone levels and unlock your first real Python project — free, no sign-up needed.

▶ Play Python Adventure Free 📚 See All Topics →