Skip to main content

📐 Lesson 9.1: Project Planning and Design

You've learned variables, control flow, functions, collections, classes, error handling, file I/O, JSON, and APIs. That's a lot of tools in your toolbox. Now it's time to use them together. But before you write a single line of code, you need a plan. Professional developers spend more time thinking about what to build than actually building it — and that planning is exactly what separates working software from frustrating spaghetti code.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Choose a project that exercises the skills you've learned
  • Write clear requirements — what the program should do, step by step
  • Break a problem into tasks using top-down decomposition
  • Write pseudocode before writing real code
  • Plan your data model — what classes, dictionaries, and files you'll need
  • Design your architecture — how files, functions, and classes fit together
  • Create a project checklist to track your progress

Estimated Time: 60 minutes

Deliverable: A complete project plan ready for Lesson 26

📑 In This Lesson

Why Plan Before You Code?

It's tempting to jump straight into coding — but that almost always leads to problems. Here's what typically happens without a plan:

flowchart TD A["Start coding immediately"] --> B["Realize you need a feature
you didn't think about"] B --> C["Hack it in with messy code"] C --> D["New feature breaks
existing code"] D --> E["Spend hours debugging"] E --> F["Start over from scratch"] style A fill:#ef4444,color:#fff style B fill:#f59e0b,color:#fff style C fill:#f59e0b,color:#fff style D fill:#ef4444,color:#fff style E fill:#ef4444,color:#fff style F fill:#ef4444,color:#fff

And here's what happens with a plan:

flowchart TD A["Write requirements"] --> B["Break into tasks"] B --> C["Write pseudocode"] C --> D["Build one piece at a time"] D --> E["Test each piece"] E --> F["Working program!"] style A fill:#3b82f6,color:#fff style B fill:#3b82f6,color:#fff style C fill:#6366f1,color:#fff style D fill:#22c55e,color:#fff style E fill:#22c55e,color:#fff style F fill:#22c55e,color:#fff

Planning doesn't mean writing a 50-page document. For a beginner project, a plan might be a single page with a list of features, a rough sketch of your data, and some pseudocode. The goal is to think through the hard parts before you're knee-deep in syntax errors.

💡 The 80/20 Rule of Planning

Spending 20% of your time planning can save you 80% of debugging time. Most bugs come from unclear thinking, not typos. If you can explain your program in plain English, translating it to code becomes much easier.

🎓 Instructor Note: Delivery Guidance

Ask students: "How many of you have ever started a project, gotten halfway through, and realized you needed to start over?" Most will raise their hands. That's the hook for why planning matters. Emphasize that professional developers at companies like Google and Microsoft spend significant time in design and planning before writing code. A whiteboard session or a simple text document is all they need at this stage. The flowcharts above are great for visual learners — the "without a plan" path resonates because students have lived it.

Choosing Your Project

Your capstone project should use at least four of these skill areas from the course:

Skill Area Modules Example Usage
Variables & Data Types Module 2 Store user settings, scores, names
Control Flow Module 3 Menu systems, game logic, validation
Functions Module 4 Organize code into reusable pieces
Collections Module 5 Lists of items, lookup tables, inventories
OOP (Classes) Module 6 Model real-world things (contacts, tasks, items)
Error Handling Module 7 Validate input, handle missing files
File I/O & JSON Module 8 Save/load data between sessions
APIs Module 8 Fetch live data from the internet

Three Project Options

Pick one of these projects — or design your own that covers at least four skill areas. Each project is designed to be completable in about 90 minutes (Lesson 26).

📋 Option A: Personal Contact Manager

A command-line app that stores, searches, and manages contacts — like a simple address book.

Skills used: Classes (Contact), Collections (list of contacts), File I/O (save/load JSON), Error Handling (input validation), Functions, Control Flow (menu system)

Features:

  • Add new contacts (name, email, phone, category)
  • Search contacts by name or category
  • Edit and delete contacts
  • Save contacts to a JSON file so they persist
  • Display contacts in a formatted table

🌦️ Option B: Weather Journal

A command-line app that fetches the current weather, lets you write a journal entry about your day, and saves both together.

Skills used: APIs (weather data), Classes (JournalEntry), File I/O (save/load JSON), Collections (list of entries), Error Handling (API failures), Functions

Features:

  • Fetch current weather for any city (using wttr.in)
  • Write a journal entry tagged with the weather
  • View past entries filtered by date or weather
  • Save all entries to a JSON file
  • Show statistics (entries per weather type, total entries)

🎮 Option C: Quiz Game Engine

A command-line trivia game that pulls questions from an API or a local file, tracks scores, and maintains a leaderboard.

Skills used: APIs (Open Trivia DB), Classes (Player, Question), File I/O (leaderboard JSON), Collections (question lists, score tracking), Error Handling, Control Flow (game loop)

Features:

  • Fetch questions from the Open Trivia Database API
  • Fall back to a local JSON file if the API is unavailable
  • Multiple-choice questions with shuffled answers
  • Track player score with a running tally
  • Save high scores to a leaderboard file
  • Display the top 10 scores
🎓 Instructor Note: Delivery Guidance

Let students pick whichever project excites them most — enthusiasm matters more than difficulty. Option A (Contact Manager) is the most straightforward and is a good default for students who feel unsure. Option B (Weather Journal) appeals to creative students and has the API component. Option C (Quiz Game) is the most fun and has the richest feature set. If a student has their own project idea, that's great — just verify it covers at least four skill areas and is scoped to ~90 minutes of building. Help students avoid scope creep: "A working Contact Manager with 5 features beats a half-finished RPG."

Writing Requirements

A requirement is a clear statement of what your program should do. Good requirements are specific, testable, and written from the user's perspective.

❌ Vague Requirement ✅ Clear Requirement
"The app manages contacts" "The user can add a contact with name, email, phone, and category"
"It saves stuff" "All contacts are saved to contacts.json when the user exits"
"Handle errors" "If the user enters an invalid email format, show an error and ask again"
"Search works" "The user can search contacts by name (case-insensitive partial match)"

Example: Contact Manager Requirements

# Contact Manager — Requirements
# ================================
# Language: Python
#
# 1. CORE FEATURES
#    a. Add a new contact (name, email, phone, category)
#    b. View all contacts in a formatted table
#    c. Search contacts by name (case-insensitive, partial match)
#    d. Search contacts by category
#    e. Edit an existing contact's fields
#    f. Delete a contact (with confirmation)
#
# 2. DATA PERSISTENCE
#    a. Save all contacts to contacts.json on exit
#    b. Load contacts from contacts.json on startup
#    c. If contacts.json doesn't exist, start with empty list
#
# 3. USER INTERFACE
#    a. Menu-driven: show numbered options, accept user choice
#    b. Clear prompts for each action
#    c. "Press Enter to continue" after viewing results
#
# 4. ERROR HANDLING
#    a. Invalid menu choice → show error, redisplay menu
#    b. Empty name → reject, ask again
#    c. Duplicate name → warn but allow (people can share names)
#    d. File read error → start with empty list, warn user
#
# 5. NICE-TO-HAVE (if time permits)
#    a. Export contacts to CSV
#    b. Sort contacts by name or category
#    c. Count contacts per category
// Contact Manager — Requirements
// ================================
// Language: JavaScript (Node.js)
//
// 1. CORE FEATURES
//    a. Add a new contact (name, email, phone, category)
//    b. View all contacts in a formatted table
//    c. Search contacts by name (case-insensitive, partial match)
//    d. Search contacts by category
//    e. Edit an existing contact's fields
//    f. Delete a contact (with confirmation)
//
// 2. DATA PERSISTENCE
//    a. Save all contacts to contacts.json on exit
//    b. Load contacts from contacts.json on startup
//    c. If contacts.json doesn't exist, start with empty list
//
// 3. USER INTERFACE
//    a. Menu-driven: show numbered options, accept user choice
//    b. Clear prompts for each action
//    c. Use readline for all user input
//
// 4. ERROR HANDLING
//    a. Invalid menu choice → show error, redisplay menu
//    b. Empty name → reject, ask again
//    c. File read error → start with empty list, warn user
//    d. JSON parse error → start fresh, warn user
//
// 5. NICE-TO-HAVE (if time permits)
//    a. Export contacts to CSV
//    b. Sort contacts by name or category
//    c. Count contacts per category
// Contact Manager — Requirements
// ================================
// Language: C# (.NET)
//
// 1. CORE FEATURES
//    a. Add a new contact (name, email, phone, category)
//    b. View all contacts in a formatted table
//    c. Search contacts by name (case-insensitive, partial match)
//    d. Search contacts by category
//    e. Edit an existing contact's fields
//    f. Delete a contact (with confirmation)
//
// 2. DATA PERSISTENCE
//    a. Save all contacts to contacts.json on exit
//    b. Load contacts from contacts.json on startup
//    c. If contacts.json doesn't exist, start with empty list
//
// 3. USER INTERFACE
//    a. Menu-driven: show numbered options, accept user choice
//    b. Clear prompts for each action
//    c. Console.ReadLine() for all input
//
// 4. ERROR HANDLING
//    a. Invalid menu choice → show error, redisplay menu
//    b. Empty name → reject, ask again
//    c. File not found → start with empty list
//    d. JSON deserialization error → start fresh, warn user
//
// 5. NICE-TO-HAVE (if time permits)
//    a. Export contacts to CSV
//    b. Sort contacts by name or category
//    c. Count contacts per category

✅ Requirements Writing Tips

  • Start each requirement with a verb: "Add", "Display", "Save", "Validate"
  • Be specific about behavior: What happens when the user does X?
  • Separate "must-have" from "nice-to-have": Build the core first, add extras if time permits
  • Think about edge cases: What if the file doesn't exist? What if the user enters nothing?

Breaking It Down

Top-down decomposition means starting with the big picture and breaking it into smaller and smaller pieces until each piece is easy to code. Think of it like an outline for an essay — you don't write the whole thing at once.

flowchart TD A["Contact Manager"] --> B["Data Layer"] A --> C["UI Layer"] A --> D["File Layer"] B --> B1["Contact class"] B --> B2["ContactList class"] C --> C1["show_menu()"] C --> C2["get_user_input()"] C --> C3["display_contacts()"] D --> D1["save_to_json()"] D --> D2["load_from_json()"] style A fill:#3b82f6,color:#fff style B fill:#6366f1,color:#fff style C fill:#6366f1,color:#fff style D fill:#6366f1,color:#fff

Each box in the diagram becomes a function or class. When every piece is small enough to describe in one sentence, you're ready to code.

The Task List

Convert your decomposition into an ordered task list. Build from the bottom up — data first, then logic, then UI:

# Contact Manager — Task Breakdown
# =================================
#
# PHASE 1: Data Foundation (build and test first)
#   [ ] Task 1: Create Contact class with name, email, phone, category
#   [ ] Task 2: Add to_dict() and from_dict() methods for JSON
#   [ ] Task 3: Test creating a few Contact objects manually
#
# PHASE 2: File Operations
#   [ ] Task 4: Write save_contacts(contacts, filename) function
#   [ ] Task 5: Write load_contacts(filename) function
#   [ ] Task 6: Test save and load — create contacts, save, load, verify
#
# PHASE 3: Core Features
#   [ ] Task 7: Write add_contact() — prompt user, create Contact, add to list
#   [ ] Task 8: Write display_contacts() — formatted table output
#   [ ] Task 9: Write search_by_name() — case-insensitive partial match
#   [ ] Task 10: Write search_by_category() — filter and display
#   [ ] Task 11: Write edit_contact() — find contact, update fields
#   [ ] Task 12: Write delete_contact() — find, confirm, remove
#
# PHASE 4: Menu & Main Loop
#   [ ] Task 13: Write show_menu() — display numbered options
#   [ ] Task 14: Write main() — menu loop, dispatch to functions
#   [ ] Task 15: Add save-on-exit behavior
#
# PHASE 5: Polish
#   [ ] Task 16: Add input validation (empty names, invalid choices)
#   [ ] Task 17: Test all features end-to-end
#   [ ] Task 18: Add nice-to-have features if time permits
// Contact Manager — Task Breakdown
// =================================
//
// PHASE 1: Data Foundation (build and test first)
//   [ ] Task 1: Create Contact class with name, email, phone, category
//   [ ] Task 2: Add toJSON() and static fromJSON() methods
//   [ ] Task 3: Test creating a few Contact objects manually
//
// PHASE 2: File Operations
//   [ ] Task 4: Write saveContacts(contacts, filename) function
//   [ ] Task 5: Write loadContacts(filename) function
//   [ ] Task 6: Test save and load — create, save, load, verify
//
// PHASE 3: Core Features
//   [ ] Task 7: Write addContact() — prompt user, create, add to array
//   [ ] Task 8: Write displayContacts() — formatted table output
//   [ ] Task 9: Write searchByName() — case-insensitive partial match
//   [ ] Task 10: Write searchByCategory() — filter and display
//   [ ] Task 11: Write editContact() — find, update fields
//   [ ] Task 12: Write deleteContact() — find, confirm, remove
//
// PHASE 4: Menu & Main Loop
//   [ ] Task 13: Write showMenu() — display numbered options
//   [ ] Task 14: Write main() — menu loop with readline
//   [ ] Task 15: Add save-on-exit behavior
//
// PHASE 5: Polish
//   [ ] Task 16: Add input validation
//   [ ] Task 17: Test all features end-to-end
//   [ ] Task 18: Add nice-to-have features if time permits
// Contact Manager — Task Breakdown
// =================================
//
// PHASE 1: Data Foundation (build and test first)
//   [ ] Task 1: Create Contact class with properties
//   [ ] Task 2: Add JSON serialization attributes
//   [ ] Task 3: Test creating a few Contact objects manually
//
// PHASE 2: File Operations
//   [ ] Task 4: Write SaveContacts(contacts, filename) method
//   [ ] Task 5: Write LoadContacts(filename) method
//   [ ] Task 6: Test save and load round-trip
//
// PHASE 3: Core Features
//   [ ] Task 7: Write AddContact() — prompt, create, add to List
//   [ ] Task 8: Write DisplayContacts() — formatted table output
//   [ ] Task 9: Write SearchByName() — case-insensitive Contains
//   [ ] Task 10: Write SearchByCategory() — filter and display
//   [ ] Task 11: Write EditContact() — find, update properties
//   [ ] Task 12: Write DeleteContact() — find, confirm, remove
//
// PHASE 4: Menu & Main Loop
//   [ ] Task 13: Write ShowMenu() — display numbered options
//   [ ] Task 14: Wire up Main() with menu loop
//   [ ] Task 15: Add save-on-exit behavior
//
// PHASE 5: Polish
//   [ ] Task 16: Add input validation
//   [ ] Task 17: Test all features end-to-end
//   [ ] Task 18: Add nice-to-have features if time permits

⚠️ Build Bottom-Up, Plan Top-Down

Plan from the big picture down (what does the whole app do?). But build from the smallest pieces up (get the Contact class working before you build the menu). If your data layer is solid, everything built on top of it will work more smoothly.

Writing Pseudocode

Pseudocode is a plain-English description of your program's logic. It looks like code but uses human language. It helps you think through your logic without getting distracted by syntax.

# Pseudocode: Main program loop
# ==============================
#
# Load contacts from file (or start with empty list)
# LOOP:
#     Show menu (1=Add, 2=View, 3=Search, 4=Edit, 5=Delete, 6=Quit)
#     Get user's choice
#     IF choice is 1: call add_contact()
#     IF choice is 2: call display_contacts()
#     IF choice is 3: call search_contacts()
#     IF choice is 4: call edit_contact()
#     IF choice is 5: call delete_contact()
#     IF choice is 6: save contacts to file, EXIT loop
#     ELSE: show "Invalid choice" error
#
# Pseudocode: search_contacts()
# ==============================
#
# Ask user: "Search by (1) name or (2) category?"
# Get search term from user
# Convert search term to lowercase
# FOR each contact in contacts:
#     IF search term is found in contact's name (lowercase):
#         Add to results list
# IF results is empty:
#     Print "No contacts found"
# ELSE:
#     Display results in formatted table
// Pseudocode: Main program loop
// ==============================
//
// Load contacts from file (or start with empty array)
// LOOP:
//     Show menu (1=Add, 2=View, 3=Search, 4=Edit, 5=Delete, 6=Quit)
//     Get user's choice via readline
//     IF choice is 1: call addContact()
//     IF choice is 2: call displayContacts()
//     IF choice is 3: call searchContacts()
//     IF choice is 4: call editContact()
//     IF choice is 5: call deleteContact()
//     IF choice is 6: save contacts to file, close readline, EXIT
//     ELSE: show "Invalid choice" error
//
// Pseudocode: searchContacts()
// ==============================
//
// Ask user: "Search by (1) name or (2) category?"
// Get search term from user
// Convert search term to lowercase
// Filter contacts array:
//     keep contacts where name.toLowerCase() includes search term
// IF filtered array is empty:
//     Print "No contacts found"
// ELSE:
//     Display filtered results in formatted table
// Pseudocode: Main program loop
// ==============================
//
// Load contacts from file (or start with empty List)
// LOOP:
//     Show menu (1=Add, 2=View, 3=Search, 4=Edit, 5=Delete, 6=Quit)
//     Get user's choice via Console.ReadLine()
//     SWITCH on choice:
//         case "1": call AddContact()
//         case "2": call DisplayContacts()
//         case "3": call SearchContacts()
//         case "4": call EditContact()
//         case "5": call DeleteContact()
//         case "6": save contacts to file, EXIT loop
//         default: show "Invalid choice" error
//
// Pseudocode: SearchContacts()
// ==============================
//
// Ask user: "Search by (1) name or (2) category?"
// Get search term from user
// Convert to lowercase
// Use LINQ Where() to find contacts where
//     Name.ToLower().Contains(searchTerm)
// IF no results:
//     Print "No contacts found"
// ELSE:
//     Display results in formatted table

✅ Good Pseudocode Habits

  • Use plain English — anyone should be able to read it
  • Use indentation to show structure (loops, conditionals)
  • Use keywords like LOOP, IF, FOR, RETURN to show control flow
  • Don't worry about syntax — no semicolons, no types, no imports
  • Focus on the "what" not the "how" — say "sort the list" not "implement quicksort"

Planning Your Data Model

Your data model defines the shape of your data — what classes you need, what properties they have, and how they relate to each other. This is where your OOP knowledge (Module 6) pays off.

# --- Data Model ---

class Contact:
    """Represents a single contact."""

    def __init__(self, name, email="", phone="", category="General"):
        self.name = name
        self.email = email
        self.phone = phone
        self.category = category

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            "name": self.name,
            "email": self.email,
            "phone": self.phone,
            "category": self.category,
        }

    @classmethod
    def from_dict(cls, data):
        """Create a Contact from a dictionary (loaded from JSON)."""
        return cls(
            name=data["name"],
            email=data.get("email", ""),
            phone=data.get("phone", ""),
            category=data.get("category", "General"),
        )

    def __str__(self):
        return f"{self.name} ({self.category}) — {self.email}, {self.phone}"


