Build a CLI Contact Book in C

Build a terminal contact book in C with structs, file I/O, and safe input handling.

Introduction

30 Second Summary

Every useful app has the same core job: store data, find it quickly, and keep it safe between sessions. Your phone's contact list does this so seamlessly that you never think about the code behind it.

In this project, you will build a command-line contact book in C. You will model contacts using structs, handle input safely, and persist everything to a file so nothing is lost between runs.

What You'll Build

You will run a single command, see an interactive menu appear in your terminal, and add contacts by name and phone number that survive even after you quit and restart the program.

By the end of this project, you'll have:

  • Add contacts by typing a name and phone number, then view them in a neatly formatted table.
  • Search contacts by partial name so that typing "jo" finds both "John" and "Joanna".
  • Quit and relaunch the program to find all your contacts still there, saved automatically to a file on disk.
  • Secret Mission: Edit existing contacts and sort your list alphabetically using qsort.

Are there any prerequisites?

This project assumes you know basic C syntax, including variables, loops, functions, and printf. You should also be familiar with compiling C programs from the terminal.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters. You're building a CLI Contact Book in C to practice the fundamentals that separate intro-level C from confident, job-ready C: structs for modeling data, safe string handling with fgets, and file I/O for persistence.

Set Up Your Project

Before writing any application code, you need to confirm your C toolchain works. A broken compiler means nothing else in this project will run.

In this step, you will verify that clang is ready, create a test file to prove you can compile and run C, and set up VS Code with C language support.

In this step, get ready to:
  • Verify your C compiler is installed and working.
  • Create a project directory with a test C file.
  • Install the C/C++ extension in VS Code.
Verify your C compiler

Your C compiler turns .c source files into runnable programs. On macOS, the compiler is clang, installed as part of the Xcode Command Line Tools.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Verify your compiler is installed by running this command:
clang --version

✔️ I see a version number

Your compiler is ready to go.

ⓧ Command not found

You need to install the Xcode Command Line Tools, which include clang.

  • Install the Command Line Tools by running:
xcode-select --install
  • Follow the prompts in the dialog that appears to complete the installation.
  • Once the installation finishes, run clang --version again to confirm you see a version number.

What about gcc?

On macOS, gcc is actually an alias for Apple clang. Running either command uses the same compiler under the hood.

Create the project directory and test file

You need a dedicated folder to hold all your project files. You will also create a small test file to confirm you can compile and run C code end to end.

  • Create your project directory and move into it by running these commands:
mkdir ~/contact-book
cd ~/contact-book
  • Create a file called hello.c with a simple test program by running this command:
cat > hello.c << 'EOF'
#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}
EOF

What does this command do?

cat > hello.c << 'EOF' is called a heredoc. It tells the terminal to write everything between the two EOF markers into a new file called hello.c.

The C program itself includes <stdio.h> for the printf function, then prints "Hello, C!" to the terminal.

  • Confirm the file was created by running:
ls

You should see hello.c listed in the output.

Don't see hello.c?

Make sure you ran the cd ~/contact-book command before creating the file. If the directory does not exist, the cd command will fail silently and you will be in the wrong folder.

Run pwd to check your current directory. It should end with /contact-book.

help me troubleshoot why hello.c was not created in my contact-book directory

Install the C/C++ extension and open your project

The C/C++ extension gives VS Code syntax highlighting, error detection, and code navigation for C files. Without it, VS Code treats your .c files as plain text.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open the Extensions view by pressing Shift+Cmd+X (macOS) or Ctrl+Shift+X (Windows).
  • Type C/C++ in the search bar.
  • Find the extension called C/C++ by Microsoft and click Install.

What does this extension do?

The C/C++ extension (ms-vscode.cpptools) adds IntelliSense (auto-complete and inline errors), code navigation, and debugging support. It automatically detects your installed clang compiler for IntelliSense configuration.

Now open your project folder so VS Code can see your files.

  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your home directory and select the contact-book folder, then click Open.
  • In the Explorer sidebar on the left, click hello.c to open it.

