Build a Python Contact Book
Build an interactive Python contact book with menus, validation, and search.
Introduction
30 Second Summary
Contact details become hard to manage as your list grows. A clear structure keeps each person's information together.
In this project, you will build an interactive contact book in PyCharm using Python. Your program will collect contact details through a menu.
What You'll Build
Your PyCharm Run tool window will show a working contact menu with saved details arranged as numbered rows.
By the end of this project, you'll have:
- An interactive menu that lets you add several contacts during one session.
- A readable contact list that shows each person's name, phone number, and email address in a numbered row.
- Clear input validation that explains empty names and unsupported menu choices while keeping the program running.
- Secret Mission: Add a case-insensitive search that finds contacts using part of a name.
Are there any prerequisites?
You need Python 3.9 or later plus PyCharm. The project uses PyCharm's free core functionality on Windows.
Before We Start
Before the hands-on work begins, this is your moment to commit to the contact book you are building. You will use one interactive program to strengthen your Python fundamentals.
Set Up the Contact Book
Your interactive contact book needs a project that runs from the start. Every later Python concept needs an immediate place to produce visible output.
PyCharm creates that home through a Pure Python project. Its default Project venv keeps the project's interpreter inside a virtual environment.
In this step, get ready to:
- Create the python-contact-book project with the default Project venv.
- Add contact_book.py to the project.
- Run the file to confirm its readiness output.
Create the PyCharm project
A PyCharm project keeps your script beside its interpreter configuration. The python-contact-book folder becomes the home for every change in this lab.
- Select New Project on PyCharm's Welcome screen.
- Choose Pure Python as the project type.
- Enter python-contact-book as the final folder name in the project location path.
- Keep the default Project venv option.
- Select Create.
You should see python-contact-book in the Project tool window. That gives every later edit a ready interpreter.
Project Did Not Open?
- Confirm the project type shows Pure Python.
- Check that the location path ends with python-contact-book.
- Ask for help with creating the PyCharm project.
Add the Python file
Python source code lives in files ending with .py. Creating contact_book.py gives the contact book one runnable module.
- Right-click python-contact-book in the Project tool window.
- Select File | New ....
- Choose Python File.
- Enter contact_book as the file name.
- Press Enter.
You now have the contact book's starting file in place. The editor should show contact_book.py as an empty Python file.
Cannot See the Python File?
- Expand python-contact-book in the Project tool window.
- Confirm the file name is contact_book.py.
- Ask for help with finding the Python file.
Run the readiness message
The print function gives your program its first visible result. This readiness message proves that PyCharm can run the file with the project interpreter.
- Make contact_book.py print its readiness message by entering this code:
print("Contact Book ready")
What Does This Code Do?
- The print function sends text to the Run tool window.
- The quoted text provides a clear readiness message for this first run.
Before you run the file, what line do you expect Python to print?
- Save contact_book.py.
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to run the selected file.
You should see Contact Book ready in the Run tool window. Your first contact book output now proves the full project path works.
Readiness Message Missing?
- Select contact_book.py in the Project tool window before using the run shortcut.
- Compare the file contents against the full-code tab below.
- Ask for help with running the readiness message.
✔️ Awesome, I've got everything!
Your saved file contains the single readiness line. Its output appears in the Run tool window.
ⓧ I'd like to double check the full code
print("Contact Book ready")
Your first Python output is live. Next, you will build a contact card from one person's details.
Build One Contact Card
Your PyCharm project already runs a Python file. That working loop gives every new concept an immediate place to produce visible output.
A single contact card is the smallest useful slice of the contact book. It connects typed input to structured storage before formatted output reads the values back.
In this step, get ready to:
- Collect a name, phone number, and email address from the Run tool window.
- Group the three values in a dictionary.
- Print a formatted one-contact card.
Collect the contact details
The input() function pauses the script for a response. The strip() method removes surrounding whitespace from that response.
- In contact_book.py, select print("Contact Book ready").
- Replace the selected line by pasting this code:
name = input("Name: ").strip()
phone = input("Phone: ").strip()
email = input("Email: ").strip()
What Does This Code Do?
- Each input() call displays a prompt in the Run tool window.
- The name, phone, and email variables keep the three responses available.
- Each strip() call removes extra whitespace from the start or end of a response.
- Save contact_book.py.
- Run the active file by pressing Ctrl+Shift+F10.
- Enter Ada Lovelace at the Name: prompt.
- Enter 555-0100 at the Phone: prompt.
- Enter ada@example.com at the Email: prompt.
You will see the three prompts appear in order. PyCharm finishes the run after the email response.
The Script Does Not Ask for Every Value?
Make sure all three lines start at the far left of contact_book.py. Check that every prompt has matching quotes and parentheses.
Ask for targeted help with fixing the input lines.
Store the details in a dictionary
A dictionary groups related values under named keys. The contact can then retrieve each value through a clear label.
- In contact_book.py, place the cursor below the email input line.
- Store the three variables in contact by pasting this code:
contact = {
"name": name,
"phone": phone,
"email": email,
}
How Does the Dictionary Work?
- The contact variable holds one set of related details.
- The name, phone, and email keys identify the stored values.
- A key such as contact['name'] gives the program a direct path to one value.
- Save contact_book.py.
- Run the active file by pressing Ctrl+Shift+F10.
- Enter a value at each of the three prompts.
The run should finish successfully after the email response. This confirms that Python created the dictionary without a syntax problem.
The Dictionary Stops the Run?
Check that the opening brace has a matching closing brace. Make sure every key has a colon before its variable.
Ask for targeted help with repairing the contact dictionary.
Print the contact card
A formatted string literal inserts stored values into a line of text. Reading from contact also proves that the dictionary contains the responses.
- In contact_book.py, place the cursor below the closing brace of contact.
- Print the contact card by pasting this code:
print("\nContact card")
print(f"{contact['name']} | {contact['phone']} | {contact['email']}")
What Does This Code Do?
- The first print() call adds the Contact card heading below the input prompts.
- The f prefix lets the second string insert expressions inside braces.
- The dictionary lookups retrieve the saved name, phone number, and email address.
- The pipe characters separate the three values in one readable row.
- Save contact_book.py.
Before you run the script, which value do you expect to appear first? Keep your prediction in mind.
- Run the active file by pressing Ctrl+Shift+F10.
- Enter Ada Lovelace at the name prompt.
- Enter 555-0100 at the phone prompt.
- Enter ada@example.com at the email prompt.
You will see Contact card followed by Ada Lovelace | 555-0100 | ada@example.com in the Run tool window.
You have the first useful slice working. Your typed details now return as one structured contact card.
The Contact Card Does Not Match?
Check that both print() lines sit below the dictionary's closing brace. Match each dictionary key to the spelling used when contact was created.
Ask for targeted help with fixing the formatted contact card.
✔️ Awesome, I've got everything!
Your saved file now collects three values, groups them in one contact, and prints the completed card.
ⓧ I'd like to double check the full code
- Compare the full contents of contact_book.py with this reference:
name = input("Name: ").strip()
phone = input("Phone: ").strip()
email = input("Email: ").strip()
contact = {
"name": name,
"phone": phone,
"email": email,
}
print("\nContact card")
print(f"{contact['name']} | {contact['phone']} | {contact['email']}")
Your contact book can now collect one person and display the saved details. Next, you will keep the program running so it can manage multiple contacts.
Turn One Contact into a Menu
Your one-contact card now runs in PyCharm. It ends after one entry, so you cannot keep adding contacts in the same session.
A list can hold several contact dictionaries while the program runs.
A loop can keep the menu available until you choose to exit. Testing an unsupported choice will reveal how the current menu responds.
In this step, get ready to:
- Keep the contact book menu running until you choose to exit.
- Store multiple contact dictionaries in a list during one session.
- Display numbered contacts before testing an unsupported menu choice.
Build the repeating menu
The contacts list starts empty each time the script runs. The while True loop keeps presenting the menu until break ends the session.
- Click inside the contact_book.py editor from the previous step.
- Drag from the first line through the final line to select the existing one-contact script.
- Replace the selected script with a repeating add-or-exit menu by pasting this code:
contacts = []
while True:
print("\nContact Book")
print("1. Add contact")
print("2. List contacts")
print("3. Exit")
choice = input("Choose 1, 2, or 3: ").strip()
if choice == "1":
name = input("Name: ").strip()
phone = input("Phone: ").strip()
email = input("Email: ").strip()
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
print(f"{name} was added.")
elif choice == "3":
print("Goodbye!")
break
What Does This Code Do?
- The contacts list holds every contact added during the current run.
- The while True loop prints the menu repeatedly.
- The choice variable stores the stripped menu input.
- The first branch builds a contact dictionary before append adds it to the list.
- The final branch uses break to leave the loop.
- Save contact_book.py.
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to run the script.
- Enter 1 at the menu prompt.
- Enter Ada Lovelace at the name prompt.
- Enter 555-0101 at the phone prompt.
- Enter ada@example.com at the email prompt.
You have your first continuous session. The Run tool window shows Ada Lovelace was added. before the menu returns.
- Enter 3 at the menu prompt.
The Run tool window prints the exit message. The script then stops cleanly.
Menu Not Repeating?
Check that every menu line sits inside the while True block. Confirm that contacts = [] sits above the loop.
Ask for help with checking the menu indentation.
Add numbered contact listing
The listing branch reads every dictionary already stored in contacts. The enumerate(contacts, start=1) call pairs each contact with a number beginning at one.
- Locate the line elif choice == "3": near the bottom of contact_book.py.
- Insert the numbered listing branch directly above that line by pasting this code:
elif choice == "2":
print("\nSaved contacts")
for number, contact in enumerate(contacts, start=1):
print(
f"{number}. {contact['name']} | "
f"{contact['phone']} | {contact['email']}"
)
How Does Numbered Listing Work?
- The elif branch runs when choice contains 2.
- The number variable receives the current row number.
- The contact variable receives one dictionary from the list during each pass.
- The formatted strings combine the row number with the stored name, phone number, and email address.
- Save contact_book.py.
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to start a fresh session.
- Enter 1 at the menu prompt.
- Enter Lin Chen at the name prompt.
- Enter 555-0103 at the phone prompt.
- Enter lin@example.com at the email prompt.
- Enter 2 at the next menu prompt.
Your numbered listing is working. The Run tool window shows 1. Lin Chen | 555-0103 | lin@example.com under the saved contacts heading.
- Enter 3 to end this test session.
Contact Row Missing?
Confirm that the listing branch aligns with the other elif branch. Check that the for loop sits one indentation level inside the listing branch.
Ask for help with fixing the numbered contact listing.
✔️ Awesome, I've got everything!
Your complete menu code is ready for the session test.
ⓧ I'd like to double check the full code
contacts = []
while True:
print("\nContact Book")
print("1. Add contact")
print("2. List contacts")
print("3. Exit")
choice = input("Choose 1, 2, or 3: ").strip()
if choice == "1":
name = input("Name: ").strip()
phone = input("Phone: ").strip()
email = input("Email: ").strip()
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
print(f"{name} was added.")
elif choice == "2":
print("\nSaved contacts")
for number, contact in enumerate(contacts, start=1):
print(
f"{number}. {contact['name']} | "
f"{contact['phone']} | {contact['email']}"
)
elif choice == "3":
print("Goodbye!")
break
What Does This Reference Show?
This reference shows the exact cumulative contents of contact_book.py. Each choice branch belongs inside the same menu loop.
Test the complete session
A session test checks whether contacts remain available across several trips through the menu. It also probes how the loop behaves beyond its supported choices.
Before you run the full check, do you think every input will receive a response?
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to start the session.
- Enter 1 at the menu prompt.
- Enter Ada Lovelace at the name prompt.
- Enter 555-0101 at the phone prompt.
- Enter ada@example.com at the email prompt.
The Run tool window confirms that Ada Lovelace was added. The menu remains active.
- Enter 1 at the menu prompt.
- Enter Grace Hopper at the name prompt.
- Enter 555-0102 at the phone prompt.
- Enter grace@example.com at the email prompt.
- Enter 2 at the next menu prompt.
You have the multi-contact flow working. The first row reads 1. Ada Lovelace | 555-0101 | ada@example.com.
The second row reads 2. Grace Hopper | 555-0102 | grace@example.com. Both contacts remain available in the same session.
Before you test a choice outside the menu, what response do you expect from the current branches?
- Enter 9 at the menu prompt.
The menu returns without any explanation. That silent response is the intended shortfall in this version.
- Enter 3 to exit the contact book.
The Run tool window prints the exit message before the program stops. Your menu can now add several contacts, list numbered rows, and exit cleanly.
Your contact book now preserves multiple entries for the whole session. Next, you will organize this menu into functions and give unsupported input a clear response.
Refactor and Validate Input
Your contact book now keeps several contacts alive during one run in PyCharm. However, the menu still carries every detail inside one long loop.
An unsupported choice still returns silently. You will refactor repeated behavior into functions.
Explicit validation will reject empty names. It will also explain unsupported choices.
In this step, get ready to:
- Organize the menu behavior into named functions.
- Add guard clauses for empty data.
- Report unsupported choices from main().
Extract the menu and add-contact functions
A function gives one behavior a reusable name. The menu loop can call that behavior without carrying every implementation detail.
- Place the cursor above contacts = [] in contact_book.py.
- Add the menu function by pasting this code:
def show_menu():
print("\nContact Book")
print("1. Add contact")
print("2. List contacts")
print("3. Exit")
What Does This Function Do?
- The show_menu() function keeps every menu label in one place.
- Each print() call displays one part of the menu.
- The loop can call this function whenever it needs to display the choices.
- Select the four menu print() lines inside the original while True loop.
- Replace the selected lines with show_menu() at the same indentation as choice.
- Save contact_book.py.
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to run the file.
You will see the same three menu choices in the Run tool window. The output now comes from show_menu().
Menu Missing or Repeating Incorrectly?
Check that show_menu() remains inside the while True loop. Confirm that its indentation matches the choice line.
Ask for help with fixing the menu function call.
The add-contact branch also has a clear responsibility. Moving it into add_contact(contacts) creates a natural place to reject an empty name.
- Place the cursor below show_menu().
- Add the contact function by pasting this code:
def add_contact(contacts):
print("\nAdd a contact")
name = input("Name: ").strip()
if not name:
print("Name cannot be empty.")
return
phone = input("Phone: ").strip()
email = input("Email: ").strip()
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
print(f"{name} was added.")
What Does This Function Do?
- The contacts parameter gives the function access to the current contact list.
- The if not name guard detects an empty string after strip() removes surrounding whitespace.
- The early return stops the function before it asks for more details.
- A valid contact becomes a dictionary before append() adds it to the list.
- Select every line beneath if choice == "1": through print(f"{name} was added.").
- Replace the selected lines with add_contact(contacts) indented beneath the branch.
- Save contact_book.py.
- Press Ctrl+Shift+F10 to run the file.
- Enter 1 at the menu prompt.
- Press Enter at the name prompt without typing any characters.
You will see Name cannot be empty. before the menu returns. The program keeps running because the guard exits only add_contact(contacts).
Empty Name Still Accepted?
Confirm that if not name: appears before the phone prompt. Check that return is indented inside that guard.
Ask for help with fixing the empty-name guard.
Protect empty contact data
A guard clause handles an edge case before the main behavior begins. The list function can use one to explain why there are no rows to display.
- Place the cursor below add_contact(contacts).
- Add the list function by pasting this code:
def list_contacts(contacts):
print("\nSaved contacts")
if not contacts:
print("No contacts saved yet.")
return
for number, contact in enumerate(contacts, start=1):
print(
f"{number}. {contact['name']} | "
f"{contact['phone']} | {contact['email']}"
)
What Does This Function Do?
- The if not contacts guard detects an empty list.
- The early return prevents the numbering loop from running when there are no contacts.
- The enumerate(contacts, start=1) call gives each saved contact a number beginning with 1.
- Select every line beneath elif choice == "2": through the closing parenthesis of the formatted print() call.
- Replace the selected lines with list_contacts(contacts) indented beneath the branch.
- Save contact_book.py.
- Press Ctrl+Shift+F10 to start a fresh run.
- Enter 2 at the menu prompt.
You will see No contacts saved yet. before the menu returns. The empty-list path now gives you a clear result.
No Empty-List Message?
Confirm that the menu branch calls list_contacts(contacts). Check that the empty-list guard appears before the for loop.
Ask for help with diagnosing the empty-list guard.
Build the validated menu loop
The main() function owns the shared contact list. It also coordinates every menu choice.
An else branch gives unsupported input an explicit response. The main-module guard starts the application when you run contact_book.py directly.
- Select everything from contacts = [] through the original break line.
- Replace the selected code with the validated main loop:
def main():
contacts = []
while True:
show_menu()
choice = input("Choose 1, 2, or 3: ").strip()
if choice == "1":
add_contact(contacts)
elif choice == "2":
list_contacts(contacts)
elif choice == "3":
print("Goodbye!")
break
else:
print("Invalid choice. Enter 1, 2, or 3.")
if __name__ == "__main__":
main()
What Does This Code Do?
- The main() function creates one contacts list for the entire session.
- Each supported branch delegates its work to a focused function.
- The else branch reports every unsupported choice before the loop repeats.
- The if __name__ == "__main__": guard calls main() when the file runs as the main program.
Before you run the finished program, which messages do you expect after an empty name and an unsupported menu choice?
- Save contact_book.py.
- Select contact_book.py in the Project tool window.
- Press Ctrl+Shift+F10 to run the file.
- Enter 1 at the menu prompt.
- Press Enter at the name prompt without typing any characters.
You will see Name cannot be empty. before the menu appears again.
- Enter 9 at the next menu prompt.
You will see Invalid choice. Enter 1, 2, or 3. before the program offers the menu again.
- Enter 3 to exit the menu.
You will see Goodbye! before the program stops. That is the refactor complete: every menu outcome now has visible feedback.
Validation Messages Missing?
Check that the empty-name guard remains inside add_contact(contacts). Confirm that the invalid-choice else aligns with the menu's if statement.
Ask for help with troubleshooting the completed validation flow.
✔️ Awesome, I've got everything!
Great work. Save contact_book.py once more so every function and validation message is stored.
ⓧ I'd like to double check the full code
def show_menu():
print("\nContact Book")
print("1. Add contact")
print("2. List contacts")
print("3. Exit")
def add_contact(contacts):
print("\nAdd a contact")
name = input("Name: ").strip()
if not name:
print("Name cannot be empty.")
return
phone = input("Phone: ").strip()
email = input("Email: ").strip()
contact = {
"name": name,
"phone": phone,
"email": email,
}
contacts.append(contact)
print(f"{name} was added.")
def list_contacts(contacts):
print("\nSaved contacts")
if not contacts:
print("No contacts saved yet.")
return
for number, contact in enumerate(contacts, start=1):
print(
f"{number}. {contact['name']} | "
f"{contact['phone']} | {contact['email']}"
)
def main():
contacts = []
while True:
show_menu()
choice = input("Choose 1, 2, or 3: ").strip()
if choice == "1":
add_contact(contacts)
elif choice == "2":
list_contacts(contacts)
elif choice == "3":
print("Goodbye!")
break
else:
print("Invalid choice. Enter 1, 2, or 3.")
if __name__ == "__main__":
main()
Secret mission
Search Contacts by Name
Add a search option to your contact book. Match lowercase name fragments while handling empty searches and missing contacts.
Clean Up Your Resources
Clean Up Your Resources
Your contact book runs locally with Python inside PyCharm. The required path costs $0, so keeping the project creates no ongoing charges.
Resources you used:
- The interactive contact book process in PyCharm's Run tool window.
- The local python-contact-book folder containing contact_book.py.
- The project-specific Python environment created through Project venv.
Keep everything running
Leave the project as it is if you want to keep practicing with the contact book.
- Keep the python-contact-book folder on your computer.
- Continue testing contact_book.py in the Run tool window.
Your saved contacts remain available during the current run. Exiting the program clears them because the project does not store contacts in a file.
Pause - I'll come back to this later
End the current contact book session to free its memory. Your code stays available for the next time you return.
- Save contact_book.py in PyCharm.
- Enter 4 in the Run tool window to exit the contact book.
The Run tool window prints Goodbye!. This confirms that the interactive process has stopped cleanly.
- Close PyCharm.
- Keep the python-contact-book folder on your computer.
Your script and project-specific Python environment remain available for another session.
Delete - I don't want to use this again
Deleting the project folder removes the finished script plus its project-specific environment. Recovery may depend on the Recycle Bin.
- Save contact_book.py in PyCharm.
- Copy contact_book.py to another folder if you want to keep the finished script.
- Enter 4 in the Run tool window to exit the contact book.
The Run tool window prints Goodbye!. The program is no longer holding the project open.
- Right-click the python-contact-book folder in PyCharm's Project tool window.
- Choose the context-menu option that reveals the selected folder in Windows.
- Close PyCharm.
- Move up one folder in the Windows folder window.
- Delete the python-contact-book folder.
- Empty the Recycle Bin to remove the folder completely.
The python-contact-book folder no longer appears on your computer. Its contact_book.py file and project-specific environment have also been removed.
Nice Work!
Nice Work!
You did it! Your interactive Python contact book now runs in PyCharm through a complete menu.
You've learned how to:
- Capture user input in a contact dictionary for each person.
- Keep every contact in a Python list for the whole session. Control the interactive menu with conditionals plus a loop. Display saved contacts as numbered rows.
- Organize repeated behavior into small functions. Use explicit validation to explain empty names or unsupported menu choices.
- Secret Mission: Add case-insensitive name search that finds contacts from a partial name.
Ready to quiz yourself? Test what you remember.