# --- JSON Structure (contacts.json) ---
# [
#     {"name": "Alice", "email": "alice@example.com", "phone": "555-0101", "category": "Friend"},
#     {"name": "Bob", "email": "bob@work.com", "phone": "555-0202", "category": "Work"}
# ]
// --- Data Model ---

class Contact {
    constructor(name, email = "", phone = "", category = "General") {
        this.name = name;
        this.email = email;
        this.phone = phone;
        this.category = category;
    }

    toJSON() {
        return {
            name: this.name,
            email: this.email,
            phone: this.phone,
            category: this.category,
        };
    }

    static fromJSON(data) {
        return new Contact(
            data.name,
            data.email || "",
            data.phone || "",
            data.category || "General"
        );
    }

    toString() {
        return `${this.name} (${this.category}) — ${this.email}, ${this.phone}`;
    }
}

// --- JSON Structure (contacts.json) ---
// [
//     {"name": "Alice", "email": "alice@example.com", "phone": "555-0101", "category": "Friend"},
//     {"name": "Bob", "email": "bob@work.com", "phone": "555-0202", "category": "Work"}
// ]
using System.Text.Json.Serialization;

// --- Data Model ---

class Contact
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = "";

    [JsonPropertyName("email")]
    public string Email { get; set; } = "";

    [JsonPropertyName("phone")]
    public string Phone { get; set; } = "";

    [JsonPropertyName("category")]
    public string Category { get; set; } = "General";

    public Contact() { }  // Needed for JSON deserialization

    public Contact(string name, string email = "", string phone = "", string category = "General")
    {
        Name = name;
        Email = email;
        Phone = phone;
        Category = category;
    }

    public override string ToString()
        => $"{Name} ({Category}) — {Email}, {Phone}";
}

