thecodex.expert · The Codex Family of Knowledge
Tier 1 · Beginner · Python Project

To-Do List

Add, view, and remove tasks — and save them so they are still there next time. Your first app that remembers things between runs.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a to-do list you can actually use: add tasks, see them numbered, remove the ones you finish, and — crucially — have them saved to a file so they survive after you close the program. It teaches lists, a menu loop, and saving data to disk.

Where this shows up: every app that stores your stuff — notes, reminders, shopping lists, saved games, settings. The pattern of “keep a list in memory, save it to a file, load it back next time” is the simplest form of a database.

2 How to Think About It

Think about the loop the program lives in, before any code:

The plan — in plain English
1. Load any saved tasks from a file when the program starts. → 2. Show a menu (add / view / remove / quit) and repeat until they quit. → 3. Each choice changes the list. → 4. Save the list back to the file whenever it changes, so nothing is lost.

Add

View

Remove

Quit

Load tasks from file

Show menu

Choice?

Add task

Show tasks

Remove task

Stop

Save to file

3 The Build — explained part by part

Here is the complete to-do list. Each part is explained below.

Pythontodo.py
import json
import os

FILE = "tasks.json"

def load_tasks():
    # If a saved file exists, read the tasks from it; otherwise start empty.
    if os.path.exists(FILE):
        with open(FILE) as f:
            return json.load(f)
    return []

def save_tasks(tasks):
    # Write the tasks to the file so they survive after the program closes.
    with open(FILE, "w") as f:
        json.dump(tasks, f)

tasks = load_tasks()

while True:
    print("\n1. Add  2. View  3. Remove  4. Quit")
    choice = input("Choose: ")

    if choice == "1":
        task = input("New task: ")
        tasks.append(task)
        save_tasks(tasks)
    elif choice == "2":
        for i, task in enumerate(tasks, 1):
            print(f"{i}. {task}")
    elif choice == "3":
        number = int(input("Remove which number? "))
        if 1 <= number <= len(tasks):
            tasks.pop(number - 1)
            save_tasks(tasks)
    elif choice == &quot;4&quot;:
        break
    else:
        print(&quot;Please choose 1 to 4.&quot;)
What each part does — in plain words
import json, osjson turns our list into text we can save and back again; os lets us check if the save file exists.

load_tasks() — if a saved file exists, read the tasks from it; if not, start with an empty list. This is what makes tasks survive between runs.

save_tasks(tasks) — write the current list to the file as JSON text. We call this every time the list changes.

while True: — the menu loop. It keeps showing the menu until the user chooses Quit (which breaks out).

tasks.append(task) — add a new task to the end of the list. tasks.pop(number - 1) removes one (we subtract 1 because people count from 1 but lists count from 0).

enumerate(tasks, 1) — walk the list while numbering each item, starting at 1, so the display reads 1, 2, 3.
Common mistakes — and how to avoid them
✗ Forgetting to call save_tasks after a change — tasks vanish when you quit.
✓ Save right after every add and remove.
✗ Using tasks.pop(number) instead of number - 1 — removes the wrong task.
✓ Lists count from 0 but people count from 1, so subtract 1.
✗ Not checking the number is in range — removing item 9 of a 2-item list crashes.
✓ Guard with if 1 <= number <= len(tasks).

4 Test & Prove Each Part

We test the list operations — adding and removing — directly, without needing the menu or a person typing.

Adding a task puts it in the list
Removing a task takes it out
Removing an out-of-range number does nothing bad
Pythontest_todo.py
def add_task(tasks, task):
    tasks.append(task)
    return tasks


def remove_task(tasks, number):
    if 1 <= number <= len(tasks):
        tasks.pop(number - 1)
    return tasks


def test_add():
    &quot;&quot;&quot;Adding a task puts it in the list.&quot;&quot;&quot;
    assert add_task([], &quot;Buy milk&quot;) == [&quot;Buy milk&quot;]


def test_remove():
    &quot;&quot;&quot;Removing task 1 takes the first item out.&quot;&quot;&quot;
    assert remove_task([&quot;a&quot;, &quot;b&quot;], 1) == [&quot;b&quot;]


def test_remove_bad_number():
    &quot;&quot;&quot;Removing a number that does not exist leaves the list unchanged.&quot;&quot;&quot;
    assert remove_task([&quot;a&quot;], 9) == [&quot;a&quot;]

Run with pytest -v. The list logic lives in small functions so tests check it directly. The bad-number test proves the program will not crash on a wrong input.

5 The Interface

INPUTmenu choice1-4, then task text or number
What it expects
Choose: 1
New task: Walk the dog
OUTPUTtask listnumbered, saved to tasks.json
What it returns
1. Walk the dog
2. Buy milk

6 Run It & Automate It

Run it locally
python3 todo.py
Add a few tasks, quit, run it again — your tasks are still there (saved in tasks.json).

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
1. Add  2. View  3. Remove  4. Quit
Choose: 1
New task: Walk the dog

1. Add  2. View  3. Remove  4. Quit
Choose: 2
1. Walk the dog
If it breaks — how to fix it
🚨 Tasks disappear after quitting.
You are not saving. Call save_tasks(tasks) after each change.
🚨 json.decoder.JSONDecodeError on start.
The save file is empty or corrupted. Delete tasks.json and start fresh.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage(&#x27;Get the code&#x27;) { steps { checkout scm } }       // download the code
        stage(&#x27;Install&#x27;) { steps { sh &#x27;python3 -m venv venv && . venv/bin/activate && pip install pytest&#x27; } }  // test tool
        stage(&#x27;Test&#x27;) { steps { sh &#x27;. venv/bin/activate && pytest -v&#x27; } }   // run every test
    }
    post {
        success { echo &#x27;All tests passed.&#x27; }
        failure { echo &#x27;A test failed — look above.&#x27; }
    }
}
🎯 Try this next — make it yours
  1. Mark as done. Add a “done” flag per task instead of removing. (Teaches: dictionaries.)
  2. Due dates. Store a date with each task. (Teaches: more structured data.)
  3. Sort tasks. Show them alphabetically or by date. (Teaches: sorting.)
What you learned
You learned lists (add/remove), a menu loop, and — the big one — saving data to a file with JSON so it survives between runs. That is the seed of every database. Related: json, File I/O.