You should see your C code with colored syntax highlighting. Keywords like int and return appear in a different color from the strings and function names.

No syntax highlighting?

Check the bottom-right corner of VS Code. It should show C as the language mode. If it shows Plain Text, click it and select C from the list.

If the C/C++ extension still does not appear as installed, try reloading VS Code by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), then typing Reload Window and pressing Enter.

help me get syntax highlighting working for C files in VS Code

Compile and run your test file

Everything is in place. Time to prove the full toolchain works by compiling and running your test program.

Before you run the next command, what do you expect to see in the terminal?

  • Switch back to your terminal. Make sure you are inside the contact-book directory (run cd ~/contact-book if you are not sure).
  • Compile and run your test program by running this command:
clang -Wall -Wextra -std=c17 -o hello hello.c && ./hello

The terminal prints Hello, C!. Your compiler, your project directory, and your editor are all working.

What do these compiler flags do?

  • -Wall enables a broad set of compiler warnings so you catch potential issues early.
  • -Wextra enables additional warnings beyond -Wall for even stricter checking.
  • -std=c17 tells clang to compile using the ISO C17 standard.
  • -o hello names the output binary hello instead of the default a.out.

Seeing a compile error?

Make sure you are in the ~/contact-book directory. Run pwd to check. If you see a different path, run cd ~/contact-book and try again.

If the error mentions a syntax problem, open hello.c in VS Code and compare it character for character against the code below. Watch for missing semicolons and mismatched braces.

help me fix a compile error in hello.c

✔️ Awesome, I've got everything!

Great. Double check you have saved hello.c in VS Code.

ⓧ I'd like to double check the full code

Here is the complete hello.c file. Compare it against yours in VS Code.

#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}

Your toolchain is confirmed working. Next up, you will build the real contact book with a data model and an interactive menu.

Build the Contact Struct and Menu

Your compiler works and VS Code is ready. Now it is time to build the actual contact book.

Every contact has a name and a phone number. In C, the way you bundle related data together is with a struct. In this step, you will define a Contact struct, write functions to add and display contacts, and wire everything together with an interactive menu loop.

In this step, get ready to:
  • Define the Contact struct and safe input helpers.
  • Write functions to add, view, and navigate contacts through a menu.
  • Compile and run the program to add and view contacts interactively.
Create the contact struct and helper function

Before you can add or display contacts, you need a data model. A C struct groups a contact's name and phone number into a single unit.

  • In VS Code, create a new file called contact_book.c inside the contact-book folder.
  • Add the includes, constants, struct definition, global array, and a trim_newline helper by copying this code into the file:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define MAX_CONTACTS 100
#define NAME_LEN 50
#define PHONE_LEN 20
#define FILENAME "contacts.txt"

// A Contact groups a name and phone number together
typedef struct {
    char name[NAME_LEN];
    char phone[PHONE_LEN];
} Contact;

// Global array to hold all contacts, and a count tracker
Contact contacts[MAX_CONTACTS];
int contact_count = 0;

// Strips the trailing newline that fgets leaves behind
void trim_newline(char *str) {
    size_t len = strlen(str);
    if (len > 0 && str[len - 1] == '\n') {
        str[len - 1] = '\0';
    }
}

What does this code do?

  • The #define lines set constants for array sizes and the save file name. Using named constants means you only change one line if you want to support more contacts or longer names.
  • The Contact struct bundles a name and phone into one unit. Each field is a character array with a fixed max length.
  • trim_newline removes the trailing newline character that fgets includes when reading input. Without it, every name and phone number would have an invisible newline stuck to the end.

Now you need a function that actually collects contact data from the user.

  • Add the add_contact function below trim_newline by copying this code:
// Prompts the user for a name and phone, then stores them
void add_contact(void) {
    if (contact_count >= MAX_CONTACTS) {
        printf("Contact book is full!\n");
        return;
    }

    printf("Enter name: ");
    fgets(contacts[contact_count].name, NAME_LEN, stdin);
    trim_newline(contacts[contact_count].name);

    printf("Enter phone: ");
    fgets(contacts[contact_count].phone, PHONE_LEN, stdin);
    trim_newline(contacts[contact_count].phone);

    contact_count++;
    printf("Contact added!\n");
}

What does this code do?

  • The guard if (contact_count >= MAX_CONTACTS) prevents writing past the end of the array. Going out of bounds in C does not throw an error. It silently corrupts memory.
  • fgets reads up to NAME_LEN - 1 characters from stdin and always null-terminates the result. This is safer than scanf, which cannot limit input length reliably.
  • After reading, trim_newline cleans up the trailing newline so the stored data is just the text the user typed.

Why fgets instead of scanf?

scanf with %s cannot limit input length reliably. It also leaves newlines in the stdin buffer, which causes the next read to behave unexpectedly.

fgets reads up to n-1 characters, always null-terminates, and includes the newline (which you strip with trim_newline). This is the standard safe-input pattern in C.

  • Save contact_book.c.
  • Verify the code compiles so far by running this compile-only check in your terminal:
clang -c -Wall -Wextra -std=c17 contact_book.c

If the command finishes with no output at all, your code compiled cleanly. You should also see a contact_book.o file appear in your folder.

What does the -c flag do?

The -c flag tells clang to compile to an object file without linking. Since you have not written a main function yet, a full compile would fail. This lets you check for syntax errors early.

Seeing compiler errors?

  • Check for missing semicolons at the end of the struct definition and after the closing brace of the typedef.
  • Make sure every opening brace { has a matching closing brace }.
  • Confirm the #include lines are at the very top of the file with no typos.

Help me debug my compiler errors in contact_book.c

Write the display and menu functions

Your program can store contacts, but it cannot show them yet. The next function prints every contact in a clean, formatted table.

  • Add the view_contacts function below add_contact by copying this code:
// Prints all contacts in a formatted table
void view_contacts(void) {
    if (contact_count == 0) {
        printf("No contacts found.\n");
        return;
    }

    printf("\n%-5s %-30s %-20s\n", "#", "Name", "Phone");
    printf("-----------------------------------------------------------\n");
    for (int i = 0; i < contact_count; i++) {
        printf("%-5d %-30s %-20s\n", i + 1, contacts[i].name, contacts[i].phone);
    }
    printf("\n");
}

What does this code do?

  • The %-5s format specifier left-aligns a string in a 5-character-wide column. The %-30s and %-20s specifiers do the same for the name and phone columns.
  • This produces a clean table where columns line up neatly, regardless of how long each name or number is.

The last helper is the menu itself. It shows the user their options each time through the loop.

  • Add the print_menu function below view_contacts:
// Displays the main menu options
void print_menu(void) {
    printf("\n===== Contact Book =====\n");
    printf("1. Add Contact\n");
    printf("2. View All Contacts\n");
    printf("3. Quit\n");
    printf("========================\n");
    printf("Choose an option: ");
}

What does this code do?

print_menu prints the numbered options and a prompt. The last printf deliberately has no \n at the end, so the cursor stays on the same line and the user types their choice right next to the prompt.

  • Save contact_book.c.
  • Run the compile-only check again to confirm everything still compiles cleanly:
clang -c -Wall -Wextra -std=c17 contact_book.c

No output means no errors. You are ready to tie everything together with a main function.

Seeing warnings or errors?

  • Double-check that the format strings in view_contacts have matching % specifiers and arguments. Each printf call with three columns needs exactly three arguments after the format string.
  • Make sure print_menu appears after view_contacts in the file. C functions must be defined before they are called (or forward-declared).

Help me fix my view_contacts or print_menu function

Write the main loop

All the building blocks are in place. The main function ties them together with a loop that shows the menu, reads the user's choice, and calls the right function.

  • Add the main function at the bottom of contact_book.c:
int main(void) {
    char choice[10];
    int running = 1;

    // Keep looping until the user picks Quit
    while (running) {
        print_menu();
        fgets(choice, sizeof(choice), stdin);

        switch (choice[0]) {
            case '1':
                add_contact();
                break;
            case '2':
                view_contacts();
                break;
            case '3':
                printf("Goodbye!\n");
                running = 0;
                break;
            default:
                printf("Invalid option. Try again.\n");
        }
    }

    return 0;
}

What does this code do?

  • The while (running) loop keeps the program alive until the user picks Quit, which sets running to 0.
  • fgets reads the user's choice as a string. The switch statement checks only the first character (choice[0]) to determine which option was selected.
  • The default case catches any input that is not 1, 2, or 3, so the program does not crash on unexpected input.
  • Save contact_book.c.

Before you compile and run, what do you think will happen when you add two contacts and then select "View All Contacts"?

  • Compile and run the full program by running this command:
clang -Wall -Wextra -std=c17 -o contact_book contact_book.c && ./contact_book
  • The menu appears. Type 1 and press Enter to add a contact.
  • Enter a name (e.g. Alice) and press Enter.
  • Enter a phone number (e.g. 555-1234) and press Enter.
  • Add a second contact using option 1 again.
  • Type 2 and press Enter to view all contacts.

Both contacts appear in a formatted table with numbers, names, and phone numbers. Your baby contact book is alive.

  • Type 3 and press Enter to quit the program.

Program does not compile or crashes on launch?

  • If you see a linker error about main, make sure you added the main function at the very bottom of the file, after all other functions.
  • If the menu prints but input does not work, make sure you are using fgets(choice, sizeof(choice), stdin) and not scanf.
  • Check that every case in the switch ends with break;. A missing break causes execution to "fall through" into the next case.

Help me debug my contact book program

✔️ Awesome, I've got everything!

Great. Double check you have saved contact_book.c.

ⓧ I'd like to double check the full code

Your contact_book.c file should contain these sections in order. Scroll up through the chunks you built and confirm each one is present:

  • Includes (stdio.h, string.h, stdlib.h, ctype.h), four #define constants, the Contact struct typedef, global array and count, and the trim_newline function.
  • The add_contact function that uses fgets and trim_newline for both name and phone.
  • The view_contacts function with printf width specifiers for the table.
  • The print_menu function showing Add, View, and Quit options.
  • The main function with a while (running) loop, fgets for input, and a switch on choice[0].

Your contact book can add and display contacts. Next up, you will add the power to search for a contact by name and delete contacts you no longer need.

Search and Delete Contacts

Your contact book can add and display contacts, which is a great start. But a contact book you can only add to is not very useful.

In this step, you will add search and delete features. These introduce two important C patterns: case-insensitive string comparison and shifting array elements to fill a gap after deletion.

In this step, get ready to:
  • Write a case-insensitive search function using tolower and strstr.
  • Write a delete function that shifts array elements to fill gaps.
  • Discover what happens to your contacts when the program exits.
Write a lowercase string helper

When a user searches for "jo", they expect to find "John" and "Joanna". To make that work, you need to compare strings without caring about uppercase or lowercase letters.

C does not have a built-in function to lowercase an entire string. You will write a small helper that converts a string character by character.

  • In contact_book.c, find the closing brace of the trim_newline function.
  • Add the following to_lower_str function below it:
// Convert a string to lowercase for case-insensitive comparison
void to_lower_str(char *dest, const char *src) {
    while (*src) {
        *dest = tolower((unsigned char)*src);
        dest++;
        src++;
    }
    *dest = '\0';
}

What does this code do?

This function walks through every character in src, converts it to lowercase with tolower, and writes the result into dest.

The cast to (unsigned char) is a safety measure. The tolower function expects a non-negative integer, and a plain char can be negative on some systems. The cast prevents undefined behavior.

  • Save contact_book.c.
Write the search function

Now you can use to_lower_str to build the actual search. The search will lowercase both the query and each contact's name, then use strstr to check if the query appears anywhere inside the name.

  • Below the view_contacts function, add the following search_contact function:
void search_contact(void) {
    char query[NAME_LEN];
    char lower_query[NAME_LEN];
    char lower_name[NAME_LEN];

    // Get the search term from the user
    printf("Enter name to search: ");
    fgets(query, NAME_LEN, stdin);
    trim_newline(query);
    to_lower_str(lower_query, query);

    // Loop through contacts comparing lowercased names
    int found = 0;
    for (int i = 0; i < contact_count; i++) {
        to_lower_str(lower_name, contacts[i].name);
        if (strstr(lower_name, lower_query) != NULL) {
            printf("%-5d %-30s %-20s\n", i + 1, contacts[i].name, contacts[i].phone);
            found = 1;
        }
    }

    if (!found) {
        printf("No contacts matching \"%s\" found.\n", query);
    }
}

What does this code do?

  • fgets reads the search query safely, then to_lower_str converts it to lowercase.
  • The loop lowercases each contact's name into lower_name, then strstr checks if the query appears as a substring anywhere inside the name.
  • If strstr returns NULL, there is no match. If it returns anything else, the query was found.
  • Save contact_book.c. You will not be able to test search yet because the menu does not call it. You will wire it up after writing the delete function.
Write the delete function

Deleting from an array in C is not as simple as removing an item from a list in Python or JavaScript. Arrays in C are contiguous blocks of memory with no built-in remove operation.

To delete an element, you shift every element after it one position to the left, overwriting the deleted slot. Then you decrement the count.

  • Below the search_contact function you just wrote, add the following delete_contact function:
void delete_contact(void) {
    // Show all contacts so the user can pick one
    view_contacts();
    if (contact_count == 0) return;

    // Get the contact number to delete
    printf("Enter contact number to delete: ");
    char buf[10];
    fgets(buf, sizeof(buf), stdin);
    int index = atoi(buf) - 1;

    if (index < 0 || index >= contact_count) {
        printf("Invalid contact number.\n");
        return;
    }

    printf("Deleted \"%s\".\n", contacts[index].name);

    // Shift all contacts after the deleted one left by one
    for (int i = index; i < contact_count - 1; i++) {
        contacts[i] = contacts[i + 1];
    }
    contact_count--;
}

How does the array shift work?

Imagine you have contacts [Alice, Bob, Carol] and you delete Bob (index 1). The loop copies Carol into Bob's slot, giving you [Alice, Carol]. Then contact_count-- shrinks the count from 3 to 2, so the old third slot is ignored.

The atoi(buf) - 1 line converts the user's input from a string to an integer and subtracts 1. The menu shows contacts starting from 1, but the array is zero-indexed.

  • Save contact_book.c.
Update the menu and main loop

Both functions are written but the menu still only shows Add, View, and Quit. You need to add the new options and update the switch statement so it calls the right function for each choice.

  • In the print_menu function, find this line:
    printf("3. Quit\n");
  • Replace that line with these three lines so Search is option 3, Delete is option 4, and Quit moves to option 5:
    printf("3. Search Contact\n");
    printf("4. Delete Contact\n");
    printf("5. Quit\n");
  • Now scroll down to the switch statement in main. Find the existing case '3': block:
            case '3':
                printf("Goodbye!\n");
                running = 0;
                break;
  • Replace that block with the following three cases, so the new options 3 and 4 call your search and delete functions, and Quit moves to option 5:
            case '3':
                search_contact();
                break;
            case '4':
                delete_contact();
                break;
            case '5':
                printf("Goodbye!\n");
                running = 0;
                break;

What changed?

The old case '3' used to handle Quit. Now case '3' calls search_contact(), case '4' calls delete_contact(), and Quit has moved to case '5'.

  • Save contact_book.c.

Seeing a compile error?

  • Make sure your to_lower_str function is placed above search_contact in the file. C reads top to bottom, so a function must be defined before it is called.
  • Check that all curly braces are balanced. Each function should have a matching { and }.
  • Make sure every case in the switch ends with break; and uses single quotes around the number character (e.g. '5', not 5).