// --- JSON Structure (contacts.json) ---
// [
//     {"name": "Alice", "email": "alice@example.com", "phone": "555-0101", "category": "Friend"},
//     {"name": "Bob", "email": "bob@work.com", "phone": "555-0202", "category": "Work"}
// ]

💡 Data Model Design Tips

  • One class per "thing": Contact, JournalEntry, Player, Question — each is a class
  • Include serialization methods: to_dict() / from_dict() so you can save/load JSON
  • Use default values: Not every contact has a phone number — make optional fields default to empty strings
  • Plan your JSON structure: Sketch out what the saved file will look like — this catches design issues early

Designing the Architecture

Architecture is how the pieces of your program fit together. For a single-file beginner project, architecture means deciding the order of your code and how functions call each other.

flowchart TD A["main()"] -->|"displays"| B["show_menu()"] A -->|"calls based on choice"| C["add_contact()"] A -->|"calls"| D["display_contacts()"] A -->|"calls"| E["search_contacts()"] A -->|"calls"| F["edit_contact()"] A -->|"calls"| G["delete_contact()"] A -->|"on exit"| H["save_contacts()"] A -->|"on start"| I["load_contacts()"] C --> J["Contact class"] F --> J H --> K["contacts.json"] I --> K style A fill:#3b82f6,color:#fff style J fill:#6366f1,color:#fff style K fill:#22c55e,color:#fff

