Hotel Management System in C++
Build a hotel system using custom linked list, hash table, and BST in C++.
Introduction
30 Second Summary
Think about how a hotel front desk juggles hundreds of rooms, guests, and bookings without losing track of anyone. Behind the scenes, that speed comes down to choosing the right data structures for the right job.
In this project, you will build a console-based hotel management system in C++ where every data structure is implemented from scratch. No STL containers allowed. You will wire together a custom hash table, binary search tree, and linked list into a working application with file persistence.
What You'll Build
You will run a hotel system where you check guests in and out, instantly look up any guest by ID, browse all rooms sorted by number, and close the program knowing everything is saved for next time.
By the end of this project, you'll have:
- A custom hash table with separate chaining that finds any guest in O(1) average time by their ID.
- A binary search tree that stores rooms sorted by number and supports insert, delete (all three cases), and inorder traversal.
- A working OOP class hierarchy with an abstract base class, inheritance, and polymorphism calculating prices differently for each room type.
- A file persistence layer that saves and loads all booking data between program runs.
- Secret Mission: Build a min-heap priority queue that recommends the cheapest available room to guests automatically.
Are there any prerequisites?
This project assumes you have a good understanding of C++ syntax, OOP basics (classes, constructors, pointers), and DSA fundamentals (what a linked list and tree are conceptually). You do not need prior experience implementing them from scratch.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
This project exercises both your data structures knowledge and your OOP skills at the same time. Taking a moment to name the specific structures and concepts you'll use helps you approach each step with intention.
Set Up the Project Structure
Every data structure in this project lives in its own header file. Before writing any algorithm code, you need a multi-file project structure that compiles cleanly.
In this step, you will create the project folder, set up header files with include guards, and verify the C++ toolchain compiles everything into a running program.
In this step, get ready to:
- Create the HotelManagement project folder on your Desktop.
- Add header files with include guards and a minimal main.cpp.
- Compile and run to confirm the toolchain works.
Create the project folder and open it in VS Code
You need a dedicated folder for your project files and a code editor pointed at it.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Open the integrated terminal by pressing Ctrl+` (the backtick key, usually below Escape).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir HotelManagement
cd HotelManagement
- Open this folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the HotelManagement folder, and click Open (or Select Folder on Windows).
VS Code's Explorer panel on the left now shows the empty HotelManagement folder. The integrated terminal also points to this folder.
Don't see the folder in the Explorer?
Make sure you selected the HotelManagement folder itself, not the Desktop. The Explorer heading should read HOTELMANAGEMENT.
If the terminal shows a different path, close it and open a new one with Ctrl+`. It will default to the open folder.
Help me fix my VS Code Explorer.
Create the header files and main program
Your project needs seven files. Six are header files for the data structures and classes you will build later. The seventh is main.cpp where the program starts.
- In VS Code's Explorer panel, click the New File icon (the page icon with a plus sign at the top of the panel).
- Type LinkedList.h and press Enter.
- Repeat this for each of these files: HashTable.h, BST.h, Room.h, Guest.h, Hotel.h, and main.cpp.
- Confirm all seven files exist by running this in your terminal:
ls
You should see all seven files listed: BST.h, Guest.h, HashTable.h, Hotel.h, LinkedList.h, Room.h, and main.cpp.
Now add include guards to each header file. Include guards prevent a header from being included more than once during compilation, which would cause duplicate definition errors.
- Open LinkedList.h and paste the following:
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
// placeholder
#endif
What do include guards do?
The #ifndef LINKEDLIST_H line checks whether a symbol called LINKEDLIST_H has been defined yet. If it has not, the code between #define and #endif is included.
If the symbol already exists (meaning this file was included once), the compiler skips everything. Every header file in C++ needs this pattern to avoid redefinition errors in multi-file projects.
- Apply the same pattern to the remaining five header files, changing the guard name to match each filename:
- HashTable.h uses HASHTABLE_H.
- BST.h uses BST_H.
- Room.h uses ROOM_H.
- Guest.h uses GUEST_H.
- Hotel.h uses HOTEL_H.
- Save all six header files by pressing Ctrl+S (Windows) or Cmd+S (macOS) in each tab.
Now write the entry point for the program.
- Open main.cpp and paste the following:
#include "Hotel.h"
#include <iostream>
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
return 0;
}
What does this code do?
The #include "Hotel.h" line pulls in your project's Hotel header (currently empty). The #include <iostream> line brings in the standard input/output library.
The main() function is where every C++ program starts. Right now it just prints a welcome message to confirm the project compiles and runs.
- Save main.cpp.
Seeing red underlines in main.cpp?
VS Code's IntelliSense may show errors because it cannot resolve the includes yet. These will disappear once you compile successfully. The compiler uses different include paths than IntelliSense.
Help me understand these red underlines.
Compile and run the program
With all files in place, you can test that the g++ compiler sees them and produces a working executable.
Before you run the compile command, what do you think will happen? The header files are mostly empty. Will the compiler produce an error or succeed?
- Compile your program by running this command in the terminal:
g++ -o hotel.exe main.cpp -std=c++17
What does this command do?
The g++ compiler reads main.cpp and every header it includes. The -o hotel.exe flag names the output executable. The -std=c++17 flag tells the compiler to use the C++17 standard.
Since all the headers contain only include guards and a comment, the compiler has nothing to reject. It compiles main.cpp successfully with the one cout statement.
- Run the compiled program:
.\hotel.exe
You should see this output in the terminal:
Welcome to Grand Hotel Management System
Seeing 'g++ is not recognized'?
This means MinGW is not in your system PATH. The default installation path is C:\msys64\ucrt64\bin. Add this directory to your system PATH environment variable, then restart VS Code.
You can verify MinGW is accessible by running:
g++ --version
If this prints a version number, the PATH is correct.
Help me add MinGW to my PATH.
✔️ Awesome, I've got everything!
Great. Double check that you have saved all seven files.
ⓧ I'd like to double check the full code
Here is each file as it should look right now.
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
// placeholder
#endif
#ifndef HASHTABLE_H
#define HASHTABLE_H
// placeholder
#endif
#ifndef BST_H
#define BST_H
// placeholder
#endif
#ifndef ROOM_H
#define ROOM_H
// placeholder
#endif
#ifndef GUEST_H
#define GUEST_H
// placeholder
#endif
#ifndef HOTEL_H
#define HOTEL_H
// placeholder
#endif
#include "Hotel.h"
#include <iostream>
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
return 0;
}
Your project skeleton is ready. Every header file is in place, the compiler is happy, and the program runs. Next up, you will fill those empty headers with a working linked list and hash table.
Build the Linked List and Hash Table
Your project folder is ready and compiling. Now you need the actual data structures that will power the hotel system.
The linked list stores booking records and powers the hash table's collision handling. The hash table gives you O(1) average-case guest lookup by ID. In this step, you will build both from scratch and prove they work with a test.
In this step, get ready to:
- Implement a template singly linked list with insert, remove, search, and proper memory cleanup.
- Implement a hash table with separate chaining for O(1) guest lookup.
- Test the hash table by inserting, searching, and removing guest objects.
Implement the linked list
A linked list is a chain of nodes where each node holds data and a pointer to the next node. Unlike arrays, linked lists grow and shrink dynamically without needing to reallocate memory.
- In VS Code, open LinkedList.h from the Explorer sidebar.
- Replace the // placeholder comment (between the include guards) with this code to start the class structure:
#include <iostream>
template <typename T>
class LinkedList {
public:
// Each node holds data and a pointer to the next node
struct Node {
T data;
Node* next;
Node(T val) : data(val), next(nullptr) {}
};
private:
Node* head; // pointer to first node
int count; // tracks number of elements
public:
// Constructor: start with an empty list
LinkedList() : head(nullptr), count(0) {}
// Destructor: walk the chain and free every node
~LinkedList() {
Node* current = head;
while (current) {
Node* temp = current;
current = current->next;
delete temp;
}
}
What does this code do?
This sets up the linked list's skeleton. The Node struct is public so other classes can traverse the list directly.
The destructor is critical for manual memory management. Every node allocated with new must eventually be freed with delete. The destructor walks the chain and frees each node when the list goes out of scope.
- Directly below the destructor, add the insert operations:
// Insert at the front: O(1)
void insertFront(T val) {
Node* newNode = new Node(val);
newNode->next = head;
head = newNode;
count++;
}
// Insert at the back: O(n) - traverse to find the tail
void insertBack(T val) {
Node* newNode = new Node(val);
if (!head) {
head = newNode;
} else {
Node* current = head;
while (current->next) {
current = current->next;
}
current->next = newNode;
}
count++;
}
Why two insert methods?
Inserting at the front is O(1) because you just rewire the head pointer. Inserting at the back is O(n) because you must walk the entire chain to find the tail.
The hotel system uses insertBack for bookings (to preserve chronological order) and insertFront is available for cases where order does not matter.
- Below the insert methods, add the removal logic:
// Remove first node whose data matches val
bool removeByValue(T val) {
if (!head) return false;
// Special case: head node matches
if (head->data == val) {
Node* temp = head;
head = head->next;
delete temp;
count--;
return true;
}
// General case: search through the chain
Node* current = head;
while (current->next) {
if (current->next->data == val) {
Node* temp = current->next;
current->next = temp->next;
delete temp;
count--;
return true;
}
current = current->next;
}
return false;
}
What does removeByValue do?
Removal has two cases. If the head matches, you rewire head to skip the first node and free it. Otherwise, you walk the chain until you find the node whose next matches, then splice it out.
This requires the type T to support the == operator. Any class stored in this list must overload operator==.
- Complete the class by adding the search, display, and utility methods, then close the class and the include guard:
// Search: returns pointer to matching data, or nullptr
T* search(T val) {
Node* current = head;
while (current) {
if (current->data == val) return &(current->data);
current = current->next;
}
return nullptr;
}
// Print all elements
void display() const {
Node* current = head;
while (current) {
std::cout << current->data << std::endl;
current = current->next;
}
}
int getSize() const { return count; }
bool isEmpty() const { return head == nullptr; }
Node* getHead() const { return head; }
};
#endif
What do the utility methods do?
The search method returns a pointer to the found data (so callers can modify it in place) or nullptr if nothing matches. The display method relies on the stored type supporting operator<<.
The getHead() method exposes the internal head pointer. This lets other classes iterate the list directly, which the hotel system uses to walk active bookings.
- Save LinkedList.h.
- Verify your file has the #ifndef LINKEDLIST_H guard at the top, then all the code you just added, then #endif at the very bottom.
✔️ Awesome, I've got everything!
Great. Make sure you have saved LinkedList.h before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm these four sections appear in order inside your LinkedList.h file, between the include guards:
- Class structure: Node struct, head/count members, constructor, destructor.
- Insert operations: insertFront and insertBack.
- Removal: removeByValue with head-case and general-case logic.
- Utilities: search, display, getSize, isEmpty, getHead, followed by }; and #endif.
Seeing red squiggles in VS Code?
Template headers often show false IntelliSense warnings until the template is instantiated elsewhere. If the squiggles are in LinkedList.h only, they are likely harmless.
Check that your #ifndef LINKEDLIST_H line is at the very top and #endif is at the very bottom. Missing guards cause duplicate-definition errors later.
help me fix red squiggles in my LinkedList.h template header
Implement the hash table
A hash table maps keys to values in O(1) average time. Your implementation uses separate chaining: each bucket holds a linked list of entries that hashed to the same index.
- Open HashTable.h and replace the // placeholder comment with this private section and constructor:
#include <iostream>
template <typename V>
class HashTable {
private:
struct HashNode {
int key;
V value;
HashNode* next;
HashNode(int k, V v) : key(k), value(v), next(nullptr) {}
};
HashNode** buckets; // array of bucket pointers
int capacity;
int size;
// Hash function: ensures non-negative index
int hashFunction(int key) const {
return ((key % capacity) + capacity) % capacity;
}
public:
HashTable(int cap = 53) : capacity(cap), size(0) {
buckets = new HashNode*[capacity];
for (int i = 0; i < capacity; i++) {
buckets[i] = nullptr;
}
}
Why capacity 53?
Prime-sized hash tables reduce collision clustering with modular hashing. 53 is a prime number comfortably above the guest counts this demo system will handle.
The hash function uses ((key % capacity) + capacity) % capacity instead of plain key % capacity. Adding capacity before the second modulo ensures the result is never negative, even if a negative key is passed in.
- Below the constructor, add the destructor and insert method:
// Destructor: free every chain, then the bucket array
~HashTable() {
for (int i = 0; i < capacity; i++) {
HashNode* current = buckets[i];
while (current) {
HashNode* temp = current;
current = current->next;
delete temp;
}
}
delete[] buckets;
}
// Insert or update a key-value pair
void insert(int key, V value) {
int index = hashFunction(key);
HashNode* current = buckets[index];
while (current) {
if (current->key == key) {
current->value = value;
return;
}
current = current->next;
}
HashNode* newNode = new HashNode(key, value);
newNode->next = buckets[index];
buckets[index] = newNode;
size++;
}
What does insert do?
The insert method first checks if the key already exists in that bucket's chain. If it does, it updates the value in place (no duplicates). If not, it creates a new node and prepends it to the chain at that bucket index.
The destructor walks every bucket's chain and frees each node, then frees the bucket array itself with delete[]. Two levels of allocation require two levels of cleanup.
- Add the search and remove methods below insert:
// Search by key: returns pointer to value or nullptr
V* search(int key) {
int index = hashFunction(key);
HashNode* current = buckets[index];
while (current) {
if (current->key == key) {
return &(current->value);
}
current = current->next;
}
return nullptr;
}
// Remove by key: unlink and delete the node
bool remove(int key) {
int index = hashFunction(key);
HashNode* current = buckets[index];
HashNode* prev = nullptr;
while (current) {
if (current->key == key) {
if (prev) {
prev->next = current->next;
} else {
buckets[index] = current->next;
}
delete current;
size--;
return true;
}
prev = current;
current = current->next;
}
return false;
}
How does remove handle the chain?
The remove method tracks a prev pointer alongside current. If the target node is the first in the chain (prev is nullptr), the bucket pointer itself must be rewired. Otherwise, prev->next skips over the deleted node.
- Complete the hash table with display, utilities, and the closing guard:
// Print all stored entries
void display() const {
for (int i = 0; i < capacity; i++) {
HashNode* current = buckets[i];
while (current) {
std::cout << "[ID: " << current->key << "] " << current->value << std::endl;
current = current->next;
}
}
}
int getSize() const { return size; }
bool isEmpty() const { return size == 0; }
};
#endif
What does display show?
The display method iterates every bucket. Empty buckets are skipped naturally because their pointer starts at nullptr. Only buckets with stored entries print anything.
- Save HashTable.h.
- Confirm your file starts with #ifndef HASHTABLE_H and ends with #endif.
✔️ Awesome, I've got everything!
Great. Make sure you have saved HashTable.h before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm these four sections appear in order inside your HashTable.h file, between the include guards:
- Private section: HashNode struct, buckets/capacity/size members, hashFunction, and the constructor.
- Memory management: destructor freeing all chains and the bucket array. Insert method with duplicate-key update logic.
- Lookup and removal: search returning V* or nullptr, and remove with prev-pointer chain unlinking.
- Utilities: display, getSize, isEmpty, followed by }; and #endif.
IntelliSense complaining about the template?
Ensure #include <iostream> appears directly after the #define HASHTABLE_H line. Missing this include causes std::cout to be unresolved.
If you see errors about the V type, check that the template <typename V> line is immediately above the class declaration.
help me debug errors in my HashTable.h template header
Test the hash table with guest data
Before you can test the hash table, you need a class to store in it. The Guest class holds the guest's ID, name, and phone number.
- Open Guest.h and replace the // placeholder comment with this class implementation:
#include <iostream>
#include <string>
class Guest {
private:
int guestId;
std::string name;
std::string phone;
public:
// Default constructor needed for hash table value storage
Guest() : guestId(0), name(""), phone("") {}
Guest(int id, std::string n, std::string p) : guestId(id), name(n), phone(p) {}
int getGuestId() const { return guestId; }
std::string getName() const { return name; }
std::string getPhone() const { return phone; }
// Lets std::cout print a Guest directly
friend std::ostream& operator<<(std::ostream& os, const Guest& guest) {
os << "Name: " << guest.name
<< " | Phone: " << guest.phone;
return os;
}
};
#endif
Why does Guest need a default constructor?
The hash table stores values by copy inside HashNode. Some compiler paths require a default constructor when copying or assigning value types. The default constructor initializes fields to safe zero-values.
The operator<< overload lets you print a Guest with std::cout << myGuest. This is what the hash table's display() method calls internally.
- Save Guest.h.
Now you can wire everything together in a test. You will create a hash table, insert three guests, search for one, remove another, and confirm the removal.
- Open main.cpp and replace the entire file contents with this test program:
#include <iostream>
#include "Hotel.h"
#include "Guest.h"
#include "HashTable.h"
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
// Test the hash table with Guest objects
HashTable<Guest> testTable;
testTable.insert(1001, Guest(1001, "Alice", "111-1111"));
testTable.insert(1002, Guest(1002, "Bob", "222-2222"));
testTable.insert(1003, Guest(1003, "Carol", "333-3333"));
Guest* found = testTable.search(1002);
if (found) std::cout << "Found: " << *found << std::endl;
testTable.remove(1001);
Guest* notFound = testTable.search(1001);
if (!notFound) std::cout << "Guest 1001 not found (removed)." << std::endl;
testTable.display();
return 0;
}
What does this test prove?
This test exercises all three core hash table operations. Insert adds three guests keyed by their ID. Search confirms O(1) lookup works for guest 1002. Remove deletes guest 1001, and a follow-up search proves the key is gone.
The final display() call prints all remaining guests, confirming only 1002 and 1003 survive.
- Save main.cpp.
Before you compile, predict what you expect to see. Will the search for guest 1002 succeed? Will guest 1001 still appear after removal?
- Compile and run the program by running these commands in your VS Code terminal:
g++ -o hotel.exe main.cpp -std=c++17
.\hotel.exe
You should see output like this:
Welcome to Grand Hotel Management System
Found: Name: Bob | Phone: 222-2222
Guest 1001 not found (removed).
[ID: 1002] Name: Bob | Phone: 222-2222
[ID: 1003] Name: Carol | Phone: 333-3333
What should I see?
The search found Bob by his ID (1002) in O(1). After removing Alice (1001), a second search confirms she is gone. The display shows only the two remaining guests. The order of the displayed guests may differ depending on which bucket they hash into.
Compilation errors?
If you see undefined reference or was not declared in this scope, check that main.cpp includes both "Guest.h" and "HashTable.h". The order of includes matters when one file depends on another.
If you see errors mentioning operator<<, check that the friend std::ostream& overload in Guest.h has the correct signature. The friend keyword must appear inside the class body.
help me fix compilation errors in my hash table project
Your foundational data structures are working. The linked list handles chains of data, and the hash table delivers O(1) lookups. Next up, you will build the room hierarchy using inheritance and polymorphism to model different room types with different prices.
Build the Room Hierarchy and BST
Your hotel has different room types: standard rooms, deluxe rooms, and suites. Each type has its own nightly price. The system needs to call getPrice() and getRoomType() on any room without knowing its concrete type.
An abstract base class with pure virtual functions solves this perfectly. This is polymorphism solving a real problem. You will also build a Binary Search Tree that stores rooms sorted by number, giving you efficient search and sorted display via inorder traversal.
In this step, get ready to:
- Build an abstract Room class with derived StandardRoom, DeluxeRoom, and Suite types.
- Implement a binary search tree that stores rooms sorted by number with insert, remove, search, and inorder traversal.
- Test polymorphism by displaying all 9 rooms sorted through the BST.
Implement the Room abstract base class
An abstract class defines a contract that all derived classes must fulfill. The Room base class declares pure virtual methods that each room type overrides with its own implementation.
- Open Room.h in VS Code.
- Replace the placeholder content with the abstract Room base class:
#ifndef ROOM_H
#define ROOM_H
#include <iostream>
#include <string>
// Abstract base class - cannot be instantiated directly
class Room {
protected:
int roomNumber;
bool isOccupied;
public:
Room(int num) : roomNumber(num), isOccupied(false) {}
virtual ~Room() {}
// Pure virtual functions - derived classes MUST override these
virtual std::string getRoomType() const = 0;
virtual double getPrice() const = 0;
int getRoomNumber() const { return roomNumber; }
bool getIsOccupied() const { return isOccupied; }
void setOccupied(bool status) { isOccupied = status; }
// Virtual display uses polymorphic calls to getRoomType and getPrice
virtual void display() const {
std::cout << " " << roomNumber << "\t| "
<< getRoomType() << "\t| Rs."
<< getPrice() << "\t| "
<< (isOccupied ? "Occupied" : "Available")
<< std::endl;
}
};
What does this code do?
The = 0 syntax on getRoomType() and getPrice() makes them pure virtual. Any class that inherits from Room MUST provide its own implementation of these methods, or the compiler will refuse to compile.
The display() method calls getRoomType() and getPrice() without knowing which room type it is dealing with. At runtime, the correct derived version runs automatically. This is polymorphism in action.
- Now add the three derived room types below the Room class, followed by the closing #endif:
// Each derived class overrides the pure virtuals with its own values
class StandardRoom : public Room {
public:
StandardRoom(int num) : Room(num) {}
std::string getRoomType() const override { return "Standard"; }
double getPrice() const override { return 2000.0; }
};
class DeluxeRoom : public Room {
public:
DeluxeRoom(int num) : Room(num) {}
std::string getRoomType() const override { return "Deluxe"; }
double getPrice() const override { return 3500.0; }
};
class Suite : public Room {
public:
Suite(int num) : Room(num) {}
std::string getRoomType() const override { return "Suite"; }
double getPrice() const override { return 5000.0; }
};
#endif
What does this code do?
Each derived class calls the Room constructor with the room number, then provides its own price and type string. StandardRoom costs Rs.2000, DeluxeRoom costs Rs.3500, and Suite costs Rs.5000.
The override keyword tells the compiler to verify these methods actually override a virtual method in the base class. If you misspell the method name, the compiler catches it.
- Save Room.h.
- Confirm there are no red squiggles in VS Code. The file should show no IntelliSense errors.
Seeing red squiggles in Room.h?
- Check that both #include <iostream> and #include <string> are at the top. The string header is needed for std::string.
- Verify the #ifndef ROOM_H, #define ROOM_H, and #endif lines wrap the entire file.
- Make sure the virtual ~Room() {} destructor has an empty body (the curly braces), not a semicolon.
Why is my Room.h showing compiler errors?
Why an abstract class instead of a simple Room with a type string?
You could store the room type as a string and the price as a field. But then adding a new room type means editing existing code. With abstract classes, you add a new derived class without touching any existing class. The system handles it automatically through polymorphism.
Implement the binary search tree
The BST stores rooms sorted by room number. Inserting rooms in any order still produces a sorted display through inorder traversal (left, root, right). You will also implement deletion with all three cases: no children, one child, and two children using the inorder successor.
- Open BST.h in VS Code.
- Replace the placeholder content with the class opening, the TreeNode struct, and the insert and findMin helpers:
#ifndef BST_H
#define BST_H
#include <iostream>
#include "Room.h"
class RoomBST {
private:
// Each tree node holds a Room pointer and links to children
struct TreeNode {
Room* room;
TreeNode* left;
TreeNode* right;
TreeNode(Room* r) : room(r), left(nullptr), right(nullptr) {}
};
TreeNode* root;
// Recursively insert by comparing room numbers
TreeNode* insertHelper(TreeNode* node, Room* room) {
if (!node) return new TreeNode(room);
if (room->getRoomNumber() < node->room->getRoomNumber()) {
node->left = insertHelper(node->left, room);
} else if (room->getRoomNumber() > node->room->getRoomNumber()) {
node->right = insertHelper(node->right, room);
}
return node;
}
// Find the leftmost node (smallest value) in a subtree
TreeNode* findMin(TreeNode* node) {
while (node && node->left) {
node = node->left;
}
return node;
}
What does this code do?
The TreeNode struct stores a Room pointer (enabling polymorphism) plus left and right child pointers. The insertHelper recursively navigates the tree: smaller room numbers go left, larger ones go right.
The findMin helper walks left until there is no more left child. This finds the inorder successor, which you need for deletion of nodes with two children.
- Add the removeHelper method directly below findMin. This handles all three BST deletion cases:
// Handles 3 deletion cases: no child, one child, two children
TreeNode* removeHelper(TreeNode* node, int roomNumber) {
if (!node) return nullptr;
if (roomNumber < node->room->getRoomNumber()) {
node->left = removeHelper(node->left, roomNumber);
} else if (roomNumber > node->room->getRoomNumber()) {
node->right = removeHelper(node->right, roomNumber);
} else {
// Case 1: no left child - replace with right
if (!node->left) {
TreeNode* temp = node->right;
delete node->room;
delete node;
return temp;
// Case 2: no right child - replace with left
} else if (!node->right) {
TreeNode* temp = node->left;
delete node->room;
delete node;
return temp;
}
// Case 3: two children - swap with inorder successor
TreeNode* successor = findMin(node->right);
Room* tempRoom = node->room;
node->room = successor->room;
successor->room = tempRoom;
node->right = removeHelper(node->right, tempRoom->getRoomNumber());
}
return node;
}
What does this code do?
BST deletion is the trickiest operation. Case 1 (no left child): just return the right subtree. Case 2 (no right child): return the left subtree. Both cases also free the removed node's memory.
Case 3 (two children): find the inorder successor (smallest node in the right subtree), swap the Room pointers, then recursively delete the successor from the right subtree. This preserves the BST ordering property.
- Add the searchHelper method directly below removeHelper:
// Binary search: compare and go left or right
Room* searchHelper(TreeNode* node, int roomNumber) {
if (!node) return nullptr;
if (roomNumber == node->room->getRoomNumber()) return node->room;
if (roomNumber < node->room->getRoomNumber())
return searchHelper(node->left, roomNumber);
return searchHelper(node->right, roomNumber);
}
// Inorder traversal prints rooms sorted by number
void inorderHelper(TreeNode* node) const {
if (!node) return;
inorderHelper(node->left);
node->room->display();
inorderHelper(node->right);
}
// Post-order traversal to free all memory
void destroyTree(TreeNode* node) {
if (!node) return;
destroyTree(node->left);
destroyTree(node->right);
delete node->room;
delete node;
}
int countHelper(TreeNode* node) const {
if (!node) return 0;
return 1 + countHelper(node->left) + countHelper(node->right);
}
int countAvailableHelper(TreeNode* node) const {
if (!node) return 0;
int c = node->room->getIsOccupied() ? 0 : 1;
return c + countAvailableHelper(node->left) + countAvailableHelper(node->right);
}
What does this code do?
searchHelper does a standard binary search. The inorderHelper visits left, then current, then right. This naturally prints rooms in ascending order by number.
destroyTree uses post-order traversal (children first, then parent) so that no node is freed while its children still exist. The count helpers recursively tally total and available rooms.
- Add the public interface below the private helpers. This exposes the BST operations to the rest of the program:
public:
RoomBST() : root(nullptr) {}
~RoomBST() {
destroyTree(root);
}
void insert(Room* room) {
root = insertHelper(root, room);
}
void remove(int roomNumber) {
root = removeHelper(root, roomNumber);
}
Room* search(int roomNumber) {
return searchHelper(root, roomNumber);
}
What does this code do?
Each public method is a thin wrapper that calls the corresponding recursive private helper, passing root as the starting node. The destructor calls destroyTree to free all allocated memory when the BST goes out of scope.
- Add the remaining public methods and close the class with the file's #endif:
void displayAll() const {
if (!root) {
std::cout << " No rooms in the system." << std::endl;
return;
}
std::cout << " Room\t| Type\t\t| Price\t\t| Status" << std::endl;
std::cout << " ------------------------------------------------" << std::endl;
inorderHelper(root);
}
int getTotalRooms() const { return countHelper(root); }
int getAvailableRooms() const { return countAvailableHelper(root); }
TreeNode* getRoot() const { return root; }
// Collect available rooms into an array for external use
void collectAvailable(TreeNode* node, Room** arr, int& idx, int maxSize) const {
if (!node || idx >= maxSize) return;
collectAvailable(node->left, arr, idx, maxSize);
if (!node->room->getIsOccupied()) {
arr[idx++] = node->room;
}
collectAvailable(node->right, arr, idx, maxSize);
}
};
#endif
What does this code do?
displayAll prints a formatted table header then runs inorder traversal to show rooms sorted by number. Each room's polymorphic display() method prints its own type, price, and occupancy status.
collectAvailable traverses the tree and gathers pointers to unoccupied rooms into an external array. You will use this later for room recommendations.
- Save BST.h.
- Confirm there are no red squiggles from IntelliSense. The class definition is now complete.
Seeing errors in BST.h?
- Check that #include "Room.h" uses double quotes (not angle brackets). It is a local header, not a system one.
- Verify every opening brace { has a matching closing brace }. The class must close with }; (semicolon after the brace).
- Make sure Room.h is saved and error-free. BST.h depends on it.
Help me fix compiler errors in my BST.h file.
Test the room hierarchy with the BST
Both data structures are implemented. Now you will wire them together: insert rooms of different types into the BST and display them sorted.
- Open main.cpp.
- Replace the entire file content with the following test code:
#include <iostream>
#include "Hotel.h"
#include "BST.h"
#include "Room.h"
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
// Create BST and insert rooms of each type
RoomBST bst;
for (int i = 101; i <= 104; i++) bst.insert(new StandardRoom(i));
for (int i = 201; i <= 203; i++) bst.insert(new DeluxeRoom(i));
for (int i = 301; i <= 302; i++) bst.insert(new Suite(i));
// Inorder traversal displays rooms sorted by number
bst.displayAll();
// Search demonstrates O(log n) lookup by room number
Room* r = bst.search(202);
if (r) std::cout << "Room 202 type: " << r->getRoomType() << std::endl;
return 0;
}
What does this code do?
You insert 4 StandardRooms (101-104), 3 DeluxeRooms (201-203), and 2 Suites (301-302). Each new StandardRoom(i) creates a polymorphic Room object on the heap.
displayAll() runs inorder traversal, which visits nodes in sorted order. The search for room 202 proves the BST can locate a specific room and call getRoomType() polymorphically.
- Save main.cpp.
Before you compile, predict: what order will the 9 rooms print in? Will room 301 appear before or after room 104?
- Compile and run by executing these commands in your VS Code terminal:
g++ -o hotel.exe main.cpp -std=c++17
.\hotel.exe
You should see all 9 rooms printed in sorted order: 101, 102, 103, 104, 201, 202, 203, 301, 302. Each row shows the room number, type, price, and "Available" status. The last line prints "Room 202 type: Deluxe".
Compile errors or no output?
- If you see "No such file or directory" for Room.h, make sure Room.h is saved in the same HotelManagement folder as main.cpp.
- If you see "cannot declare variable bst to be of abstract type", you may have forgotten the override keyword on one of the derived class methods.
- If rooms print in wrong order, check that insertHelper compares room numbers correctly: smaller goes left, larger goes right.
Help me fix my BST and Room compilation errors.
✔️ Awesome, I've got everything!
Great. Double check you have saved both Room.h and BST.h.
ⓧ I'd like to double check the full code
Your project now has three complete files for this step. Scroll up and confirm each chunk is present, in order:
- Room.h: The abstract Room base class with display(), followed by StandardRoom, DeluxeRoom, and Suite.
- BST.h: TreeNode struct and insertHelper, then findMin and removeHelper, then searchHelper through countAvailableHelper, then the public methods (constructor through search), then displayAll through collectAvailable and closing.
- main.cpp: Includes Hotel.h, BST.h, Room.h. Creates a RoomBST, inserts 9 rooms, calls displayAll(), searches for room 202.
Your room hierarchy and BST are working together. You have polymorphism giving each room its own price, and a BST keeping them sorted. Next up, you will wire these data structures into a full Hotel system with check-in, check-out, and guest search.
Wire the Hotel System Together
You have three working data structures sitting in isolation. A hash table that can store guests, a BST that can store rooms, and a linked list that can track records.
Individual data structures are useless until connected. In this step, you will build the Hotel class that orchestrates guest check-in across all three structures. The hash table stores the guest, the BST finds and marks the room, and the linked list records the booking.
In this step, get ready to:
- Build the Hotel class that owns all three data structures.
- Implement check-in, check-out, and guest search operations.
- Discover a double-booking bug in the first version.
Implement the Booking struct in Hotel.h
The Hotel.h file is currently a placeholder. You will rebuild it from scratch, starting with a Booking struct that represents a single guest-to-room assignment.
- Open Hotel.h in VS Code and replace its entire contents with the following code:
#ifndef HOTEL_H
#define HOTEL_H
#include <iostream>
#include <string>
#include <fstream>
#include "Room.h"
#include "Guest.h"
#include "HashTable.h"
#include "BST.h"
#include "LinkedList.h"
// Represents a single guest-to-room assignment
struct Booking {
int guestId;
int roomNumber;
std::string guestName;
std::string guestPhone;
Booking() : guestId(0), roomNumber(0), guestName(""), guestPhone("") {}
Booking(int gId, int rNum, std::string name, std::string phone)
: guestId(gId), roomNumber(rNum), guestName(name), guestPhone(phone) {}
bool operator==(const Booking& other) const {
return guestId == other.guestId && roomNumber == other.roomNumber;
}
friend std::ostream& operator<<(std::ostream& os, const Booking& b) {
os << " Guest ID: " << b.guestId
<< " | Name: " << b.guestName
<< " | Room: " << b.roomNumber;
return os;
}
};
What does this code do?
The Booking struct stores the guest ID, room number, guest name, and phone for each reservation.
- operator== lets the linked list's removeByValue find and delete a specific booking by matching guest ID and room number.
- operator<< lets you print bookings directly with std::cout, which the linked list's display() method uses.
- Directly below the Booking struct (before the #endif), add the Hotel class with its constructor and room display method:
class Hotel {
private:
HashTable<Guest> guestTable;
RoomBST roomTree;
LinkedList<Booking> bookings;
std::string hotelName;
// Populates the BST with 9 hotel rooms
void initializeRooms() {
for (int i = 101; i <= 104; i++)
roomTree.insert(new StandardRoom(i));
for (int i = 201; i <= 203; i++)
roomTree.insert(new DeluxeRoom(i));
for (int i = 301; i <= 302; i++)
roomTree.insert(new Suite(i));
}
public:
Hotel(std::string name) : hotelName(name), guestTable(53) {
initializeRooms();
}
// Displays all rooms via BST inorder traversal
void displayRooms() const {
std::cout << "\n===== " << hotelName << " - Room Status =====" << std::endl;
roomTree.displayAll();
std::cout << " ------------------------------------------------" << std::endl;
std::cout << " Total: " << roomTree.getTotalRooms()
<< " | Available: " << roomTree.getAvailableRooms() << std::endl;
}
What does this code do?
The Hotel class owns all three data structures: a HashTable<Guest> for O(1) guest lookup, a RoomBST for sorted room storage, and a LinkedList<Booking> for booking history.
The constructor calls initializeRooms() to populate the BST with 4 Standard rooms, 3 Deluxe rooms, and 2 Suites. The displayRooms() method delegates to the BST's inorder traversal, printing them sorted by room number.
The Hotel class is not complete yet (you will add more methods shortly), but you can verify it compiles. First, you need a temporary closing brace.
- After the displayRooms() method, add a temporary class closing and the getRoomTree() accessor, then close the file with #endif:
RoomBST& getRoomTree() { return roomTree; }
};
#endif
- Save Hotel.h.
- Now update main.cpp to test the Hotel class. Replace its entire contents with this:
#include <iostream>
#include "Hotel.h"
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
std::cout << "Powered by custom Data Structures (No STL!)" << std::endl;
Hotel hotel("Grand Hotel");
hotel.displayRooms();
return 0;
}
- Save main.cpp.
- Compile and run by entering these commands in your terminal:
g++ -o hotel.exe main.cpp -std=c++17
.\hotel.exe
You should see the welcome message followed by all 9 rooms displayed in sorted order (101 through 302), all showing "Available". The Hotel class is now orchestrating the BST internally.
Seeing a compilation error?
- Check that your Hotel.h includes all five headers: Room.h, Guest.h, HashTable.h, BST.h, and LinkedList.h.
- Verify the closing }; for the Hotel class appears before #endif.
- Make sure main.cpp no longer includes BST.h or Room.h directly. Hotel.h pulls them in.
Why won't my Hotel class compile?
Implement the check-in method
Check-in is where all three data structures work together for the first time. One operation touches the BST, the hash table, and the linked list in sequence.
- In Hotel.h, find the line that says RoomBST& getRoomTree() { return roomTree; }.
- Add the following checkIn() method directly above that line:
void checkIn() {
int guestId, roomNumber;
std::string name, phone;
std::cout << "\n--- Check-In ---" << std::endl;
std::cout << "Enter Guest ID: ";
std::cin >> guestId;
std::cin.ignore();
std::cout << "Enter Guest Name: ";
std::getline(std::cin, name);
std::cout << "Enter Phone: ";
std::getline(std::cin, phone);
std::cout << "Enter Room Number: ";
std::cin >> roomNumber;
Room* room = roomTree.search(roomNumber);
if (!room) {
std::cout << "Error: Room " << roomNumber << " does not exist!" << std::endl;
return;
}
// NOTE: No occupied check here — double-booking bug
room->setOccupied(true);
Guest newGuest(guestId, name, phone);
guestTable.insert(guestId, newGuest);
Booking newBooking(guestId, roomNumber, name, phone);
bookings.insertBack(newBooking);
std::cout << "Check-in successful! " << name
<< " -> Room " << roomNumber
<< " (" << room->getRoomType() << ", Rs." << room->getPrice() << "/night)"
<< std::endl;
}
What does this code do?
- Prompts the user for guest details and a room number.
- roomTree.search(roomNumber) uses the BST to find the room in O(log n) time.
- guestTable.insert(guestId, newGuest) stores the guest in the hash table for O(1) future lookups.
- bookings.insertBack(newBooking) appends the booking record to the linked list.
- Save Hotel.h.
- Update main.cpp to call checkIn() after displaying rooms. Find the line hotel.displayRooms(); and add this line directly below it:
hotel.checkIn();
- Save main.cpp and compile by running:
g++ -o hotel.exe main.cpp -std=c++17
.\hotel.exe
- The program will display all rooms and then prompt you for a check-in.
- Enter Guest ID: 1001.
- Enter Guest Name: Alice.
- Enter Phone: 9876543210.
- Enter Room Number: 101.
You should see: Check-in successful! Alice -> Room 101 (Standard, Rs.2000/night). All three data structures worked together in a single operation.
Check-in not prompting for name?
- Make sure the std::cin.ignore() line appears after reading the guest ID. Without it, the leftover newline character from std::cin >> guestId gets consumed by std::getline, skipping the name prompt entirely.
Why is my check-in skipping the name input?
Complete the system and discover the bug
With check-in working, you need three more methods to make the system complete: check-out, guest search, and booking display.
- In Hotel.h, add the checkOut() method directly below checkIn():
void checkOut() {
int roomNumber;
std::cout << "\n--- Check-Out ---" << std::endl;
std::cout << "Enter Room Number: ";
std::cin >> roomNumber;
Room* room = roomTree.search(roomNumber);
if (!room) {
std::cout << "Error: Room " << roomNumber << " does not exist!" << std::endl;
return;
}
if (!room->getIsOccupied()) {
std::cout << "Room " << roomNumber << " is already vacant." << std::endl;
return;
}
room->setOccupied(false);
LinkedList<Booking>::Node* current = bookings.getHead();
while (current) {
if (current->data.roomNumber == roomNumber) {
std::cout << "Guest " << current->data.guestName << " checked out." << std::endl;
std::cout << "Total bill: Rs." << room->getPrice() << std::endl;
guestTable.remove(current->data.guestId);
bookings.removeByValue(current->data);
break;
}
current = current->next;
}
}
What does this code do?
- Searches the BST for the room and checks if it is occupied.
- Walks the bookings linked list to find the matching booking by room number.
- Removes the guest from the hash table, removes the booking from the linked list, and marks the room as available.
- Calls room->getPrice() which uses polymorphism to calculate the bill based on the room's concrete type.
- Add the searchGuest() and displayBookings() methods directly below checkOut():
void searchGuest() {
int guestId;
std::cout << "\n--- Search Guest ---" << std::endl;
std::cout << "Enter Guest ID: ";
std::cin >> guestId;
Guest* found = guestTable.search(guestId);
if (found) {
std::cout << "Guest Found! " << *found << std::endl;
} else {
std::cout << "Guest with ID " << guestId << " not found." << std::endl;
}
}
void displayBookings() const {
std::cout << "\n===== Active Bookings =====" << std::endl;
if (bookings.isEmpty()) {
std::cout << " No active bookings." << std::endl;
return;
}
bookings.display();
}
What does this code do?
- searchGuest() calls guestTable.search(guestId) for O(1) average-time lookup via the hash table.
- displayBookings() delegates to the linked list's display() method, which traverses and prints every active booking.
- Save Hotel.h.
- Verify it compiles by running:
g++ -o hotel.exe main.cpp -std=c++17
You should see no errors. Now it is time to add the full menu in main.cpp.
- Replace the entire contents of main.cpp with the menu display function:
#include <iostream>
#include "Hotel.h"
void displayMenu() {
std::cout << "\n========================================" << std::endl;
std::cout << " Hotel Management System" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << " 1. Display All Rooms" << std::endl;
std::cout << " 2. Check-In Guest" << std::endl;
std::cout << " 3. Check-Out Guest" << std::endl;
std::cout << " 4. Search Guest by ID" << std::endl;
std::cout << " 5. Display Active Bookings" << std::endl;
std::cout << " 6. Exit" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << " Enter choice: ";
}
- Directly below the displayMenu() function, add the main() function with the menu loop:
int main() {
std::cout << "Welcome to Grand Hotel Management System" << std::endl;
std::cout << "Powered by custom Data Structures (No STL!)" << std::endl;
Hotel hotel("Grand Hotel");
int choice;
do {
displayMenu();
std::cin >> choice;
switch (choice) {
case 1:
hotel.displayRooms();
break;
case 2:
hotel.checkIn();
break;
case 3:
hotel.checkOut();
break;
case 4:
hotel.searchGuest();
break;
case 5:
hotel.displayBookings();
break;
case 6:
std::cout << "Goodbye." << std::endl;
break;
default:
std::cout << "Invalid choice. Try again." << std::endl;
}
} while (choice != 6);
return 0;
}
What does this code do?
The do-while loop shows the menu, reads the user's choice, and dispatches to the corresponding Hotel method via a switch statement. The loop continues until the user picks option 6 (Exit).
- Save main.cpp and compile:
g++ -o hotel.exe main.cpp -std=c++17
.\hotel.exe
You should see the welcome message and the menu with 6 options. The system is running. Now test it with a deliberate scenario.
Before you proceed, think about this: what do you expect will happen if you check in two different guests to the same room? Will it produce an error?
- Enter 2 to check in a guest.
- Enter Guest ID: 1001, Name: Alice, Phone: 9876543210, Room: 101.
- Enter 2 again to check in a second guest.
- Enter Guest ID: 1002, Name: Bob, Phone: 1234567890, Room: 101 (the same room).
Both check-ins succeed with no error. The system happily prints "Check-in successful!" for Bob in room 101, even though Alice is already there.
- Enter 5 to display active bookings.
You will see both bookings listed, but the hash table silently overwrote Alice's guest entry with Bob's (same key slot). Alice's booking is effectively lost. This is the double-booking bug.
- Enter 6 to exit the program.
Why did this happen?
The checkIn() method searches for the room in the BST and immediately marks it as occupied. It never asks "is this room already occupied?" before proceeding.
This teaches a critical lesson: querying state before mutating it is not optional. In the next step, you will fix this with a single validation check.
✔️ Awesome, I've got everything!
Make sure both Hotel.h and main.cpp are saved.
ⓧ I'd like to double check the full code
Hotel.h should contain these sections in order. Scroll up and confirm each is present:
- Include guards and all six #include directives (iostream, string, fstream, Room.h, Guest.h, HashTable.h, BST.h, LinkedList.h).
- Booking struct with 4 members, default constructor, parameterized constructor, operator==, and operator<<.
- Hotel class with private members (guestTable, roomTree, bookings, hotelName) and initializeRooms().
- Constructor, displayRooms(), checkIn() (no occupied check), checkOut(), searchGuest(), displayBookings(), getRoomTree().
- Closing }; and #endif.
main.cpp should contain these sections in order:
- #include <iostream> and #include "Hotel.h" (only these two includes).
- displayMenu() function printing 6 menu options (Display, Check-In, Check-Out, Search, Bookings, Exit).
- main() with a do-while loop and a 6-case switch statement, looping until choice equals 6.
You now have a complete hotel management system powered entirely by custom data structures. The BST, hash table, and linked list are all wired together through a single Hotel class. In the next step, you will fix the double-booking bug and add file persistence so the system remembers its state between runs.
Fix the Bug and Add File Persistence
You discovered in the last step that your hotel system has a dangerous flaw. Two different guests can check into the same room because checkIn() never validates whether a room is already taken.
In this step, you'll fix that bug with a defensive validation check. Then you'll add File I/O so the hotel remembers its state between program restarts, turning this from a toy into a real application.
In this step, get ready to:
- Fix the double-booking vulnerability in checkIn().
- Add saveToFile() and loadFromFile() methods for file persistence.
- Wire persistence into the menu and constructor so data survives restarts.
Fix the double-booking bug in checkIn()
The bug lives in one specific spot. After checkIn() finds the room in the BST, it immediately calls setOccupied(true) without checking whether the room is already taken.
- In Hotel.h, scroll to the checkIn() method.
- Find this comment and the line below it:
// NOTE: No occupied check here — double-booking bug
room->setOccupied(true);
- Delete that comment line and replace it with the following validation check, keeping room->setOccupied(true); on the line after:
if (room->getIsOccupied()) {
std::cout << "Error: Room " << roomNumber << " is already occupied!" << std::endl;
return;
}
room->setOccupied(true);
What does this code do?
Before marking a room as occupied, the code now checks room->getIsOccupied(). If the room already has a guest, it prints an error and returns immediately, preventing the double-booking.
This is defensive programming. Always query state before mutating it.
- Save Hotel.h.
- Compile the project by running:
g++ -o hotel.exe main.cpp -std=c++17
Before you test, think about this: what do you expect to happen now if you try to check in a second guest to room 101?
- Run the program:
.\hotel.exe
- Choose option 2 to check in a guest. Enter ID 1001, name Alice, phone 555-0001, room 101.
- Choose option 2 again. Enter ID 1002, name Bob, phone 555-0002, room 101.
You should see Error: Room 101 is already occupied! printed. The double-booking bug is fixed.
- Choose option 6 to exit the program.
Double-booking still works?
Make sure you placed the if (room->getIsOccupied()) check BEFORE the room->setOccupied(true) line, not after it.
Also confirm you deleted the old comment line entirely. If the code still compiles but doesn't reject, the check might be placed after the room is already marked occupied.
Help me debug why my room occupancy check isn't blocking double-bookings.
Implement saveToFile() and wire it to the menu
Your hotel data currently vanishes the moment the program closes. The saveToFile() method will iterate the bookings linked list and write each active booking to a file.
- In Hotel.h, scroll to the end of the Hotel class (just above the final RoomBST& getRoomTree() method).
- Add the saveToFile() method by copying the following code above getRoomTree():
void saveToFile() {
std::ofstream file("hotel_data.txt");
if (!file.is_open()) {
std::cout << "Error: Could not save data." << std::endl;
return;
}
LinkedList<Booking>::Node* current = bookings.getHead();
while (current) {
file << current->data.guestId << ","
<< current->data.guestName << ","
<< current->data.guestPhone << ","
<< current->data.roomNumber << std::endl;
current = current->next;
}
file.close();
std::cout << "Data saved successfully!" << std::endl;
}
What does this code do?
- Opens (or creates) hotel_data.txt for writing using std::ofstream.
- Walks the bookings linked list using getHead() and writes each booking as a comma-separated line: guestId,name,phone,roomNumber.
- This is your linked list traversal doing real work. Each node's data gets serialized to a text file.
- Save Hotel.h.
Now you need to update main.cpp so the menu calls this new method when the user exits.
- In main.cpp, find this line inside displayMenu():
std::cout << " 6. Exit" << std::endl;
- Replace it with:
std::cout << " 6. Save & Exit" << std::endl;
- Now find the case 6: block in the switch statement:
case 6:
std::cout << "Goodbye." << std::endl;
break;
- Replace the entire case 6 block with:
case 6:
hotel.saveToFile();
std::cout << "Thank you! Goodbye." << std::endl;
break;
What changed?
Option 6 now calls hotel.saveToFile() before printing the goodbye message. Every time the user exits, all active bookings are written to disk.
- Save main.cpp and compile:
g++ -o hotel.exe main.cpp -std=c++17
- Run the program:
.\hotel.exe
- Choose option 2 to check in a guest. Enter ID 1001, name Alice, phone 555-0001, room 101.
- Choose option 6 to Save & Exit.
You should see Data saved successfully! followed by the goodbye message.
- Confirm the file was created by running:
ls
You should see hotel_data.txt listed alongside your other project files.
Don't see hotel_data.txt?
Make sure you chose option 6 (Save & Exit) and not just closed the terminal window. The file is only written when saveToFile() is explicitly called.
Also check that the <fstream> header is included at the top of Hotel.h. It should already be there from the previous step.
Help me figure out why hotel_data.txt isn't being created.
Implement loadFromFile() and complete persistence
Saving data is only half the story. Right now if you restart the program, all that saved data is ignored. The loadFromFile() method reads the saved file and reconstructs your data structures on startup.
- In Hotel.h, add the loadFromFile() method directly below saveToFile(). Start with the file opening and parsing loop:
void loadFromFile() {
std::ifstream file("hotel_data.txt");
if (!file.is_open()) return;
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue;
int guestId = 0, roomNumber = 0;
std::string name = "", phone = "";
int commaCount = 0;
std::string parts[4];
std::string current = "";
for (int i = 0; i < (int)line.length(); i++) {
if (line[i] == ',' && commaCount < 3) {
parts[commaCount] = current;
current = "";
commaCount++;
} else {
current += line[i];
}
}
parts[commaCount] = current;
What does this parsing loop do?
Each line in the file is formatted as guestId,name,phone,roomNumber. The for-loop splits the line on commas by iterating character-by-character.
This manual parsing avoids STL algorithms. The commaCount < 3 guard ensures commas inside a guest name (if any) get captured in the last field rather than splitting further.
- Immediately below that code (still inside the while loop), add the field extraction and data structure reconstruction:
if (commaCount < 3) continue;
guestId = std::stoi(parts[0]);
name = parts[1];
phone = parts[2];
roomNumber = std::stoi(parts[3]);
Room* room = roomTree.search(roomNumber);
if (room && !room->getIsOccupied()) {
room->setOccupied(true);
Guest guest(guestId, name, phone);
guestTable.insert(guestId, guest);
Booking booking(guestId, roomNumber, name, phone);
bookings.insertBack(booking);
}
}
file.close();
}
What does this reconstruction do?
- Validates the line has all 4 fields (skips malformed lines with commaCount < 3).
- Converts string fields to the right types using std::stoi().
- Rebuilds all three data structures: inserts the guest into the hash table, marks the room occupied in the BST, and adds a booking to the linked list.
- Save Hotel.h.
The method exists, but nothing calls it yet. The Hotel constructor needs to load saved data after initializing rooms.
- In Hotel.h, find the constructor:
Hotel(std::string name) : hotelName(name), guestTable(53) {
initializeRooms();
}
- Add loadFromFile(); on the line after initializeRooms(); so it looks like this:
Hotel(std::string name) : hotelName(name), guestTable(53) {
initializeRooms();
loadFromFile();
}
Why load after initializeRooms()?
The rooms must exist in the BST before loadFromFile() can search for them and mark them occupied. If you loaded before initializing, the BST would be empty and every roomTree.search() call would return nullptr.
- Save Hotel.h and compile:
g++ -o hotel.exe main.cpp -std=c++17
Before you run the program again, think: do you expect the guest you checked in earlier (Alice in room 101) to still be there?
- Run the program:
.\hotel.exe
- Choose option 1 to display all rooms.
Room 101 should show Occupied. Your data survived the restart.
- Choose option 4 and search for guest ID 1001. Confirm it finds Alice.
- Choose option 2 and try to check in another guest to room 101. Confirm you see the occupied error.
- Choose option 3 and check out room 101.
- Choose option 6 to Save & Exit.
- Run the program one more time:
.\hotel.exe
- Choose option 1. Room 101 should now show Available. The checkout was saved and loaded correctly.
Data not loading on restart?
Check that loadFromFile() is called inside the constructor, after initializeRooms(). If it's placed before, the BST is empty and searches return nullptr.
Also verify your hotel_data.txt format matches exactly: guestId,name,phone,roomNumber with no spaces after commas.
Help me debug why my hotel data isn't loading from the file on restart.
✔️ Awesome, I've got everything!
Great. Double check you've saved both Hotel.h and main.cpp.
ⓧ I'd like to double check the full code
Both files are longer than 30 lines at this point. Scroll through your code and confirm the following pieces are present, in order.
In Hotel.h:
- The Booking struct with all four fields.
- The Hotel constructor calling initializeRooms() then loadFromFile().
- The checkIn() method with the if (room->getIsOccupied()) validation BEFORE setOccupied(true).
- The checkOut(), searchGuest(), and displayBookings() methods unchanged.
- The saveToFile() method writing CSV lines to hotel_data.txt.
- The loadFromFile() method with the character-by-character parsing loop.
- The getRoomTree() getter at the end.
In main.cpp:
- The displayMenu() function shows 6. Save & Exit.
- The case 6: block calls hotel.saveToFile() before printing the goodbye message.
Your hotel system is now production-ready. It rejects invalid bookings, saves its state on exit, and restores it on startup. Three custom data structures working together with file persistence and defensive programming. That is a real application.
Secret mission
Smart Room Allocation with a Min-Heap
Your hotel system works, but guests still have to browse all rooms manually to find an affordable option. What if the system could instantly recommend the cheapest available room? Build a min-heap priority queue from scratch to make that happen.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The HotelManagement project folder on your Desktop containing all source files (main.cpp, LinkedList.h, HashTable.h, BST.h, Room.h, Guest.h, Hotel.h, MinHeap.h).
- The compiled binary hotel.exe in the same folder.
- The hotel_data.txt file storing booking state (created after your first save).
Keep everything running
No action needed. Choose this if you are still actively building or want to reference the code later.
- Your HotelManagement folder stays on your Desktop for future reference and extension.
- The hotel_data.txt file persists your last booking state so you can pick up where you left off.
- No cloud services or background processes are running. Nothing will incur costs.
Pause - I'll come back to this later
There are no persistent services to pause for this project. Everything is local files on your machine.
- Your project folder and all files remain on your Desktop until you decide to delete them.
- No background processes are running after you close the terminal.
Delete - I don't want to use this again
Remove all project files and start fresh. This deletes everything you built in this project.
- Delete the entire project folder by running the command for your operating system below.
macOS
- Open your terminal and run:
rm -rf ~/Desktop/HotelManagement
- Confirm the folder is gone by running:
ls ~/Desktop | grep HotelManagement
You should see no output, confirming the folder is deleted.
Windows
- Open your terminal and run:
rmdir /s /q %USERPROFILE%\Desktop\HotelManagement
- Confirm the folder is gone by running:
dir %USERPROFILE%\Desktop\HotelManagement
You should see a "File Not Found" or "The system cannot find the path specified" message.
Nice Work!
Nice Work!
Nice work! You just built a fully functional hotel management system powered entirely by custom data structures you implemented from scratch.
You've learned how to:
- Build custom data structures from scratch (hash table with separate chaining, binary search tree with insert/delete/traversal, and linked list template) and wire them together into a working application.
- Design an OOP class hierarchy using abstract classes, inheritance, and polymorphism to calculate room pricing through virtual method dispatch.
- Discover and fix a double-booking bug through defensive programming, and add file persistence so the system survives between runs.
- Secret Mission: Built a min-heap priority queue for smart room allocation that recommends the cheapest available room to guests.
Ready to quiz yourself?