Why won't my contact_book.c compile?

✔️ Awesome, I've got everything!

Great. Make sure you have saved contact_book.c before moving on.

ⓧ I'd like to double check the full code

Your contact_book.c is now too long to show in a single block. Scroll up and confirm these chunks are present, in this order from top to bottom:

  • The four #include headers and four #define constants, followed by the Contact struct, the global array, and contact_count.
  • trim_newline, then to_lower_str (new this step).
  • add_contact, then view_contacts (unchanged from the previous step).
  • search_contact, then delete_contact (both new this step).
  • print_menu with five options (Add, View, Search, Delete, Quit).
  • main with a switch covering cases '1' through '5' plus default.
Test search and delete
  • Compile and run your program by running this command:
clang -Wall -Wextra -std=c17 -o contact_book contact_book.c && ./contact_book
  • Add three contacts through the menu. For example, add "John Smith", "Joanna Lee", and "Alice Wong".
  • Select option 3 to search.
  • Type jo as your search query.

You should see both "John Smith" and "Joanna Lee" appear in the results, but not "Alice Wong". The search matched the substring "jo" inside both names, ignoring case.

  • Now select option 4 to delete a contact.
  • Enter the number next to one of your contacts to delete it.
  • Select option 2 to view all contacts and confirm the deleted contact is gone.

Search not finding anything?

  • Confirm that to_lower_str is defined above search_contact in the file. If it is below, the compiler cannot find it.
  • Make sure your switch has case '3': search_contact(); and not the old Quit logic.

Help me debug my search_contact function in C.

Search and delete are working. Now for the real test.

  • Select option 5 to quit the program.

Before you relaunch, think about this: what do you expect to see when the program starts again? Will your contacts still be there?

  • Relaunch the program by running this command:
./contact_book
  • Select option 2 to view all contacts.

You should see "No contacts found." Every contact you added is gone.

Why did my contacts disappear?

Your contacts live in the contacts array, which exists only in your program's memory. The moment the program exits, that memory is released and everything in it vanishes.

This is exactly the problem you will solve in the next step by writing contacts to a file on disk.

  • Select option 5 to quit the program.

Your contact book now handles adding, viewing, searching, and deleting. The only problem is that nothing survives a restart. In the next step, you will fix that by saving contacts to a file so they persist between runs.

Persist Contacts to a File

In the last step, you saw firsthand what happens when your program exits. Every contact you added disappeared because the contacts array only lives in memory.

File I/O fixes this. In this step, you'll write your contacts to a text file every time something changes, and read them back when the program starts.

In this step, get ready to:
  • Write a save function that persists contacts to a file on disk.
  • Write a load function that restores contacts when the program starts.
  • Wire both functions into the menu loop so persistence happens automatically.
Write the save function and wire it into main

To write data to disk in C, you open a file with fopen, write to it with fprintf, and close it with fclose. Your save_contacts function will use this pattern to dump every contact into a simple text file.

  • In contact_book.c, find the print_menu function. Add the following save_contacts function above it:
void save_contacts(void) {
    // Open contacts.txt in write mode (overwrites existing content)
    FILE *file = fopen(FILENAME, "w");
    if (file == NULL) {
        printf("Error: could not save contacts.\n");
        return;
    }

    // Write the total count on the first line
    fprintf(file, "%d\n", contact_count);
    // Write each contact's name and phone on separate lines
    for (int i = 0; i < contact_count; i++) {
        fprintf(file, "%s\n%s\n", contacts[i].name, contacts[i].phone);
    }

    fclose(file);
}

What does this code do?

  • fopen(FILENAME, "w") opens contacts.txt for writing. The "w" mode creates the file if it does not exist, or overwrites it if it does.
  • If fopen fails (returns NULL), the function prints an error and returns early instead of crashing.
  • The file format is simple: the contact count on line 1, then alternating name and phone lines. This makes it easy to read back later.
  • fclose(file) flushes any buffered data and releases the file handle. Skipping this can lose data.