Code Organization Pattern

# contact_manager.py — File Structure
# =====================================
#
# SECTION 1: Imports
#   import json, os
#
# SECTION 2: Constants
#   FILENAME = "contacts.json"
#
# SECTION 3: Contact class
#   class Contact:
#       __init__, to_dict, from_dict, __str__
#
# SECTION 4: File operations
#   def save_contacts(contacts, filename)
#   def load_contacts(filename)
#
# SECTION 5: Feature functions
#   def add_contact(contacts)
#   def display_contacts(contacts)
#   def search_contacts(contacts)
#   def edit_contact(contacts)
#   def delete_contact(contacts)
#
# SECTION 6: UI helpers
#   def show_menu()
#   def get_input(prompt, required=False)
#
# SECTION 7: Main function
#   def main()
#
# SECTION 8: Entry point
#   if __name__ == "__main__":
#       main()
// contactManager.js — File Structure
// =====================================
//
// SECTION 1: Imports
//   const fs = require("fs");
//   const readline = require("readline");
//
// SECTION 2: Constants
//   const FILENAME = "contacts.json";
//
// SECTION 3: Contact class
//   class Contact { constructor, toJSON, fromJSON, toString }
//
// SECTION 4: File operations
//   function saveContacts(contacts, filename)
//   function loadContacts(filename)
//
// SECTION 5: Readline helper
//   function prompt(question) — returns Promise
//
// SECTION 6: Feature functions
//   async function addContact(contacts)
//   function displayContacts(contacts)
//   async function searchContacts(contacts)
//   async function editContact(contacts)
//   async function deleteContact(contacts)
//
// SECTION 7: UI
//   function showMenu()
//
// SECTION 8: Main function
//   async function main()
//
// SECTION 9: Entry point
//   main();
// Program.cs — File Structure
// =====================================
//
// SECTION 1: Using statements
//   using System.Text.Json;
//
// SECTION 2: Constants
//   const string Filename = "contacts.json";
//
// SECTION 3: Contact class (can be in same file for simplicity)
//   class Contact { Properties, Constructor, ToString }
//
// SECTION 4: File operations
//   static void SaveContacts(List<Contact> contacts, string filename)
//   static List<Contact> LoadContacts(string filename)
//
// SECTION 5: Feature methods
//   static void AddContact(List<Contact> contacts)
//   static void DisplayContacts(List<Contact> contacts)
//   static void SearchContacts(List<Contact> contacts)
//   static void EditContact(List<Contact> contacts)
//   static void DeleteContact(List<Contact> contacts)
//
// SECTION 6: UI helpers
//   static void ShowMenu()
//   static string GetInput(string prompt, bool required = false)
//
// SECTION 7: Main program (top-level statements)
//   Load → Loop → Save

