Build a Contact Book CLI in Python
Build a CLI contact manager using Python's core idioms and standard library.
Introduction
30 Second Summary
Everyone has a phone full of contacts. But have you ever thought about what happens behind the scenes when you search, add, or delete one?
In this project, you will build a Contact Book CLI application in Python that stores, searches, and manages contacts directly from your terminal.
What You'll Build
A command-line application that manages contacts with add, list, search, and delete operations, persisted to a JSON file.
By the end of this project, you'll have:
- A working CLI application that manages contacts with add, list, search, and delete operations.
- Hands-on experience with Python's distinctive features like list comprehensions, f-strings, context managers, and tuple unpacking.
- A JSON file acting as persistent storage, demonstrating Python's standard library for file I/O.
- Secret Mission: Add contact categories and a CSV export feature.
Are there any prerequisites?
This project assumes you have programming experience in another language. You do not need any prior Python knowledge.
Before We Start
Your Contact Book app runs on Python, so the first thing to sort out is getting Python installed on your machine.
You'll also set up VS Code with the right extension for Python development. By the end of this step, you'll have an empty project folder ready for code.
In this step, get ready to:
- Install Python 3.14.
- Install the Python extension for VS Code.
- Create your project folder and first file.
Install Python 3.14
Python is the language you'll write your Contact Book in. You need version 3.14 or higher installed on your machine.
- Open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing VS Code, and pressing Enter.
- Open the integrated terminal by pressing Ctrl+` (Windows) or Control+` (macOS).
- Check if Python is already installed by running the version command for your operating system:
macOS
python3 --version
Windows
python --version
✔️ I see Python 3.14 or higher
You're all set. Your terminal shows something like Python 3.14.6. Skip ahead to the next substep.
ⓧ I see an older version
You have Python installed, but your version is below 3.14. You need to install the correct version alongside it.
- Go to python.org/downloads in your browser.
- Click the download button for Python 3.14.
macOS
- Download the macOS installer (.pkg file).
- Double-click the downloaded file and follow the installer prompts.
- After installation finishes, double-click Install Certificates.command in the /Applications/Python 3.14/ folder to set up SSL certificates.
- Close and reopen your VS Code terminal, then run:
python3 --version
You should see Python 3.14.6 (or a similar 3.14.x version).
Windows
- Download the Windows installer (64-bit) option (listed as Recommended).
- Run the installer. On the first page, check the box labelled Add Python to PATH before clicking Install Now.
- Close and reopen your VS Code terminal, then run:
python --version
You should see Python 3.14.6 (or a similar 3.14.x version).
ⓧ Command not found
Python is not installed on your machine. Let's install it from python.org.
- Go to python.org/downloads in your browser.
- Click the download button for Python 3.14.
macOS
- Download the macOS installer (.pkg file).
- Double-click the downloaded file and follow the installer prompts.
- After installation finishes, double-click Install Certificates.command in the /Applications/Python 3.14/ folder to set up SSL certificates.
- Close and reopen your VS Code terminal, then run:
python3 --version
You should see Python 3.14.6 (or a similar 3.14.x version).
Windows
- Download the Windows installer (64-bit) option (listed as Recommended).
- Run the installer. On the first page, check the box labelled Add Python to PATH before clicking Install Now.
- Close and reopen your VS Code terminal, then run:
python --version
You should see Python 3.14.6 (or a similar 3.14.x version).
Why check Add Python to PATH?
The PATH is a list of folders your terminal searches when you type a command. Checking this box during installation means your terminal can find the python command without you manually configuring anything.
Still seeing the wrong version?
On macOS, the system may have an older Python that shadows your new install. Try running python3.14 --version to target the version directly. If that works, use python3.14 instead of python3 for the rest of this project.
On Windows, close all VS Code windows and reopen. The PATH update only takes effect in new terminal sessions.
help me fix my Python version issue
Install the Python extension for VS Code
The Python extension gives VS Code syntax highlighting, code suggestions, and debugging support for Python files.
- Press Ctrl+Shift+X (Windows) or Cmd+Shift+X (macOS) to open the Extensions panel.
- Type Python in the search bar at the top.
- Find the extension published by Microsoft (it has the ID ms-python.python).
- Click Install.
What does the extension include?
Installing the Python extension also installs Pylance (for fast code suggestions and type checking) and the Python Debugger. You get full IntelliSense and debugging without any extra setup.
- Confirm the extension is active by checking that you see Python listed under your installed extensions in the panel.
Extension not showing up?
Make sure you're searching for the one by Microsoft, not a similarly-named community extension. The publisher name appears directly below the extension title.
help me find and install the Python extension in VS Code
Create your project folder
Every Python project lives in its own folder. You'll create one called contact-book on your Desktop and open it in VS Code.
- Open the integrated terminal in VS Code by pressing Ctrl+` (Windows) or Control+` (macOS).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the project folder and move into it by running these commands:
mkdir contact-book
cd contact-book
- Open this folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the contact-book folder, and click Open.
Why open the folder this way?
Opening a folder in VS Code sets it as your workspace. The Explorer sidebar shows only files in that folder, and the integrated terminal starts inside it automatically.
- Create the main project file by clicking the New File icon at the top of the Explorer sidebar (the page icon with a plus sign).
- Type contacts.py as the filename and press Enter.
- Confirm the file exists by checking that contacts.py appears in the Explorer sidebar and opens in the editor as an empty file.
Don't see the Explorer sidebar?
Press Ctrl+Shift+E (Windows) or Cmd+Shift+E (macOS) to toggle it open. If you still don't see your file, make sure you opened the contact-book folder (not a parent folder or individual file).
help me set up my project folder in VS Code
Your environment is ready. Next up, you'll write the functions that load and save contacts to a file.
Set Up Python and VS Code
Before writing any Python code, you need to confirm your tools are configured correctly. A misconfigured PATH or missing extension can lead to confusing errors later.
In this step, you'll run a few quick checks to verify that Python and your VS Code extension are working together, then open your project file so it's ready for code.
In this step, get ready to:
- Verify Python runs from your terminal.
- Test the Python extension with a quick script.
- Open your project file for editing.
Verify your Python installation
Python needs to be accessible from your terminal for you to run scripts. The quickest way to confirm this is to ask Python for its version number.
- In VS Code, open a new terminal by pressing Ctrl+Shift+` (macOS: Cmd+Shift+`).
- Check your Python version by running this command:
python --version
You should see Python 3.14.x printed in the terminal (the exact patch number may differ).
Not seeing a version number?
If you see command not found or an error, Python may not be on your system PATH. Close and reopen VS Code to refresh terminal environment variables.
On Windows, reinstall Python from python.org and make sure to check Add Python to PATH on the first page of the installer.
help me fix Python not being found in my terminal
Test the Python extension
The Python extension gives VS Code superpowers like autocomplete, error highlighting, and one-click script execution. A quick test file confirms everything is connected.
- Press Ctrl+N (macOS: Cmd+N) to create a new file.
- Save it as test.py inside your contact-book folder by pressing Ctrl+S (macOS: Cmd+S).
- Type this code into the file:
print("hello")
- Save the file, then run it in your terminal with this command:
python test.py
You should see hello printed in the terminal.
What does the Python extension give you?
The extension automatically installs Pylance for IntelliSense (autocomplete and type checking) and the Python Debugger for stepping through code. You get all three from a single install.
Not seeing hello in the output?
Make sure you saved the file before running the command. Check that you're in the contact-book directory in your terminal. You can confirm by running ls (macOS/Linux) or dir (Windows) and looking for test.py in the output.
help me run my first Python script in VS Code
- Now that the test passed, delete test.py by right-clicking it in the Explorer sidebar and selecting Delete.
Open your project file
Your project lives in a single file. All the code you write in the following steps goes into this file.
- In the Explorer sidebar, click contacts.py to open it in the editor.
The file is empty for now. You'll start filling it in the next step.
Why one file?
Python makes it easy to keep small applications in a single module. For a CLI tool of this size, one file keeps things simple and easy to run with a single python contacts.py command.
Your environment is ready. Next up, you'll build the data layer that loads and saves contacts to a JSON file.
Build the Core Data Layer
Your Python environment is ready and contacts.py is waiting for code.
Every contact book needs a way to save and retrieve data. In this step, you will build the persistence layer using Python's built-in json module, then add functions to create and display contacts.
In this step, get ready to:
- Create file I/O functions that load and save contacts as JSON.
- Build an add_contact function that collects user input and stores it as a dictionary.
- Build a list_contacts function that displays contacts in a formatted table.
Create the file I/O functions
Python's context managers (the with statement) handle opening and closing files automatically. This prevents resource leaks even if your code crashes mid-operation.
- In contacts.py, add the imports, file path constant, and both file I/O functions by copying this code:
import json
import os
CONTACTS_FILE = "contacts.json"
def load_contacts():
"""Load contacts from JSON file."""
if not os.path.exists(CONTACTS_FILE):
return []
with open(CONTACTS_FILE, "r") as f:
return json.load(f)
def save_contacts(contacts):
"""Save contacts to JSON file."""
with open(CONTACTS_FILE, "w") as f:
json.dump(contacts, f, indent=2)
What does this code do?
CONTACTS_FILE is a module-level constant that stores the filename. Python doesn't need a type declaration. The variable name in SCREAMING_CASE is a convention that signals "don't change this value."
load_contacts() checks whether the file exists before attempting to read it. If the file doesn't exist yet, it returns an empty list. The with open(...) block automatically closes the file when the block ends.
save_contacts() writes the entire contacts list to disk as formatted JSON. The indent=2 parameter makes the file human-readable.
- Save contacts.py.
- You won't see any output yet. The next function (add_contact) will put these helpers to work.
Create the add_contact function
Python dictionaries are the natural way to represent a single contact. Each dictionary holds key-value pairs for name, phone, and email.
- Below your save_contacts function in contacts.py, add the add_contact function:
def add_contact(contacts):
"""Add a new contact."""
name = input("Name: ")
phone = input("Phone: ")
email = input("Email: ")
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
save_contacts(contacts)
print(f"\n'{name}' added successfully!")
What does this code do?
input() pauses the program and waits for the user to type something. The string you pass to it becomes the prompt text.
The contact dictionary uses curly braces with key-value pairs. Notice the trailing comma after the last entry. This is a Python convention that makes future edits cleaner (no diff noise when you add a new field).
The f"..." syntax is an f-string. Python evaluates anything inside {...} and inserts it into the string. It replaces the string formatting patterns you may know from other languages.
- Save contacts.py.
- You won't see output yet. The next function (list_contacts) will let you view the data you create.
Create the list_contacts function
Python's enumerate() gives you both the index and the item during iteration. Combined with f-string format specifiers, you can build clean columnar output without any external library.
- Below your add_contact function in contacts.py, add the list_contacts function:
def list_contacts(contacts):
"""Display all contacts."""
if not contacts:
print("\nNo contacts found.")
return
print(f"\n{'Name':<20} {'Phone':<15} {'Email'}")
print("-" * 55)
for i, contact in enumerate(contacts, start=1):
print(f"{i}. {contact['name']:<17} {contact['phone']:<15} {contact['email']}")
What does this code do?
The early return pattern (if not contacts: return) exits immediately when the list is empty. This avoids nesting the rest of the function inside an else block.
enumerate(contacts, start=1) yields pairs of (number, item), starting the count at 1 instead of the default 0. In other languages you might maintain a separate counter variable.
The :<20 inside the f-string is a format specifier. It left-aligns text and pads it to 20 characters wide, creating neat columns without importing a table library.
- Save contacts.py.
- Test your functions by running this command in the terminal:
python -i contacts.py
What does this command do?
The -i flag tells Python to run your script and then drop into interactive mode. All your functions are loaded and ready to call by hand.
- In the interactive session, type the following and press Enter after each line:
contacts = load_contacts()
add_contact(contacts)
- Enter a test name, phone number, and email when prompted.
- Then type:
list_contacts(contacts)
- You should see a formatted table with one numbered row showing your test contact.
- Type exit() to leave interactive mode.
Seeing a SyntaxError or IndentationError?
Python is strict about whitespace. Make sure every indented line uses exactly 4 spaces (not tabs). Check that your def lines start at column 0 and all function body lines are indented one level.
If you see a NameError, confirm that function names match exactly: load_contacts, save_contacts, add_contact, list_contacts.
help me debug a Python IndentationError or NameError in my contacts.py file
✔️ Awesome, I've got everything!
Great. Double check you've saved contacts.py before moving on.
ⓧ I'd like to double check the full code
import json
import os
CONTACTS_FILE = "contacts.json"
def load_contacts():
"""Load contacts from JSON file."""
if not os.path.exists(CONTACTS_FILE):
return []
with open(CONTACTS_FILE, "r") as f:
return json.load(f)
def save_contacts(contacts):
"""Save contacts to JSON file."""
with open(CONTACTS_FILE, "w") as f:
json.dump(contacts, f, indent=2)
def add_contact(contacts):
"""Add a new contact."""
name = input("Name: ")
phone = input("Phone: ")
email = input("Email: ")
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
save_contacts(contacts)
print(f"\n'{name}' added successfully!")
def list_contacts(contacts):
"""Display all contacts."""
if not contacts:
print("\nNo contacts found.")
return
print(f"\n{'Name':<20} {'Phone':<15} {'Email'}")
print("-" * 55)
for i, contact in enumerate(contacts, start=1):
print(f"{i}. {contact['name']:<17} {contact['phone']:<15} {contact['email']}")
Your data layer is solid. Next up, you will add search and delete functions so users can find and remove contacts from the book.
Add Search and Delete
Your contact book can add and display contacts. That covers the "write" and "read" side of things, but a real contact book also needs to find specific people quickly and remove outdated entries.
In this step, you will write two new functions: one that searches contacts by name using a list comprehension, and one that deletes a contact by number with proper error handling.
In this step, get ready to:
- Write a search function that filters contacts using a list comprehension.
- Write a delete function with exception handling and index validation.
Create the search function
Searching a list of contacts means filtering down to only the entries that match a query. Python has a dedicated syntax for this called a list comprehension, which builds a new list by applying a condition to every item in an existing list.
- In contacts.py, add the following search_contacts function below your existing list_contacts function:
def search_contacts(contacts):
"""Search contacts by name."""
query = input("Search: ").lower()
results = [c for c in contacts if query in c["name"].lower()]
if not results:
print(f"\nNo contacts matching '{query}'.")
return
print(f"\nFound {len(results)} result(s):")
for contact in results:
print(f" - {contact['name']}: {contact['phone']} | {contact['email']}")
What does this code do?
The line results = [c for c in contacts if query in c["name"].lower()] is a list comprehension. It loops through every contact in the list and keeps only those where the search query appears somewhere in the contact's name.
Calling .lower() on both the query and the name means the search is case-insensitive. Searching for "alice" will match "Alice" or "ALICE".
- Save contacts.py.
- You won't see this in action yet. The next step builds the menu loop that calls this function.
Seeing a syntax error?
Check that the list comprehension uses square brackets [c for c in contacts if ...] and not parentheses.
Make sure each string inside f-strings uses the opposite quote style from the outer string. The outer string uses double quotes, so dictionary keys inside use single quotes: contact['name'].
help me fix a syntax error in my search_contacts function
Create the delete function
Deleting a contact requires showing the user the numbered list, asking which number to remove, and handling the case where they type something invalid. Python's try/except block handles errors gracefully instead of crashing the program.
- In contacts.py, add the following delete_contact function below search_contacts:
def delete_contact(contacts):
"""Delete a contact by number."""
list_contacts(contacts)
if not contacts:
return
try:
index = int(input("\nEnter contact number to delete: ")) - 1
if 0 <= index < len(contacts):
removed = contacts.pop(index)
save_contacts(contacts)
print(f"\n'{removed['name']}' deleted.")
else:
print("\nInvalid number.")
except ValueError:
print("\nPlease enter a valid number.")
What does this code do?
The function first displays the current contacts by reusing list_contacts. If the list is empty, it returns early so there is nothing to delete.
The try/except ValueError block catches the case where the user types letters instead of a number. Without it, int() would crash the program with an unhandled exception.
The condition 0 <= index < len(contacts) is called comparison chaining. It checks that the index falls within the valid range before calling contacts.pop(index) to remove and return the contact at that position.
- Save contacts.py.
- You won't see this in action yet. The next step wires both new functions into the menu loop.
Indentation looks wrong?
Everything inside the try: block must be indented one level deeper than try itself. The except ValueError: line must align with try: at the same indentation level.
If you see an IndentationError, make sure you are using consistent spaces (4 spaces per level) and not mixing tabs with spaces.
help me fix an indentation error in my delete_contact function
✔️ Awesome, I've got everything!
Great. Double check you've saved contacts.py with both new functions in place.
ⓧ I'd like to double check the full code
Here is the full contacts.py file up to this point:
import json
import os
CONTACTS_FILE = "contacts.json"
def load_contacts():
"""Load contacts from JSON file."""
if not os.path.exists(CONTACTS_FILE):
return []
with open(CONTACTS_FILE, "r") as f:
return json.load(f)
def save_contacts(contacts):
"""Save contacts to JSON file."""
with open(CONTACTS_FILE, "w") as f:
json.dump(contacts, f, indent=2)
def add_contact(contacts):
"""Add a new contact."""
name = input("Name: ")
phone = input("Phone: ")
email = input("Email: ")
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
save_contacts(contacts)
print(f"\n'{name}' added successfully!")
def list_contacts(contacts):
"""Display all contacts."""
if not contacts:
print("\nNo contacts found.")
return
print(f"\n{'Name':<20} {'Phone':<15} {'Email'}")
print("-" * 55)
for i, contact in enumerate(contacts, start=1):
print(f"{i}. {contact['name']:<17} {contact['phone']:<15} {contact['email']}")
def search_contacts(contacts):
"""Search contacts by name."""
query = input("Search: ").lower()
results = [c for c in contacts if query in c["name"].lower()]
if not results:
print(f"\nNo contacts matching '{query}'.")
return
print(f"\nFound {len(results)} result(s):")
for contact in results:
print(f" - {contact['name']}: {contact['phone']} | {contact['email']}")
def delete_contact(contacts):
"""Delete a contact by number."""
list_contacts(contacts)
if not contacts:
return
try:
index = int(input("\nEnter contact number to delete: ")) - 1
if 0 <= index < len(contacts):
removed = contacts.pop(index)
save_contacts(contacts)
print(f"\n'{removed['name']}' deleted.")
else:
print("\nInvalid number.")
except ValueError:
print("\nPlease enter a valid number.")
Your contact book now has all four core operations: add, list, search, and delete. Next up, you will wire these functions into an interactive menu loop so you can actually use them from the terminal.
Wire Up the Menu Loop
You have all the individual operations built. Each function handles one specific task: adding, listing, searching, and deleting contacts.
Right now, there's no way to actually run the application. You need a central loop that shows a menu, accepts the user's choice, and calls the right function. In this step, you'll create a main function that ties everything together using a dictionary of tuples and a while True loop.
In this step, get ready to:
- Create a main function with a menu dictionary and dispatch loop.
- Add the if __name__ == "__main__" entry point guard.
- Test the full application flow end-to-end.
Create the main function with a menu loop
Python doesn't have a built-in entry point like public static void main in Java. Instead, you define a regular function (conventionally called main) and call it yourself.
- In contacts.py, scroll to the bottom of the file (after the delete_contact function) and add the following main function:
def main():
"""Main application loop."""
print("=== Contact Book ===")
contacts = load_contacts()
menu = {
"1": ("Add contact", add_contact),
"2": ("List contacts", list_contacts),
"3": ("Search contacts", search_contacts),
"4": ("Delete contact", delete_contact),
"5": ("Quit", None),
}
while True:
print("\nMenu:")
for key, (label, _) in menu.items():
print(f" {key}. {label}")
choice = input("\nChoice: ")
if choice == "5":
print("Goodbye!")
break
elif choice in menu:
_, action = menu[choice]
action(contacts)
else:
print("Invalid choice. Try again.")
What does this code do?
The menu dictionary maps string keys ("1", "2", etc.) to tuples. Each tuple holds a display label and a reference to the function that handles that choice. Storing functions as values in a dictionary is a common Python pattern for dispatch tables.
The for key, (label, _) in menu.items() line uses tuple unpacking to pull apart each tuple directly in the loop header. The _ is a Python convention for a value you intentionally ignore (the function reference isn't needed while printing the menu).
When the user makes a choice, _, action = menu[choice] unpacks the tuple again. This time the label is ignored and the function reference is captured in action. Calling action(contacts) dispatches to the correct function without a chain of if/elif statements.
You won't see a change yet. The next chunk adds the entry point that actually calls main().
Add the entry point guard
Python files can be run directly or imported by other files. The __name__ variable tells you which situation you're in. When a file is run directly, Python sets __name__ to "__main__".
- At the very bottom of contacts.py (after the main function, with two blank lines above), add this guard:
if __name__ == "__main__":
main()
Why use this guard?
Without the guard, any code at the top level of your file runs on import. If you ever want to reuse load_contacts or search_contacts in another script, the guard prevents the menu loop from launching unexpectedly.
This pattern is standard in Python. You'll see it in almost every Python project.
- Save contacts.py.
- Run your app by entering this command in the VS Code terminal:
python contacts.py
You should see the title banner and menu printed in your terminal:
=== Contact Book ===
Menu:
1. Add contact
2. List contacts
3. Search contacts
4. Delete contact
5. Quit
Choice:
Don't see the menu?
- Make sure if __name__ == "__main__": is at the very bottom of the file with no indentation. It must be a top-level statement, not inside any function.
- Check that there are two blank lines between the end of main() and the guard. Python doesn't require them, but a syntax error above the guard can silently prevent execution.
- Confirm you saved the file before running the command.
Help me figure out why my Contact Book menu isn't appearing.
Test the full application flow
Your Contact Book is now fully wired. Time to verify every operation works and that data persists between runs.
- With the app running, type 1 and press Enter to add a contact.
- Enter a name, phone number, and email when prompted.
- Add a second and third contact using option 1 again.
- Type 2 to list all contacts. You should see a formatted table with numbered rows.
- Type 3 to search. Enter part of a name and confirm the results appear.
- Type 4 to delete a contact by its number. Then list contacts again to confirm it was removed.
- Type 5 to quit.
Now verify persistence. Run the app again:
python contacts.py
- Type 2 to list contacts. You should see the contacts you added in the previous session.
Where is your data stored?
When you added your first contact, the app created a contacts.json file in the same folder. Open it in VS Code to see the raw JSON data. This is how your contacts survive between sessions.
✔️ Awesome, I've got everything!
Great. Double check you've saved contacts.py and that your app runs cleanly with no errors.
ⓧ I'd like to double check the full code
Here's the complete contacts.py file. Compare yours against it:
import json
import os
CONTACTS_FILE = "contacts.json"
def load_contacts():
"""Load contacts from JSON file."""
if not os.path.exists(CONTACTS_FILE):
return []
with open(CONTACTS_FILE, "r") as f:
return json.load(f)
def save_contacts(contacts):
"""Save contacts to JSON file."""
with open(CONTACTS_FILE, "w") as f:
json.dump(contacts, f, indent=2)
def add_contact(contacts):
"""Add a new contact."""
name = input("Name: ")
phone = input("Phone: ")
email = input("Email: ")
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
save_contacts(contacts)
print(f"\n'{name}' added successfully!")
def list_contacts(contacts):
"""Display all contacts."""
if not contacts:
print("\nNo contacts found.")
return
print(f"\n{'Name':<20} {'Phone':<15} {'Email'}")
print("-" * 55)
for i, contact in enumerate(contacts, start=1):
print(f"{i}. {contact['name']:<17} {contact['phone']:<15} {contact['email']}")
def search_contacts(contacts):
"""Search contacts by name."""
query = input("Search: ").lower()
results = [c for c in contacts if query in c["name"].lower()]
if not results:
print(f"\nNo contacts matching '{query}'.")
return
print(f"\nFound {len(results)} result(s):")
for contact in results:
print(f" - {contact['name']}: {contact['phone']} | {contact['email']}")
def delete_contact(contacts):
"""Delete a contact by number."""
list_contacts(contacts)
if not contacts:
return
try:
index = int(input("\nEnter contact number to delete: ")) - 1
if 0 <= index < len(contacts):
removed = contacts.pop(index)
save_contacts(contacts)
print(f"\n'{removed['name']}' deleted.")
else:
print("\nInvalid number.")
except ValueError:
print("\nPlease enter a valid number.")
def main():
"""Main application loop."""
print("=== Contact Book ===")
contacts = load_contacts()
menu = {
"1": ("Add contact", add_contact),
"2": ("List contacts", list_contacts),
"3": ("Search contacts", search_contacts),
"4": ("Delete contact", delete_contact),
"5": ("Quit", None),
}
while True:
print("\nMenu:")
for key, (label, _) in menu.items():
print(f" {key}. {label}")
choice = input("\nChoice: ")
if choice == "5":
print("Goodbye!")
break
elif choice in menu:
_, action = menu[choice]
action(contacts)
else:
print("Invalid choice. Try again.")
if __name__ == "__main__":
main()
Secret mission
Add Contact Categories and CSV Export
Your Contact Book works, but every contact sits in one flat list. What if you could tag contacts by category (work, personal, family) and export the whole book to a spreadsheet-friendly CSV file?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- contact-book project folder (contains contacts.py, contacts.json, and contacts.csv).
- Python 3.14 (installed in Step 1).
- VS Code Python extension (ms-python.python).
Keep everything running
No action needed. Choose this if you plan to keep building on your Contact Book or want to reference the code later.
- Your contact-book folder stays on your Desktop.
- Python and the VS Code extension remain installed and available for future projects.
Pause - I'll come back to this later
There are no running processes to stop. Your Contact Book only runs when you explicitly execute python contacts.py.
- Close VS Code to free up memory.
- Your files will be waiting in the contact-book folder on your Desktop when you return.
Delete - I don't want to use this again
Remove all project files and optionally uninstall the tools you set up.
- Delete the project folder by running one of the following commands in your terminal.
macOS
rm -rf ~/Desktop/contact-book
Windows
rmdir /s /q %USERPROFILE%\Desktop\contact-book
- To uninstall the VS Code Python extension, press Ctrl+Shift+X to open the Extensions panel, find Python by Microsoft, click the gear icon, and select Uninstall.
- To uninstall Python on Windows, open Settings, select Apps, then Installed apps. Search for Python 3.14 and click Uninstall.
- To uninstall Python on macOS, delete the Python framework folder and the symlinks it created in /usr/local/bin/. Check the ReadMe file in your /Applications/Python 3.14/ folder for the exact steps.
Nice Work!
Nice Work!
Nice work! You just built a fully interactive Contact Book CLI application in Python from scratch.
You've learned how to:
- Build an interactive CLI using Python data structures (lists and dictionaries) with f-strings and format specifiers for clean terminal output.
- Write list comprehensions for filtering and searching data, and handle errors with try/except blocks.
- Implement persistent storage with the json module and context managers, and structure a program using functions, a main loop, and the if __name__ == "__main__" guard.
- Secret Mission: Added contact categories, category filtering, and CSV export using Python's standard library.
Ready to quiz yourself?