Project 2~45 minutesGuided Practice

Task Tracker

Interview Scenario: "Build a task management app. Users should be able to add tasks, mark them complete, and delete them. Store everything in a database."

What You'll Learn

  • Full CRUD operations (Create, Read, Update, Delete)
  • Pydantic models for request validation
  • In-memory data storage with Python lists
  • List rendering in React with proper keys
  • Optimistic UI updates
Loading...

Interview Mode: Simplified in-memory storage. Perfect for live coding.

Step 1: Backend Setup

Let's create a FastAPI backend with full CRUD operations. No database needed - we'll store tasks in memory. This is perfect for interviews where you need to move fast.

backend/main.py
1from fastapi import FastAPI, HTTPException
2from fastapi.middleware.cors import CORSMiddleware
3from pydantic import BaseModel
4
5app = FastAPI()
6
7app.add_middleware(
8 CORSMiddleware,
9 allow_origins=["http://localhost:3000"],
10 allow_credentials=True,
11 allow_methods=["*"],
12 allow_headers=["*"],
13)
14
15# In-memory storage (resets when server restarts)
16tasks = []
17next_id = 1
18
19class Task(BaseModel):
20 title: str
21
22class TaskUpdate(BaseModel):
23 title: str | None = None
24 completed: bool | None = None
25
26@app.get("/api/tasks")
27def get_tasks():
28 return tasks
29
30@app.post("/api/tasks")
31def create_task(task: Task):
32 global next_id
33 new_task = {"id": next_id, "title": task.title, "completed": False}
34 tasks.append(new_task)
35 next_id += 1
36 return new_task
37
38@app.patch("/api/tasks/{task_id}")
39def update_task(task_id: int, task: TaskUpdate):
40 for t in tasks:
41 if t["id"] == task_id:
42 if task.title is not None:
43 t["title"] = task.title
44 if task.completed is not None:
45 t["completed"] = task.completed
46 return t
47 raise HTTPException(status_code=404, detail="Task not found")
48
49@app.delete("/api/tasks/{task_id}")
50def delete_task(task_id: int):
51 global tasks
52 tasks = [t for t in tasks if t["id"] != task_id]
53 return {"message": "Task deleted"}

Why no database?

In a 45-minute interview, setting up SQLAlchemy can eat 15+ minutes. Using a simple list demonstrates the same CRUD concepts without the boilerplate. You can always mention: "In production, I'd use a database like PostgreSQL with SQLAlchemy."

The trade-off

This in-memory approach resets when the server restarts. That's fine for interviews! Switch to Production Mode above to see the full database setup.

Step 2: Challenge - Build the Frontend

Your Challenge

Before looking at the solution, try to build the frontend yourself. Create a page that:

  1. Fetches and displays all tasks on page load
  2. Has an input + button to add new tasks
  3. Shows a checkbox to toggle completion
  4. Has a delete button for each task

Spend 15-20 minutes attempting this before looking at the solution.

Show Solution
frontend/app/page.tsx
1"use client"
2
3import { useState, useEffect } from "react"
4
5interface Task {
6 id: number
7 title: string
8 completed: boolean
9}
10
11const API_URL = "http://localhost:8000/api"
12
13export default function TaskTracker() {
14 const [tasks, setTasks] = useState<Task[]>([])
15 const [newTask, setNewTask] = useState("")
16 const [loading, setLoading] = useState(true)
17
18 // Fetch all tasks
19 useEffect(() => {
20 fetchTasks()
21 }, [])
22
23 const fetchTasks = async () => {
24 const res = await fetch(`${API_URL}/tasks`)
25 const data = await res.json()
26 setTasks(data)
27 setLoading(false)
28 }
29
30 // Add new task
31 const addTask = async (e: React.FormEvent) => {
32 e.preventDefault()
33 if (!newTask.trim()) return
34
35 const res = await fetch(`${API_URL}/tasks`, {
36 method: "POST",
37 headers: { "Content-Type": "application/json" },
38 body: JSON.stringify({ title: newTask }),
39 })
40 const task = await res.json()
41 setTasks([...tasks, task]) // Add to local state
42 setNewTask("") // Clear input
43 }
44
45 // Toggle completion
46 const toggleTask = async (task: Task) => {
47 const res = await fetch(`${API_URL}/tasks/${task.id}`, {
48 method: "PATCH",
49 headers: { "Content-Type": "application/json" },
50 body: JSON.stringify({ completed: !task.completed }),
51 })
52 const updated = await res.json()
53 setTasks(tasks.map(t => t.id === task.id ? updated : t))
54 }
55
56 // Delete task
57 const deleteTask = async (id: number) => {
58 await fetch(`${API_URL}/tasks/${id}`, { method: "DELETE" })
59 setTasks(tasks.filter(t => t.id !== id))
60 }
61
62 if (loading) return <p className="p-8">Loading...</p>
63
64 return (
65 <main className="min-h-screen p-8 max-w-2xl mx-auto">
66 <h1 className="text-3xl font-bold mb-6">Task Tracker</h1>
67
68 <form onSubmit={addTask} className="flex gap-2 mb-6">
69 <input
70 type="text"
71 value={newTask}
72 onChange={(e) => setNewTask(e.target.value)}
73 placeholder="Add a new task..."
74 className="flex-1 border p-2 rounded"
75 />
76 <button
77 type="submit"
78 className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
79 >
80 Add
81 </button>
82 </form>
83
84 <ul className="space-y-2">
85 {tasks.map((task) => (
86 <li
87 key={task.id}
88 className="flex items-center gap-3 p-3 bg-gray-50 rounded"
89 >
90 <input
91 type="checkbox"
92 checked={task.completed}
93 onChange={() => toggleTask(task)}
94 className="w-5 h-5"
95 />
96 <span className={task.completed ? "line-through text-gray-400 flex-1" : "flex-1"}>
97 {task.title}
98 </span>
99 <button
100 onClick={() => deleteTask(task.id)}
101 className="text-red-500 hover:text-red-700"
102 >
103 Delete
104 </button>
105 </li>
106 ))}
107 </ul>
108
109 {tasks.length === 0 && (
110 <p className="text-gray-500 text-center">No tasks yet. Add one above!</p>
111 )}
112 </main>
113 )
114}

Key Patterns

Optimistic UI Updates

Notice how we update local state immediately after API calls:setTasks([...tasks, task])Instead of re-fetching everything. This makes the UI feel instant.

Form Submission Pattern

e.preventDefault() stops the page from reloading. This is critical for single-page apps. Interviewers often test if you know this - forgetting it is a common mistake.

The .map() Pattern with Keys

{tasks.map(task => ...)} is how you render lists in React. Always include a unique key prop (key={task.id}). React uses this to efficiently update the DOM. Missing keys is a common interview gotcha.

Exercises

What You Learned

FastAPI Basics

  • In-memory data storage with lists
  • Full CRUD endpoints (GET, POST, PATCH, DELETE)
  • Pydantic schemas for validation
  • HTTPException for errors

React Patterns

  • List rendering with .map() and keys
  • Form submission with preventDefault
  • Optimistic UI updates
  • Conditional styling

Time for a Solo Challenge

Project 3 is a Notes App that you'll build entirely on your own, using everything you've learned. Requirements only - no code provided.

Project 3: Notes App