✅ Architecture Principles for Beginners

  • One file is fine. Don't split into multiple files until a single file feels unmanageable (300+ lines).
  • Group related code. Keep all file operations together, all display functions together.
  • Functions should do ONE thing. add_contact() adds a contact. It doesn't also save the file.
  • Main() is the conductor. It calls functions but doesn't contain the logic itself.
  • Data flows through parameters. Pass the contacts list to functions — don't use global variables.

Creating Your Project Checklist

Before moving to Lesson 26, create your own project plan using this template. Fill it in for whichever project you chose.

# ==========================================
# PROJECT PLAN: [Your Project Name]
# Language: Python
# Date: [Today's date]
# ==========================================
#
# 1. PROJECT DESCRIPTION (1-2 sentences)
#    [What does this program do?]
#
# 2. SKILLS USED (check at least 4)
#    [ ] Variables & Data Types
#    [ ] Control Flow (if/else, loops)
#    [ ] Functions
#    [ ] Collections (lists, dicts)
#    [ ] Classes (OOP)
#    [ ] Error Handling (try/except)
#    [ ] File I/O & JSON
#    [ ] API Requests
#
# 3. REQUIREMENTS (list 5-8 specific features)
#    a. [User can ...]
#    b. [User can ...]
#    c. [Program saves ...]
#    d. [Program handles ...]
#    e. ...
#
# 4. DATA MODEL (what classes/data structures?)
#    Class: [ClassName]
#       - property1: type
#       - property2: type
#    JSON file: [filename.json]
#       - structure: [...]
#
# 5. TASK LIST (ordered, build bottom-up)
#    [ ] Task 1: ...
#    [ ] Task 2: ...
#    [ ] ...
#
# 6. PSEUDOCODE (for the main loop + 1 key function)
#    [Write it out]
#
# 7. NICE-TO-HAVE (bonus features if time permits)
#    - ...
#    - ...
// ==========================================
// PROJECT PLAN: [Your Project Name]
// Language: JavaScript (Node.js)
// Date: [Today's date]
// ==========================================
//
// 1. PROJECT DESCRIPTION (1-2 sentences)
//    [What does this program do?]
//
// 2. SKILLS USED (check at least 4)
//    [ ] Variables & Data Types
//    [ ] Control Flow (if/else, loops)
//    [ ] Functions
//    [ ] Collections (arrays, objects)
//    [ ] Classes (OOP)
//    [ ] Error Handling (try/catch)
//    [ ] File I/O & JSON
//    [ ] API Requests (fetch)
//
// 3. REQUIREMENTS (list 5-8 specific features)
//    a. [User can ...]
//    b. [User can ...]
//    c. [Program saves ...]
//    d. [Program handles ...]
//    e. ...
//
// 4. DATA MODEL (what classes/data structures?)
//    Class: [ClassName]
//       - property1: type
//       - property2: type
//    JSON file: [filename.json]
//       - structure: [...]
//
// 5. TASK LIST (ordered, build bottom-up)
//    [ ] Task 1: ...
//    [ ] Task 2: ...
//    [ ] ...
//
// 6. PSEUDOCODE (for the main loop + 1 key function)
//    [Write it out]
//
// 7. NICE-TO-HAVE (bonus features if time permits)
//    - ...
//    - ...
// ==========================================
// PROJECT PLAN: [Your Project Name]
// Language: C# (.NET)
// Date: [Today's date]
// ==========================================
//
// 1. PROJECT DESCRIPTION (1-2 sentences)
//    [What does this program do?]
//
// 2. SKILLS USED (check at least 4)
//    [ ] Variables & Data Types
//    [ ] Control Flow (if/else, loops, switch)
//    [ ] Functions / Methods
//    [ ] Collections (List, Dictionary)
//    [ ] Classes (OOP)
//    [ ] Error Handling (try/catch)
//    [ ] File I/O & JSON
//    [ ] API Requests (HttpClient)
//
// 3. REQUIREMENTS (list 5-8 specific features)
//    a. [User can ...]
//    b. [User can ...]
//    c. [Program saves ...]
//    d. [Program handles ...]
//    e. ...
//
// 4. DATA MODEL (what classes/data structures?)
//    Class: [ClassName]
//       - Property1: type
//       - Property2: type
//    JSON file: [filename.json]
//       - structure: [...]
//
// 5. TASK LIST (ordered, build bottom-up)
//    [ ] Task 1: ...
//    [ ] Task 2: ...
//    [ ] ...
//
// 6. PSEUDOCODE (for the main loop + 1 key function)
//    [Write it out]
//
// 7. NICE-TO-HAVE (bonus features if time permits)
//    - ...
//    - ...

