https://ift.tt/NTlAjeX Learning Python through isolated exercises is useful, but it doesn't quite capture what it feels like to...
Learning Python through isolated exercises is useful, but it doesn't quite capture what it feels like to build something real. Exercises teach you the syntax for dictionaries, loops, and functions in isolation. A project shows you how those pieces work together to produce an application that actually does something.
In this tutorial, we'll build a food ordering app from scratch. Imagine you run a local restaurant and want to give customers a way to browse your menu, add items to a cart, adjust quantities, and place an order. We'll build all of that using core Python fundamentals, with no external libraries required.
By the end, you'll have a complete, working Python application and a clearer sense of how functions, dictionaries, and loops fit into a real program architecture.
What You'll Learn
By the end of this tutorial, you'll know how to:
- Use nested dictionaries to store structured menu data
- Write modular functions that each handle one responsibility
- Use the
input()function to build interactive user experiences - Chain functions together so each building block feeds into the next
- Control application flow with a
whileloop and boolean flag - Test and debug incrementally as you build
Before You Start
You'll need Python 3.8+ and Jupyter Notebook. No external libraries are required. If you want to brush up on the fundamentals first, the Python Basics for Data Analysis path covers the dictionary, loop, and function skills that underpin this project.
Access the full project in the Dataquest app and the solution notebook on GitHub.
The Project Architecture
Before writing any code, it helps to think about what the application needs. A food ordering app has roughly three layers:
Data: What's on the menu? What's in the cart? What actions can a user take?
Logic: Functions that handle each action — displaying the menu, adding items, removing items, viewing and modifying the cart, and checking out.
Flow: A main loop that ties everything together, takes user input, and calls the right function.
We'll build these layers in order.
Part 1: Global Variables
Global variables are values that stay constant or need to be accessible throughout the entire application. We'll define all of them upfront.
RESTAURANT_NAME = "Hungry Hare"
By convention, global constants are written in ALL_CAPS. This doesn't change how Python handles them, but it signals to anyone reading your code that this value is meant to be stable.
Our menu uses a nested dictionary structure:
menu = {
"sku1": {"name": "Hamburger", "price": 6.51},
"sku2": {"name": "Cheeseburger", "price": 7.75},
"sku3": {"name": "Milkshake", "price": 5.99},
"sku4": {"name": "Fries", "price": 2.39},
"sku5": {"name": "Sub", "price": 5.87},
"sku6": {"name": "Ice Cream", "price": 1.55},
"sku7": {"name": "Fountain Drink", "price": 3.45},
"sku8": {"name": "Cookie", "price": 3.15},
"sku9": {"name": "Brownie", "price": 2.46},
"sku10": {"name": "Sauce", "price": 0.75}
}
Each menu item is identified by a SKU (stock keeping unit) rather than its name. This matters for maintainability: if the price of a hamburger changes, you update one dictionary value. If the item gets renamed, you update the name without touching anything else in the codebase. Referencing things by a stable ID is a pattern you'll see in real systems.
The app actions dictionary maps numbers to descriptions. These are the choices your user will see at every step:
app_actions = {
"1": "Add a new menu item to cart",
"2": "Remove an item from the cart",
"3": "Modify a cart item's quantity",
"4": "View cart",
"5": "Checkout",
"6": "Exit"
}
Finally, the sales tax rate and an empty cart:
SALES_TAX_RATE = 0.07
cart = {}
The cart starts empty and gets populated as the user orders. It will store SKU keys with integer quantities as values: {"sku1": 2, "sku4": 1} would mean two hamburgers and one order of fries.
Learning Insight: Separating your data from your logic is one of the most valuable habits in programming. When your menu lives in one dictionary and your cart in another, you can update either one without hunting through your function code to find where prices or quantities are stored.
Part 2: Displaying the Menu
def display_menu():
"""Displays all menu item SKUs, names, and prices."""
print("\n****Menu****\n")
for sku in menu:
parsed_sku = sku[3:]
item = menu[sku]['name']
price = menu[sku]['price']
print("(" + parsed_sku + ") " + item + ": $" + str(price))
print("\n")
sku[3:] slices off the "sku" prefix so the user sees "(1) Hamburger: \$6.51" instead of "(sku1) Hamburger: \$6.51". Price needs to be converted to a string with str() before concatenation since you can't use + to combine a string and a float directly.
Test it immediately:
display_menu()
****Menu****
(1) Hamburger: $6.51
(2) Cheeseburger: $7.75
(3) Milkshake: $5.99
(4) Fries: $2.39
...
Learning Insight: Test each function the moment you finish writing it. Catching a bug in a 10-line function is much easier than catching it after you've written 100 more lines that depend on it. Every function in this project gets a test call right after it's defined.
Part 3: Adding Items to the Cart
def add_to_cart(sku, quantity=1):
"""
Add an item and its quantity to the cart.
:param string sku: The input SKU number being ordered.
:param int quantity: The input quantity being ordered.
"""
if sku in menu:
if sku in cart:
cart[sku] += quantity
else:
cart[sku] = quantity
print("Added", quantity, "of", menu[sku]['name'], "to the cart.")
else:
print("I'm sorry. The menu number", sku, "that you entered is not on the menu.")
Two important design decisions here. First, quantity=1 is a default parameter value. If a customer adds an item without specifying how many, the function assumes one. If they specify more, the default is overridden. This is good UX because it reduces the number of inputs required for the common case.
Second, the function checks whether the SKU already exists in the cart. If it does, it increments the count. If it doesn't, it creates a new entry. This prevents a second order of hamburgers from overwriting the first.
Test it:
add_to_cart("sku1") # Adds 1 hamburger
add_to_cart("sku1", 10) # Adds 10 more hamburgers
print(cart)
Added 1 of Hamburger to the cart.
Added 10 of Hamburger to the cart.
{'sku1': 11}
Part 4: Removing Items from the Cart
def remove_from_cart(sku):
"""
Remove an item from the cart.
:param string sku: The input SKU number to remove from the cart.
"""
if sku in cart:
removed_val = cart.pop(sku)
print("Removed", removed_val, "of", menu[sku]['name'], "from the cart.")
else:
print(f"I'm sorry. The item with SKU {sku} is not currently in the cart.")
dict.pop(key) both removes the key and returns its value in one operation. We use the returned value to tell the user what was removed. Notice the else branch handles the case where the user tries to remove something that isn't in the cart — the function gives a helpful message rather than crashing.
The f-string in the else branch (f"I'm sorry. The item with SKU {sku}...") is a cleaner alternative to string concatenation for cases where you need to embed a variable. Anything inside {} gets evaluated and inserted into the string.
Part 5: Modifying Cart Quantities
def modify_cart(sku, quantity):
"""
Modify an item's quantity in the cart.
:param string sku: The input SKU number being modified.
:param int quantity: The input new quantity to use for the SKU.
"""
if sku in cart:
if quantity > 0:
cart[sku] = quantity
print("Modified", menu[sku]['name'], "quantity to", quantity, "in the cart.")
else:
remove_from_cart(sku)
else:
print(f"I'm sorry.", menu[sku]['name'], "is not currently in the cart.")
This function demonstrates calling one function from within another. If the user sets a quantity to zero or below, there's no reason to keep the item in the cart — so modify_cart calls remove_from_cart rather than duplicating that logic. This is the "don't repeat yourself" principle in practice: when two functions need to do the same thing, one calls the other.
Part 6: Viewing Cart Contents
def view_cart():
"""Display the menu item names and quantities inside the cart."""
print("\n****Cart Contents****\n")
subtotal = 0
for sku in cart:
if sku in menu:
quantity = cart[sku]
subtotal += menu[sku]["price"] * quantity
print(quantity, " x ", menu[sku]["name"])
tax = subtotal * SALES_TAX_RATE
total = subtotal + tax
print("Total: $", round(total, 2))
print("\n")
The function loops through the cart, looks up each item's price from the menu dictionary, and accumulates a subtotal. Tax and total are calculated outside the loop since they only need to be computed once after all items have been accounted for. Rounding the total to 2 decimal places ensures the dollar amount displays correctly.
Part 7: Checkout
def checkout():
"""Display the subtotal information for the user to checkout."""
print("\n****Checkout****\n")
view_cart()
print("Thank you for your order! Goodbye!")
print("\n")
Checkout is deliberately simple because all the real work is already done by view_cart(). This is reuse at its cleanest — one function calls another that already knows how to do exactly what's needed.
Part 8: Getting User Input
This is the most involved function in the project. It handles collecting SKU and quantity input from the user, with built-in validation.
def get_sku_and_quantity(sku_prompt, quantity_prompt=None):
"""
Get input from the user.
:param string sku_prompt: Prompt displayed before SKU entry.
:param string quantity_prompt: Prompt displayed before quantity entry.
Defaults to None when quantity input is not needed.
:returns: The full sku# value and optionally the quantity.
"""
item_sku = input(sku_prompt)
item_sku = "sku" + item_sku
if quantity_prompt:
quantity = input(quantity_prompt)
if not quantity.isdigit():
quantity = 1
quantity = int(quantity)
return item_sku, quantity
else:
return item_sku
A few things worth understanding here. The input() function always returns a string. When a user types "4", Python stores it as the string "4", not the integer 4. That's why we call int(quantity) before returning it.
The item_sku = "sku" + item_sku line prepends "sku" to whatever the user types. This means the user only has to type "4" to refer to Fries, and the function assembles the full key "sku4" that matches our dictionary.
quantity_prompt=None as a default parameter makes the function flexible. Some actions (like removing an item) only need the SKU. Others (like adding or modifying) need both the SKU and a quantity. The same function handles both cases by checking whether quantity_prompt was provided.
The return item_sku, quantity line returns two values as a tuple. When this function is called, the caller can unpack both values in one line: ordered_sku, quantity = get_sku_and_quantity(...).
Part 9: The Main Order Loop
With all the building blocks in place, the order loop ties them together.
def order_loop():
"""Loop ordering actions until checkout or exit."""
print("Welcome to the " + RESTAURANT_NAME + "!")
ordering = True
while ordering:
print("\n****Ordering Actions****\n")
for number in app_actions:
description = app_actions[number]
print("(" + number + ")", description)
response = input("Please enter the number of the action you want to take: ")
if response == "1":
display_menu()
sku_prompt = "Please enter the SKU number for the menu item you want to order: "
quantity_prompt = "Please enter the quantity you want to order [default is 1]: "
ordered_sku, quantity = get_sku_and_quantity(sku_prompt, quantity_prompt)
add_to_cart(ordered_sku, quantity)
elif response == "2":
display_menu()
sku_prompt = "Please enter the SKU number for the menu item you want to remove: "
item_sku = get_sku_and_quantity(sku_prompt)
remove_from_cart(item_sku)
elif response == "3":
display_menu()
sku_prompt = "Please enter the SKU number for the menu item you want to modify: "
quantity_prompt = "Please enter the quantity you want to change to [default is 1]: "
item_sku, quantity = get_sku_and_quantity(sku_prompt, quantity_prompt)
modify_cart(item_sku, quantity)
elif response == "4":
view_cart()
elif response == "5":
checkout()
ordering = False
elif response == "6":
print("Goodbye!")
ordering = False
else:
print("You have entered an invalid action number. Please try again.")
The ordering = True flag controls the while loop. As long as it's True, the loop keeps running and presenting the menu of actions. When the user checks out (response "5") or exits (response "6"), ordering is set to False and the loop ends.
Every action branch calls a function we already wrote. The loop itself is almost entirely function calls — which is exactly the point. The individual functions handle the details; the loop handles the flow.
Run the app with:
order_loop()
Welcome to the Hungry Hare!
****Ordering Actions****
(1) Add a new menu item to cart
(2) Remove an item from the cart
(3) Modify a cart item's quantity
(4) View cart
(5) Checkout
(6) Exit
Please enter the number of the action you want to take:
Learning Insight: The
whileloop with a boolean flag is a common pattern for interactive applications. It keeps the experience running until the user explicitly signals they're done. Without theordering = Falselines, the loop would run forever. Making sure every loop has a clear exit condition is one of the first habits to build as a programmer.
What We Built
Starting with a blank notebook, we built a complete interactive food ordering app using nothing but Python fundamentals. The full architecture looks like this:
Global data layer: menu, cart, app_actions, RESTAURANT_NAME, SALES_TAX_RATE
Function layer: display_menu(), add_to_cart(), remove_from_cart(), modify_cart(), view_cart(), checkout(), get_sku_and_quantity()
Application loop: order_loop() — the entry point that ties every function together
Each function handles one thing. Each layer depends on the one below it. This structure makes the app easy to test, easy to extend, and easy to explain.
Next Steps
Add an inventory command. Right now there's no way to view your cart without going through the action menu. A quick view_cart() call added as action "7" would be a simple addition.
Complete the modify_cart logic. The solution notebook includes a placeholder for this function, making it a great opportunity to practice on your own. The logic is very similar to add_to_cart and remove_from_cart, so try implementing it using what you've already written as a guide.
Add a clear cart option. A function that resets cart = {} and prints a confirmation message is a small but satisfying addition.
Reduce repetition. Notice that display_menu() gets called at the top of each order action in the main loop. That repeated call could be factored out to avoid duplication.
Build a front end. This application is the backend — the logic that powers the experience. If you want to give it a visual interface, Streamlit or Gradio are beginner-friendly options that let you wrap Python apps in a simple web UI without knowing HTML or JavaScript.
Resources
- Project in the Dataquest app
- Solution notebook on GitHub
- Python Basics for Data Analysis path
- Dataquest Community Forum
Share your version in the community and tag @Anna_strahl. If you extended the app with new features, added a front end, or connected it to a database, those extensions are exactly the kind of additions that make a project portfolio-worthy.
from Dataquest https://ift.tt/k9CWXVf
via RiYo Analytics

No comments