# Tasks in Memory

Before a to-do app can save anything or take commands, it needs one thing: a way to hold a task in the program's memory. Get the shape of the data right and everything after this gets shorter. So that's where we start - no files, no commands yet, only tasks living in a list while the program runs.

## What is a task, really?

A task isn't one value. It has a few parts: the text of the thing to do, whether it's finished, and an id so we can point at it later ("mark task 2 done"). When you have a bundle of named values like that, a Python **dictionary** is the natural fit.

Here's one task as a dict:

```python runnable
task = {"id": 1, "text": "buy milk", "done": False}
print(task)
print("The text is:", task["text"])
print("Done yet?", task["done"])
```

Run that. You get the whole dict, then two values pulled out by name. The keys - `id`, `text`, `done` - are how we reach inside. That's the entire data model for one task. No class, no library. A dict is plenty.

## Many tasks: a list of dicts

One task is a dict. A to-do **list** is, fittingly, a Python list of those dicts:

Before you run this one, guess how many lines it prints. Then check.

```python runnable
tasks = [
    {"id": 1, "text": "buy milk", "done": False},
    {"id": 2, "text": "call the bank", "done": False},
    {"id": 3, "text": "water the plants", "done": True},
]

print("You have", len(tasks), "tasks.")
for task in tasks:
    print(task["id"], "-", task["text"])
```

A list of dictionaries is one of the most common shapes in all of Python. Rows from a database, items in a shopping cart, results from an API - they almost always arrive looking like this. Learn it here and you'll recognize it everywhere.

## Adding a task

Right now we typed the tasks by hand. The app needs to *add* them on demand. We'll write a function that takes the current list and the new text, and appends a fresh dict to the end.

The one wrinkle is the id. Each task needs a number nobody else has, and the first task added to an empty list should get id 1.

**Your turn.** This function is the point of the phase, so have a go before you read on. Fill it in and hit Run: the checks underneath tell you whether it works. My version is in the next block whenever you want it.

```python runnable
def add_task(tasks, text):
    # Append one new task dict to `tasks`. It needs three keys:
    #   "id"   - a number no other task in the list has
    #   "text" - the text passed in
    #   "done" - False, because a new task isn't finished
    # Adding to an empty list should produce id 1.
    pass


# --- checks: fix your function until this prints "All good." ---
tasks = []
add_task(tasks, "buy milk")
assert tasks == [{"id": 1, "text": "buy milk", "done": False}], f"after one add, got: {tasks}"

add_task(tasks, "call the bank")
assert tasks[1]["id"] == 2, f"the second task should get id 2, got: {tasks[1]}"

ids = [task["id"] for task in tasks]
assert len(ids) == len(set(ids)), f"ids must be unique, got: {ids}"
print("All good.")
```

Stuck on the id? Ask what the ids already in the list can tell you.

### One way to write it

```python runnable
def add_task(tasks, text):
    if tasks:
        new_id = max(task["id"] for task in tasks) + 1
    else:
        new_id = 1
    tasks.append({"id": new_id, "text": text, "done": False})

tasks = []
add_task(tasks, "buy milk")
add_task(tasks, "call the bank")
add_task(tasks, "water the plants")

for task in tasks:
    print(task["id"], "-", task["text"])
```

Walk through it. We start with an empty list. The first `add_task` sees no tasks, so `new_id` is 1. The next sees a max id of 1, so it picks 2. Then 3. The list grew from nothing to three tasks, each with its own id, and we never had to track a counter ourselves.

If you reached for `len(tasks) + 1`, that is a reasonable first instinct and it passes every check above. It breaks in Phase 3, when we add deleting. Delete task 2 from a list of three and `len` is 2, so `len + 1` hands the next task an id of 3 - which already exists. Reading the actual max id keeps every id unique no matter what you've removed. It's a small choice now that saves a real bug later.

## Listing what you have

Adding is half of it. The other half is showing the list back in a way a human wants to read. Let's make a `list_tasks` function that prints each task with its id and a marker for whether it's done.

Two tasks go in and one gets marked done. Before you run it: which line comes back with the `x`?

```python runnable
def add_task(tasks, text):
    new_id = max((task["id"] for task in tasks), default=0) + 1
    tasks.append({"id": new_id, "text": text, "done": False})

def list_tasks(tasks):
    if not tasks:
        print("No tasks yet. Add one!")
        return
    for task in tasks:
        mark = "x" if task["done"] else " "
        print(f"[{mark}] {task['id']}. {task['text']}")

tasks = []
add_task(tasks, "buy milk")
add_task(tasks, "call the bank")
tasks[0]["done"] = True   # pretend we finished the first one

list_tasks(tasks)
```

A couple of things to notice. We tightened `add_task` using `max(..., default=0)` - the `default` kicks in when the list is empty, so the `if/else` disappears and the function is one line of logic. Same behavior, less code.

In `list_tasks`, the `mark` line is a small conditional: `"x"` if the task is done, a space if not. The `f"..."` string drops the values straight into the text. The result reads like an actual checklist:

```
[x] 1. buy milk
[ ] 2. call the bank
```

That bracket-and-number format is something you can scan in a real terminal. We set `tasks[0]["done"] = True` by hand here only to show the marker working - in Phase 3 we'll write a proper function for it.

## Where we are

You now have the spine of the app: a task is a dict, the list is a list of dicts, `add_task` grows it, and `list_tasks` shows it. Everything from here builds on this shape.

The catch - and you may have felt it - is that the moment the program ends, the list vanishes. Run it again and you're back to empty. That's the next problem to solve: making your tasks stick around. On to saving them to a file.
