Lesson 8 of 34 · 0 of 1 exercises solved
Mini Project: CLI Todo App
In this project you build a command-line todo app that saves tasks to a file. It brings together everything from the earlier lessons.
We'll build a todo app that persists tasks to a file. It will support adding, listing, completing, and deleting tasks. This uses file I/O, JSON, , and all the other concepts from previous lessons.
import json
import os
import sys
from datetime import datetime
TODO_FILE = "todos.json"
def load_todos():
if not os.path.exists(TODO_FILE):
return []
with open(TODO_FILE, "r") as f:
return json.load(f)
def save_todos(todos):
with open(TODO_FILE, "w") as f:
json.dump(todos, f, indent=2)
def add_todo(text):
todos = load_todos()
todo = {
"id": len(todos) + 1,
"text": text,
"done": False,
"created": datetime.now().isoformat()
}
todos.append(todo)
save_todos(todos)
print(f"Added: {text}")
def list_todos():
todos = load_todos()
if not todos:
print("No todos yet. Add some!")
return
for todo in todos:
status = "✓" if todo["done"] else "○"
print(f"[{todo['id']}] {status} {todo['text']}")
def complete_todo(todo_id):
todos = load_todos()
for todo in todos:
if todo["id"] == todo_id:
todo["done"] = True
save_todos(todos)
print(f"Completed: {todo['text']}")
return
print(f"No todo with id {todo_id}")
def delete_todo(todo_id):
todos = load_todos()
todos = [t for t in todos if t["id"] != todo_id]
save_todos(todos)
print(f"Deleted todo {todo_id}")
def main():
if len(sys.argv) < 2:
print("Usage: python todo.py [add|list|done|delete] [args]")
return
command = sys.argv[1]
if command == "add" and len(sys.argv) > 2:
add_todo(" ".join(sys.argv[2:]))
elif command == "list":
list_todos()
elif command == "done" and len(sys.argv) > 2:
complete_todo(int(sys.argv[2]))
elif command == "delete" and len(sys.argv) > 2:
delete_todo(int(sys.argv[2]))
else:
print("Unknown command or missing arguments")
if __name__ == "__main__":
main()Save this as todo.py and try it out. The if __name__ == "__main__": pattern means the main() function only runs when you execute the file directly, not when it's imported as a — a crucial Python convention.
python todo.py add "Buy groceries"
python todo.py add "Write code"
python todo.py list
python todo.py done 1
python todo.py list
python todo.py delete 2Common question
Should a beginner build a whole program before mastering the parts?
A project is where the parts come together. Functions get callers, data structures get data, handling gets errors, and file I/O gets a reason to exist. The to-do app is small enough to finish in one sitting but shaped like real software: a that reads commands, hands them to functions, and keeps state on disk between runs.
Build it in . First print a hard-coded ; then add tasks in ; then save and load; then complete and delete. Run it after every slice. When it works, extend it in a direction you care about (due dates, priorities, colours), because the extension you actually want is the one you will finish.
Source: The C Programming Language by Kernighan and Ritchie ends almost every section with exercises that ask you to write a small working program.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
You should learn every feature first and combine them later; building early just locks in bad habits.
A to-do is too simple to teach anything.
Storing tasks in a JSON file is unprofessional; real programs use .
Practice
The todo app stores tasks as dictionaries. Write two helpers for it: complete_task(tasks, index) marks the task at that 1-based position as done and returns True, or returns False if the index is out of range; pending(tasks) returns a list of the titles that are not yet done.
Todo helpers
tasks = [
{"title": "Read lesson 8", "done": False},
{"title": "Write FizzBuzz", "done": True},
{"title": "Push to GitHub", "done": False},
]
def complete_task(tasks, index):
...
def pending(tasks):
...
complete_task(tasks, 1)
print(pending(tasks))
Press Run to try your code, then Check answer to grade it.Tip: use the ← and → keys to move between lessons.