The function exists now, but nothing calls it yet. You need to trigger a save after every operation that changes the contact list.

  • Scroll down to the switch statement inside main.
  • Find case '1': (the add contact case). It currently looks like this:
            case '1':
                add_contact();
                break;
  • Add save_contacts(); on a new line between add_contact(); and break;, so the case looks like this:
            case '1':
                add_contact();
                save_contacts();
                break;
  • Do the same for case '4': (delete). Add save_contacts(); between delete_contact(); and break;.
  • For case '5': (quit), add save_contacts(); as the first line in the case, before the printf("Goodbye!\n"); line.

After all three edits, your three updated cases should look like this:

            case '1':
                add_contact();
                save_contacts();
                break;
            // ... cases '2' and '3' unchanged ...
            case '4':
                delete_contact();
                save_contacts();
                break;
            case '5':
                save_contacts();
                printf("Goodbye!\n");
                running = 0;
                break;

Why save in three places?

You save after adding (case 1) and deleting (case 4) because those are the operations that change the contact list. You also save before quitting (case 5) as a final safety net.

Viewing and searching (cases 2 and 3) are read-only. They do not modify data, so there is nothing new to save.

  • Save contact_book.c.
  • Compile and run your program by running this command:
clang -Wall -Wextra -std=c17 -o contact_book contact_book.c && ./contact_book
  • Add a contact (option 1). Enter a name and phone number.
  • Quit the program (option 5).
  • Check that the save worked by running this command:
cat contacts.txt

You should see the contact count on the first line, followed by the name and phone number you just entered on separate lines.

Not seeing contacts.txt?

  • Make sure you saved contact_book.c before recompiling. Unsaved edits are not compiled.
  • Check that the save_contacts(); call is inside case '1': in the switch statement. If it is outside the switch, it will not run when you add a contact.
  • Confirm the save_contacts function is placed above main in the file. In C, functions must be declared before they are called.

Why isn't my contacts.txt file being created?

Write the load function

Your contacts now survive on disk, but the program does not read them back yet. The next function reverses the process: it reads contacts.txt on startup and fills the contacts array with whatever was saved.

  • In contact_book.c, find the save_contacts function you just wrote. Add the following load_contacts function directly below it (still above print_menu):
void load_contacts(void) {
    // Open contacts.txt in read mode
    FILE *file = fopen(FILENAME, "r");
    if (file == NULL) {
        // File does not exist yet (first run) - nothing to load
        return;
    }

    // Read the contact count from the first line
    char buf[100];
    if (fgets(buf, sizeof(buf), file) == NULL) {
        fclose(file);
        return;
    }
    contact_count = atoi(buf);

    // Read each contact's name and phone
    for (int i = 0; i < contact_count; i++) {
        fgets(contacts[i].name, NAME_LEN, file);
        trim_newline(contacts[i].name);
        fgets(contacts[i].phone, PHONE_LEN, file);
        trim_newline(contacts[i].phone);
    }

    fclose(file);
    printf("Loaded %d contact(s) from file.\n", contact_count);
}

What does this code do?

  • fopen(FILENAME, "r") opens the file in read mode. If the file does not exist (first time running the program), fopen returns NULL and the function returns silently.
  • atoi(buf) converts the first line (a text string) into an integer so you know how many contacts to read.
  • The loop reads name and phone lines in the same order they were written by save_contacts. Each line gets its trailing newline stripped by trim_newline.

Now you need to call load_contacts at the very start of main, before the menu loop runs.

  • Scroll down to your main function. Find the opening lines:
int main(void) {
    char choice[10];
  • Add load_contacts(); as the first line inside main, so the opening looks like this:
int main(void) {
    load_contacts();

    char choice[10];
  • Save contact_book.c.

Before you recompile, think about what should happen. You already have a contacts.txt file from the last test. Do you think the program will find those contacts on startup?

  • Compile and relaunch the program by running this command:
clang -Wall -Wextra -std=c17 -o contact_book contact_book.c && ./contact_book

The very first line of output should read Loaded 1 contact(s) from file. (or however many contacts you saved earlier).

  • Select option 2 to view all contacts. The contacts you saved in the last test should appear in the table.
  • Add one more contact (option 1), then quit (option 5).
  • Relaunch with ./contact_book and confirm the loaded count went up by one.

Your contacts now survive between runs. The data-loss problem from the end of Step 3 is gone.

Contacts not loading on restart?

  • Make sure load_contacts(); is the very first line inside main, before the while loop.
  • Confirm the load_contacts function is placed above main in the file. C requires functions to be declared before they are called.
  • Run cat contacts.txt to check the file exists and has content. If the file is empty or missing, the save step did not work correctly.

Why aren't my contacts loading when I restart the program?

✔️ Awesome, I've got everything!

Great. Double check you have saved contact_book.c and that your contacts persist between runs.

ⓧ I'd like to double check the full code

The full file is long, so scroll through and confirm each section is present in this order:

  • Includes and defines at the top (#include, #define, Contact struct, globals).
  • trim_newline and to_lower_str helper functions.
  • add_contact, view_contacts, search_contact, and delete_contact functions.
  • save_contacts function (new in this step).
  • load_contacts function (new in this step).
  • print_menu function (unchanged).
  • main function with load_contacts(); as the first line, and save_contacts(); calls in cases 1, 4, and 5.

Your contact book now has full persistence. Every add and delete is automatically saved, and every launch restores your data from disk. Next up, you will take on a secret mission to add editing and alphabetical sorting.

Secret mission

Edit Contacts and Sort Alphabetically

Your contact book can add, search, delete, and persist contacts. But what happens when you mistype a name? And wouldn't it be nice to see your contacts in order? In this mission, you'll add editing and alphabetical sorting using qsort with a custom comparator function.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files, or delete them entirely.

Resources you used:

  • The contact-book project folder on your Desktop (contains hello.c, contact_book.c, contacts.txt, and compiled binaries).
  • The C/C++ VS Code extension (ms-vscode.cpptools).

Keep everything running

No action needed. Choose this if you are still actively building or want to experiment with the contact book further.

  • Your contact-book folder stays on your Desktop with all source files and compiled binaries.
  • The C/C++ extension remains installed in VS Code, ready for your next C project.

Pause - I'll come back to this later

There are no running processes or cloud resources to shut down. Your project files sit on disk and take up minimal space.

  • Your source files and contacts.txt stay on your Desktop. You can recompile and run any time.
  • If you want to free a small amount of disk space, delete the compiled binaries (hello and contact_book) inside the folder. You can always recompile from source later.

Delete - I don't want to use this again

Remove all project files and the VS Code extension to start fresh.

  • Delete the project folder by running this command in your terminal:
rm -rf ~/Desktop/contact-book
  • Uninstall the C/C++ extension in VS Code:
  • Open the Extensions view by pressing Shift+Cmd+X (macOS) or Ctrl+Shift+X (Windows).
  • Find C/C++ by Microsoft in your installed extensions.
  • Click the gear icon next to the extension, then select Uninstall.

Nice Work!

Nice Work!

You just built a fully functional CLI Contact Book in C from scratch. You can add, view, search, and delete contacts from the terminal, and every change persists to a file so nothing is lost when you quit.

You learned how to:

  • Modeled real-world data using a Contact struct with character arrays, and built an interactive menu loop with fgets for safe input handling that avoids buffer overflow risks.
  • Implemented case-insensitive substring search using tolower and strstr, and handled contact deletion by shifting array elements to fill the gap.
  • Added file persistence with fopen, fprintf, and fclose so contacts survive between program runs, after experiencing firsthand why memory alone is not enough.
  • Secret Mission: Added edit functionality with conditional updates and alphabetical sorting using qsort with a custom comparator function.

Ready to quiz yourself?