Build a CLI Key-Value Store in C++
Build an interactive terminal key-value store using C++ classes and std::unordered_map.
Introduction
30 Second Summary
Every app on your phone stores data as pairs: a contact name matched to a phone number, a setting matched to its value. This pattern is called a key-value store.
In this project, you will build your own interactive key-value store in C++. You will type commands directly into your terminal to store, retrieve, and delete data on the fly.
What You'll Build
Run the finished program in your terminal and watch it respond to commands like SET name Alice and GET name in real time.
By the end of this project, you'll have:
- A working interactive program that responds to SET, GET, DEL, and EXIT commands in real time.
- A KVStore class you designed yourself that hides its data behind clean public methods, demonstrating encapsulation in action.
- First-hand experience catching a real C++ bug where reading a missing key silently creates a phantom entry, then applying the fix that prevents it.
- Secret Mission: Add LIST and COUNT commands to display all stored pairs and how many exist.
Do I need any C++ experience?
No. This project is beginner-friendly. Basic programming concepts like variables, loops, and conditionals in any language are helpful but not required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This is the moment to commit to the goal before any hands-on work begins.
Set Up Your Project
Before building the key-value store itself, you need a working foundation. A compilable C++ file and a confirmed compiler ensure every line of code you write from here on can run immediately.
In this step, you'll create a project folder, write a minimal "Hello, world!" program, and compile it with g++. This proves your toolchain is ready before you add any real logic.
In this step, get ready to:
- Create a project folder and a starter kvstore.cpp file.
- Compile and run the program to confirm your setup works.
Create a project folder and starter file
Your project needs a dedicated folder and a single C++ source file to get started.
- Press Ctrl+` to open the integrated terminal in VS Code.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir kvstore-project
cd kvstore-project
- Open this folder in VS Code so the sidebar and terminal both point to the right place.
- Click File in the VS Code menu bar.
- Click Open Folder.
- Select the kvstore-project folder on your Desktop.
- Click Open.
The Explorer sidebar now shows KVSTORE-PROJECT as the workspace root.
- If the terminal closed when the folder opened, press Ctrl+` to reopen it.
- Create the starter file by running this command:
touch kvstore.cpp
- Confirm the file was created by running:
ls
You should see kvstore.cpp in the output.
- Click on kvstore.cpp in the Explorer sidebar to open it in the editor.
- Copy the following code into kvstore.cpp:
#include <iostream>
int main() {
// Print a test message to confirm the program runs
std::cout << "Hello, world!" << std::endl;
return 0;
}
What does this code do?
- #include <iostream> pulls in the standard input/output library so you can use std::cout to print text.
- main() is the entry point every C++ program needs. The operating system calls this function when you run the executable.
- std::cout << "Hello, world!" << std::endl; sends text to the terminal, followed by a newline.
- return 0; tells the operating system the program finished successfully.
✔️ Awesome, I've got everything!
Great. Make sure you've saved kvstore.cpp before moving on.
ⓧ I'd like to double check the full code
Here is the complete kvstore.cpp file so far. Make sure yours matches exactly.
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
return 0;
}
- Save kvstore.cpp by pressing Cmd+S (macOS) or Ctrl+S (Windows).
File not appearing in the sidebar?
- Make sure you opened the kvstore-project folder in VS Code, not a parent folder like your Desktop.
- Check that you ran touch kvstore.cpp while the terminal was inside the kvstore-project directory.
- Help me find my kvstore.cpp file in VS Code
Compile and run
C++ is a compiled language. Before you can run your code, you need to pass it through a compiler that turns the source file into a binary your computer can execute.
What is a compiler?
A compiler reads your human-readable .cpp file and translates it into machine code the processor can run directly. The g++ command invokes this compiler.
- Compile your program by running this command in the terminal:
g++ -std=c++17 -o kvstore kvstore.cpp
What does this command do?
- g++ is the C++ compiler.
- -std=c++17 tells the compiler to use the C++17 standard, which is the modern default.
- -o kvstore names the output executable kvstore. Without this flag, the compiler defaults to a generic name.
- kvstore.cpp is the source file you want to compile.
If the command completes with no output, that means compilation succeeded. Silence is good news from a compiler.
- Confirm the executable was created by running:
ls
You should see both kvstore (the executable) and kvstore.cpp (your source file) listed.
Seeing a compilation error?
- Check that every line in kvstore.cpp matches the code above exactly, including semicolons and angle brackets around iostream.
- Make sure you saved the file before running the compile command.
- Confirm you are inside the kvstore-project directory by running pwd.
- Help me fix my g++ compilation error
Before you run the program, what do you think you'll see printed in the terminal?
- Run your program:
./kvstore
You should see Hello, world! printed in the terminal. Your compiler and project setup are working.
Nothing happened or seeing permission denied?
- If you see "Permission denied", make the file executable by running chmod +x kvstore and then try ./kvstore again.
- If you see "No such file or directory", the compile step may have failed silently. Run the g++ command again and check for error messages.
- Help me run my compiled C++ program
Your toolchain is working and your project folder is ready. Next up, you'll build the interactive command loop that lets users type commands into your store.
Build the Command Loop
Your compiler is working and kvstore.cpp prints to the terminal. Now it is time to make the program interactive.
A key-value store needs a way to receive commands from the user. In this step, you will build a command loop that reads input, identifies the command word, and responds. By the end, you will have a live program you can type into.
In this step, get ready to:
- Build a REPL (read-eval-print loop) that reads lines of input.
- Parse each line to extract the command word.
- Handle the EXIT command and echo unknown commands back to the user.
Replace hello world with a REPL loop
Right now your program prints one line and exits. A key-value store needs to keep running and waiting for the next command. This pattern is called a REPL (read-eval-print loop).
- In VS Code, open kvstore.cpp.
- Select all the existing code and replace it with the following:
#include <iostream>
#include <string>
#include <sstream>
int main() {
// Store each line of user input
std::string line;
// Print the first prompt
std::cout << "kvstore> ";
// Keep reading input until the stream ends
while (std::getline(std::cin, line)) {
// Skip blank lines
if (line.empty()) {
std::cout << "kvstore> ";
continue;
}
// Print the prompt for the next input
std::cout << "kvstore> ";
}
return 0;
}
What does this code do?
- #include <string> and #include <sstream> bring in the string and stream-parsing tools you will use shortly.
- std::getline(std::cin, line) reads an entire line of input (spaces included) and stores it in line. The while loop keeps running until the input stream closes.
- The line.empty() check skips blank lines so the program just re-prompts instead of trying to process nothing.
- kvstore> is the prompt. It prints once before the loop starts and again after every input, so the user always sees where to type next.
- Save kvstore.cpp.
- Compile the updated file by running this command in your VS Code terminal:
g++ -std=c++17 -o kvstore kvstore.cpp
- Run the program by running:
./kvstore
You should see kvstore> printed, and the cursor waits for your input. Type anything and press Enter. A new kvstore> prompt appears.
- Press Ctrl+C to stop the program.
Program exits immediately?
- Make sure the while (std::getline(std::cin, line)) line matches exactly. A missing parenthesis or a typo in getline will cause the loop to fail.
- Check that #include <string> is at the top of your file. Without it, std::string and std::getline may not compile.
Why is my REPL loop exiting immediately instead of waiting for input?
Parse the command word
The loop reads full lines, but you need to split each line into a command word and any arguments that follow it. std::istringstream turns a string into a stream you can extract words from, just like reading from std::cin.
- In kvstore.cpp, find the closing kvstore> prompt line inside the while loop. Find this line:
// Print the prompt for the next input
std::cout << "kvstore> ";
- Add the following code directly above that line, between the closing } of the if (line.empty()) check and the prompt:
// Extract the first word as the command
std::istringstream iss(line);
std::string command;
iss >> command;
// Handle EXIT or echo unknown commands
if (command == "EXIT") {
std::cout << "Goodbye!" << std::endl;
break;
} else {
std::cout << "Unknown command: " << command << std::endl;
}
What does this code do?
- std::istringstream iss(line) wraps the input line in a stream. The >> operator then pulls out the first whitespace-separated word and stores it in command.
- If the command is EXIT, the program prints Goodbye! and break exits the loop, ending the program.
- For anything else, the program echoes the command back with Unknown command: so you know your input was received.
- Save kvstore.cpp.
Compile and test interactively
Your command loop is complete. Time to compile and see it handle real input.
- Compile the updated file by running:
g++ -std=c++17 -o kvstore kvstore.cpp
Before you run the program, think about this: if you type HELLO and press Enter, what do you expect the program to print?
- Run the program by running:
./kvstore
- Type HELLO and press Enter.
You should see Unknown command: HELLO followed by a fresh kvstore> prompt.
- Type EXIT and press Enter.
The program prints Goodbye! and exits cleanly. You are back at your normal terminal prompt.
What is a REPL?
REPL stands for Read-Eval-Print Loop. The program reads a line of input, evaluates (processes) it, prints a response, and loops back to wait for the next line. Python's interactive shell and Node.js's console both use this same pattern.
Seeing a compile error?
- Check that #include <sstream> is at the top of your file. Without it, std::istringstream is undefined and the compiler will reject it.
- Make sure every opening brace { has a matching closing brace }. The while loop, the if/else block, and main() each need their own pair.
- Confirm that the parsing block (the istringstream section) sits inside the while loop, between the empty-line check and the closing prompt.
Help me debug my C++ compile error in kvstore.cpp
✔️ Awesome, I've got everything!
Make sure you have saved kvstore.cpp. You are all set to move on.
ⓧ I'd like to double check the full code
Scroll up and confirm both code chunks are in your kvstore.cpp file, in this order:
- The three #include lines at the top (<iostream>, <string>, <sstream>).
- The main() function with a while (std::getline(std::cin, line)) loop, an empty-line check, and the initial kvstore> prompt before the loop.
- The command parsing block inside the loop: std::istringstream iss(line), the EXIT check, and the unknown-command echo.
- A closing std::cout << "kvstore> " line at the bottom of the loop, right before the closing braces.
Your command loop is up and running. Next, you will build the KVStore class to give those commands real data to store and retrieve.
Create the KVStore Class with SET and GET
Your command loop can read input and echo it back, but it has no way to store anything. To build a real key-value store, you need a data structure that holds pairs of strings and methods that interact with them.
C++ classes let you bundle data and operations together in one place. In this step, you'll create a KVStore class that wraps a hash map behind a clean interface, then wire it into your command loop so you can store and retrieve values.
In this step, get ready to:
- Define a KVStore class with a private data store and public methods.
- Wire SET and GET commands into the command loop.
- Test storing and retrieving values interactively.
Define the KVStore class
A C++ class groups a piece of data together with the functions that operate on it. The private keyword hides the data from outside code, and public methods provide the only way to interact with it.
- In kvstore.cpp, find the line #include <sstream> and add the following include directly below it:
#include <unordered_map>
- Now add the KVStore class between the includes and int main() by copying this code below #include <unordered_map>:
class KVStore {
private:
// The underlying hash map that stores all key-value pairs
std::unordered_map<std::string, std::string> store;
public:
// Insert or update a key-value pair, then confirm with "OK"
void set(const std::string& key, const std::string& value) {
store[key] = value;
std::cout << "OK" << std::endl;
}
// Look up a key and print its value
void get(const std::string& key) {
std::cout << store[key] << std::endl;
}
};
What does this code do?
- private: makes the store member invisible to any code outside the class. Only set() and get() can touch the map.
- std::unordered_map<std::string, std::string> is a hash map that maps string keys to string values with average O(1) lookups.
- set() uses store[key] = value to insert a new pair or update an existing one, then prints "OK" to confirm.
- get() uses store[key] to look up the value for a key and print it to the terminal.
What is encapsulation?
Encapsulation means hiding a class's internal data and only exposing controlled operations. Here, nothing outside KVStore can directly read or write the store map.
If you ever need to change how data is stored (swap the hash map for a sorted tree, add logging, enforce limits), you only change the class internals. Code that calls set() and get() never needs to know.
- Save kvstore.cpp.
Wire SET and GET into the command loop
The class exists, but the command loop doesn't know about it yet. You need to create a KVStore instance and add branches for the SET and GET commands.
- In kvstore.cpp, find this line inside main():
std::string line;
- Add KVStore db; on a new line directly above it, so the two lines look like this:
KVStore db;
std::string line;
- Now find the existing if/else block inside the while loop:
if (command == "EXIT") {
std::cout << "Goodbye!" << std::endl;
break;
} else {
std::cout << "Unknown command: " << command << std::endl;
}
- Replace that entire block with the following code, which adds SET and GET branches while keeping EXIT and the unknown-command handler:
// Handle SET command: extract key and value, store them
if (command == "SET") {
std::string key, value;
if (iss >> key >> value) {
db.set(key, value);
} else {
std::cout << "Usage: SET <key> <value>" << std::endl;
}
// Handle GET command: extract key, retrieve value
} else if (command == "GET") {
std::string key;
if (iss >> key) {
db.get(key);
} else {
std::cout << "Usage: GET <key>" << std::endl;
}
} else if (command == "EXIT") {
std::cout << "Goodbye!" << std::endl;
break;
} else {
std::cout << "Unknown command: " << command << std::endl;
}
What does this code do?
- iss >> key >> value extracts the next two words from the input line. If fewer than two words remain, the extraction fails and the if check falls through to a usage message.
- The SET branch calls db.set(key, value) which stores the pair and prints "OK".
- The GET branch calls db.get(key) which looks up and prints the value.
- EXIT and the unknown-command handler are unchanged from the previous step.
- Save kvstore.cpp.
✔️ Awesome, I've got everything!
Great. Double check you've saved kvstore.cpp before compiling.
ⓧ I'd like to double check the full code
Your kvstore.cpp should now have these sections, in order. Scroll up and confirm each is present:
- Four #include lines: <iostream>, <string>, <sstream>, <unordered_map>.
- The KVStore class with a private store member and public set() and get() methods.
- main() with KVStore db; as the first line, followed by the REPL loop.
- The if/else if chain inside the loop handling SET, GET, EXIT, and unknown commands.
Compile and test the store
- Recompile your program by running this command in the VS Code terminal:
g++ -std=c++17 -o kvstore kvstore.cpp
Seeing a compilation error?
- Check that your class definition ends with }; (semicolon after the closing brace). Missing this semicolon is one of the most common C++ class errors.
- Make sure the class definition is above int main(), not inside it. The compiler needs to see the class before main() tries to use it.
- Confirm #include <unordered_map> is at the top of the file with your other includes.
Why won't my kvstore.cpp compile?
- Start the program by running:
./kvstore
You should see the kvstore> prompt waiting for input.
- Type SET name Alice and press Enter.
You should see OK printed back. Your key-value pair is stored.
- Type GET name and press Enter.
You should see Alice printed back. The store works for setting and getting values.
Now try something different. Before you type the next command, think about what you expect to happen if you ask for a key that was never set.
- Type GET missing and press Enter.
Instead of an error message, you see a blank line. That is not the behaviour you would want from a real key-value store.
You just hit a real C++ bug
When you write store[key] and the key does not exist, C++ does not throw an error. Instead, operator[] silently inserts a new entry with an empty string as the value and returns that empty string.
This means every time you GET a missing key, a phantom entry is created behind your back. Your store is silently filling up with empty values you never asked for. This is intentional. You will fix it in the next step.
- Type EXIT and press Enter to quit the program.
Your store can set and get values, but the operator[] bug means any missing-key lookup silently corrupts your data. Next up, you'll fix get() to check before accessing and add a DEL command to remove entries.
Fix the GET Bug and Add DELETE
In the last step, you built a KVStore class and wired SET and GET into the command loop. You also discovered a bug. Typing GET on a key that doesn't exist printed a blank line instead of an error.
That blank line comes from operator[]. When it can't find a key, it silently creates a new entry with an empty value. In this step, you'll fix that bug with find() and add a DEL command to remove entries.
In this step, get ready to:
- Fix the get() method to check if a key exists before accessing it.
- Add a del() method that removes entries and handles missing keys.
- Wire DEL into the command loop and test the full SET/GET/DEL/EXIT cycle.
Fix get() to use find()
The current get() method uses store[key] to read values. The problem is that operator[] silently inserts a default-constructed empty string when the key doesn't exist. The fix is find(), which returns an iterator that either points to the matching entry or to store.end() if the key isn't there.
- In kvstore.cpp, find the get() method inside your KVStore class.
- It currently looks like this:
void get(const std::string& key) {
std::cout << store[key] << std::endl;
}
- Replace the entire get() method with this updated version:
void get(const std::string& key) {
// Look up the key without inserting a phantom entry
auto it = store.find(key);
if (it != store.end()) {
// Key exists - print its value
std::cout << it->second << std::endl;
} else {
std::cout << "ERROR: Key not found" << std::endl;
}
}
What does this code do?
- store.find(key) searches the map and returns an iterator. If the key exists, the iterator points to that entry. If it doesn't, the iterator equals store.end().
- it->second accesses the value half of the key-value pair. Every entry in the map is a pair where .first is the key and .second is the value.
- Unlike operator[], find() never modifies the map. No more phantom entries.
- Save kvstore.cpp.
- Recompile your program by running this command in your VS Code terminal:
g++ -std=c++17 -o kvstore kvstore.cpp
Before you test, think about this: what do you expect to see when you type GET missing now? Will it still print a blank line, or something different?
- Run your program to find out:
./kvstore
- At the kvstore> prompt, type GET missing and press Enter.
You should see ERROR: Key not found printed back. The blank-line bug is gone.
- Confirm the happy path still works by typing SET name Alice and then GET name.
- You should see OK after SET, then Alice after GET.
- Type EXIT to quit the program.
Still seeing a blank line for missing keys?
- Make sure you replaced the entire get() method, including the opening and closing braces. The old single-line body must be completely gone.
- Check that you saved kvstore.cpp before recompiling. Unsaved changes won't be included in the build.
- Verify that your if condition compares the iterator to store.end() using !=, not ==.
Why is my GET still printing a blank line instead of an error?
Add the del() method
Now that GET handles missing keys properly, you need a way to remove entries entirely. The std::unordered_map provides an erase() method that deletes an entry by key and returns how many elements it removed (0 or 1).
- In kvstore.cpp, add this new del() method inside the KVStore class, directly after the closing brace of your get() method:
void del(const std::string& key) {
// erase() returns how many elements were removed (0 or 1)
size_t removed = store.erase(key);
if (removed > 0) {
std::cout << "OK" << std::endl;
} else {
std::cout << "ERROR: Key not found" << std::endl;
}
}
What does this code do?
- store.erase(key) removes the entry with the given key from the map. It returns a size_t value: 1 if the key existed and was removed, 0 if it wasn't there.
- By checking the return value, you can give the user clear feedback. This is the same error-handling idea as find() in get(): check first, then act.
- Save kvstore.cpp. You'll compile and test del() in the next substep after wiring it into the command loop.
Wire DEL into the command loop and test the full flow
The del() method exists, but the command loop doesn't know about it yet. You need to add a DEL branch that extracts the key and calls db.del().
- In the main() function, scroll down to the command-parsing section.
- Find this line:
} else if (command == "EXIT") {
- Add this new block directly above that line:
} else if (command == "DEL") {
std::string key;
if (iss >> key) {
db.del(key);
} else {
std::cout << "Usage: DEL <key>" << std::endl;
}
What does this code do?
This follows the same pattern as your GET branch: extract the key from the input stream, then call the matching method on db. If the user forgets the key, it prints a usage hint.
- Save kvstore.cpp.
- Recompile by running:
g++ -std=c++17 -o kvstore kvstore.cpp
Seeing a compile error?
- Check that the new } else if (command == "DEL") block sits between the closing brace of the GET block and the opening of the EXIT block. An extra or missing brace here breaks the chain.
- Make sure the del() method is inside the KVStore class (above the }; that closes the class), not inside main().
Help me fix a compile error in my C++ kvstore program.
Time for the full test. Before you run the program, predict: what will happen if you SET a key, then DEL it, then try to GET it again?
- Start your program by running:
./kvstore
- Type SET city NYC and press Enter. You should see OK.
- Type GET city. You should see NYC.
- Type DEL city. You should see OK.
- Type GET city again. You should see ERROR: Key not found.
- Type DEL city one more time. You should see ERROR: Key not found because the key is already gone.
- Type EXIT to quit.
Every command now gives clear feedback. SET confirms the write, GET either returns the value or reports an error, and DEL confirms the removal or tells you the key wasn't there.
✔️ Awesome, I've got everything!
Great. Double check you've saved kvstore.cpp and your program compiled without errors.
ⓧ I'd like to double check the full code
Scroll through your kvstore.cpp and confirm each of these sections is present, in this order:
- Four #include lines at the top: <iostream>, <string>, <sstream>, <unordered_map>.
- The KVStore class with a private store member and three public methods: set(), get() (using find()), and del() (using erase()).
- The main() function with a while loop containing branches for SET, GET, DEL, EXIT, and an unknown-command fallback.
- The DEL branch appears between GET and EXIT in the if-else chain.
Your key-value store now handles storing, retrieving, and deleting entries with proper error handling. If you're up for one more challenge, a Secret Mission awaits.
Secret mission
Add LIST and COUNT Commands
Your store handles individual keys, but what about seeing everything at once? Add LIST and COUNT commands to see all stored pairs and how many exist.
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:
- kvstore-project folder on your Desktop (contains kvstore.cpp and the compiled kvstore binary).
Keep everything running
No action needed. Choose this if you want to keep experimenting with your key-value store or extend it further.
- Your kvstore-project folder stays on your Desktop. You can recompile and run kvstore any time.
- g++ is part of your Xcode Command Line Tools and stays installed on your system.
Pause - I'll come back to this later
Since this is a local project with no running processes, there is nothing to pause. Your files sit on disk and use a negligible amount of space.
- Leave the kvstore-project folder on your Desktop. When you are ready to return, open it in VS Code and recompile.
Delete - I don't want to use this again
Remove the project folder to start fresh.
- Delete the kvstore-project folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/kvstore-project
Windows
rmdir /s /q %USERPROFILE%\Desktop\kvstore-project
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see kvstore-project in the output.
Nice Work!
Nice Work!
You just built a fully interactive command-line key-value store in C++. You can type commands, store data, retrieve it, delete it, and handle errors, all from a program you wrote and compiled yourself.
You've learned how to:
- Build an interactive REPL command loop using std::getline and std::istringstream to read and parse user input from the terminal.
- Design a KVStore class with a private std::unordered_map and public methods like set(), get(), and del(), demonstrating encapsulation in practice.
- Discover the operator[] phantom-entry bug firsthand and fix it with find(), learning why you always check before accessing a key in a map.
- Secret Mission: Took on an optional challenge to push your skills further.
Ready to quiz yourself?