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, HTTPException2from fastapi.middleware.cors import CORSMiddleware3from pydantic import BaseModel45app = FastAPI()67app.add_middleware(8 CORSMiddleware,9 allow_origins=["http://localhost:3000"],10 allow_credentials=True,11 allow_methods=["*"],12 allow_headers=["*"],13)1415# In-memory storage (resets when server restarts)16tasks = []17next_id = 11819class Task(BaseModel):20 title: str2122class TaskUpdate(BaseModel):23 title: str | None = None24 completed: bool | None = None2526@app.get("/api/tasks")27def get_tasks():28 return tasks2930@app.post("/api/tasks")31def create_task(task: Task):32 global next_id33 new_task = {"id": next_id, "title": task.title, "completed": False}34 tasks.append(new_task)35 next_id += 136 return new_task3738@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.title44 if task.completed is not None:45 t["completed"] = task.completed46 return t47 raise HTTPException(status_code=404, detail="Task not found")4849@app.delete("/api/tasks/{task_id}")50def delete_task(task_id: int):51 global tasks52 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:
- Fetches and displays all tasks on page load
- Has an input + button to add new tasks
- Shows a checkbox to toggle completion
- 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"23import { useState, useEffect } from "react"45interface Task {6 id: number7 title: string8 completed: boolean9}1011const API_URL = "http://localhost:8000/api"1213export default function TaskTracker() {14 const [tasks, setTasks] = useState<Task[]>([])15 const [newTask, setNewTask] = useState("")16 const [loading, setLoading] = useState(true)1718 // Fetch all tasks19 useEffect(() => {20 fetchTasks()21 }, [])2223 const fetchTasks = async () => {24 const res = await fetch(`${API_URL}/tasks`)25 const data = await res.json()26 setTasks(data)27 setLoading(false)28 }2930 // Add new task31 const addTask = async (e: React.FormEvent) => {32 e.preventDefault()33 if (!newTask.trim()) return3435 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 state42 setNewTask("") // Clear input43 }4445 // Toggle completion46 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 }5556 // Delete task57 const deleteTask = async (id: number) => {58 await fetch(`${API_URL}/tasks/${id}`, { method: "DELETE" })59 setTasks(tasks.filter(t => t.id !== id))60 }6162 if (loading) return <p className="p-8">Loading...</p>6364 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>6768 <form onSubmit={addTask} className="flex gap-2 mb-6">69 <input70 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 <button77 type="submit"78 className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"79 >80 Add81 </button>82 </form>8384 <ul className="space-y-2">85 {tasks.map((task) => (86 <li87 key={task.id}88 className="flex items-center gap-3 p-3 bg-gray-50 rounded"89 >90 <input91 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 <button100 onClick={() => deleteTask(task.id)}101 className="text-red-500 hover:text-red-700"102 >103 Delete104 </button>105 </li>106 ))}107 </ul>108109 {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