Saving to a File
Last phase ended on a sour note: the moment the program stops, your tasks are gone. Memory is temporary by design. To make a to-do app worth using, the list has to outlive a single run - you add a task today and it's still there tomorrow. That means writing it to a file and reading it back. Let's do exactly that.
Why JSON
Our task list is a list of dicts. We need to turn that into text we can save, then turn the text back into a list of dicts later. Python has a module built for precisely this: json.
JSON is a text format that looks almost identical to Python lists and dicts. That's not a coincidence - it was designed to carry data like ours. The json module gives us two pairs of functions:
| Function | Direction | What it does |
|---|---|---|
json.dumps |
object → text | turns a list/dict into a JSON string |
json.loads |
text → object | turns a JSON string back into a list/dict |
json.dump |
object → file | writes a list/dict straight to an open file |
json.load |
text → file | reads a list/dict straight from an open file |
The ones without the s work with files; the ones with the s work with strings (s for string). We'll use both.
Seeing the conversion
Before touching files, let's watch the round trip in pure memory. Take a list of tasks, turn it into a JSON string, then turn that string back into Python:
=
=
=
Run it. The middle is a clean JSON string - indent=2 makes it readable with line breaks and spacing instead of one long line. Then json.loads reads that string and hands you back a real Python list you can index into. Out and back, no data lost. False came back as False, the text came back as text. That round trip is the whole idea behind saving.
Writing to a file and reading it back
Now the real thing. In your browser, we'll use a temporary file so the code runs in isolation - and the file path stays the same when you move to your own machine.
# A file path. On your machine this would simply be "tasks.json".
=
=
# Save: open the file for writing, dump the list into it.
# Load: open the file for reading, load the list back out.
=
The with open(...) block opens the file and closes it automatically when the block ends, even if something goes wrong inside - that's why we use with rather than opening and closing by hand. The "w" means write (and replace whatever was there); "r" means read.
This block proves the loop that matters: data went to disk, the program could have ended right there, and we still read every task back. That's persistence.
The first-run problem
There's a trap waiting. The very first time someone runs the app, tasks.json doesn't exist yet. Try to open a missing file for reading and Python raises FileNotFoundError and crashes. Not the welcome we want.
The fix is to expect it: if the file isn't there, start with an empty list. Let's wrap loading in a function that handles both the happy path and the first run.
=
return
return
# Make sure we're starting fresh for this demo.
# First run: no file yet, so we get an empty list - no crash.
=
# Add one and save.
# Second run: load again, the task is there.
=
Look at the two prints. The first is [] - an empty list, because the file didn't exist and load_tasks caught the FileNotFoundError and returned [] instead of crashing. Then we add a task, save, and "run again" by calling load_tasks a second time. This time the task comes back. That's the full life cycle of saved data, handled cleanly.
The try/except here is the kind of error handling worth keeping. It's not guarding against something impossible - a missing file on first run is guaranteed to happen. Catching it turns a crash into a sensible default.
Where we are
You've got load_tasks and save_tasks, and together they make the list permanent. Add a task, save, quit, come back, load - it's still there. These two functions are the storage layer of the app, and we won't change them again. On your own machine you'd point path at "tasks.json" and it would behave exactly as you saw here.
Next we make the list do more than grow: marking tasks done, deleting them, and filtering open from finished.