Exercises

🏋️ Exercise 1: Write Your Project Plan

Objective: Using the template above, write a complete project plan for your chosen capstone project (Option A, B, or C — or your own).

Requirements:

  1. Fill in all 7 sections of the template
  2. Your requirements should have at least 5 specific, testable features
  3. Your task list should have at least 10 tasks in build order
  4. Write pseudocode for the main loop and at least one feature function

Save your plan as a comment at the top of your project file, or as a separate plan.txt file. You'll use it as your roadmap in Lesson 26.

🏋️ Exercise 2: Build and Test the Data Model

Objective: Before Lesson 26, write just the data class for your project and test it.

Steps:

  1. Create your class (Contact, JournalEntry, Player, Question, etc.)
  2. Add serialization methods (to_dict/from_dict, toJSON/fromJSON)
  3. Create 2-3 test objects and print them
  4. Convert them to JSON (dictionary/object) and back — verify nothing is lost

This is the foundation everything else builds on. Get it right now and Lesson 26 will go much smoother.

🏋️ Exercise 3: Pseudocode Practice

Objective: Write pseudocode for every feature function in your project — not just the main loop.

This is optional but highly recommended. Students who write thorough pseudocode finish Lesson 26 significantly faster than those who don't.

🎓 Instructor Note: Delivery Guidance

Exercise 1 is the most important — every student should leave this lesson with a written project plan. Exercise 2 (building the data model) is an excellent "homework" assignment before Lesson 26 — it gives students a head start and reduces Lesson 26 to assembling pre-tested pieces. If teaching in a classroom setting, consider having students pair up and review each other's plans. A good review question: "If I read just your task list, could I build this project without asking you any questions?" If not, the tasks need more detail. Warn students about scope creep: "Your plan should describe a project you can build in 90 minutes, not 9 hours."

Summary

🎉 Key Takeaways

  • Plan before you code: 20% planning saves 80% debugging
  • Write clear requirements: Specific, testable statements of what the program does
  • Decompose top-down: Break the big problem into small, manageable tasks
  • Build bottom-up: Start with data classes, then file I/O, then features, then the UI
  • Pseudocode first: Think through logic in plain English before writing syntax
  • Design your data model: Classes + JSON structure = the foundation of your project
  • Keep a task checklist: Check things off as you build — it keeps you focused and motivated

The Planning Process

flowchart LR A["Choose
Project"] --> B["Write
Requirements"] B --> C["Break Into
Tasks"] C --> D["Write
Pseudocode"] D --> E["Design Data
Model"] E --> F["Plan
Architecture"] F --> G["Ready
to Build!"] style A fill:#3b82f6,color:#fff style B fill:#3b82f6,color:#fff style C fill:#6366f1,color:#fff style D fill:#6366f1,color:#fff style E fill:#22c55e,color:#fff style F fill:#22c55e,color:#fff style G fill:#f59e0b,color:#fff

🚀 What's Next?

In Lesson 26: Building Your Project, you'll take your plan and turn it into a working program. You'll follow your task list phase by phase, testing as you go. Come prepared with your project plan and (ideally) a tested data class. Let's build something real!

🎯 Quick Check

Question 1: What's the recommended order for building a project?

Question 2: What makes a good requirement?

Question 3: Why should your data class include to_dict()/from_dict() methods?