Final Project

Reviewed & published by Brayan K

This is where everything clicks together. You'll build three real, runnable C++ programs from worked examples, extend each one yourself, then strike out on a project of your own — and see exactly where these skills can take your career.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Build

💡 How This Capstone Works

Each milestone follows the same loop the pros use: read a fully-working program, then do a small piece yourself to prove the idea stuck. The worked examples are deliberately complete so you can run them, break them, and see how the pieces fit before you extend them.

Think of a project like building with LEGO. The worked example is the finished model on the box; your "your turn" steps snap on one new brick at a time; the stretch challenge hands you a baseplate and a picture and lets you build it. By the end you'll start a project of your own from a blank-ish template — the skill that actually gets you hired.

🏆 Worked Example: Grade Manager

A complete system that stores students with grades, calculates averages, assigns letter grades, and ranks everyone. Read every comment, run it, and watch how a struct (the data) and a class (the behaviour) work together.

Skills combined: structs & classes, vectors, sorting with a lambda, const methods, formatted output

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

// MILESTONE 1: Student Grade Manager
// Stores students and their grades, calculates averages, and ranks them.

struct Student {
    string name;
    vector<int> grades;

    double average() const {                 // const = does not change the Student
        if (grades.empty()) return 0.0;
        double sum = 0;
        for (int g : grades) sum += g;        // range-for over every grade
        return sum / grades.size();           // size() is the grade count
    }

    char letterGrade() const {
        double avg = average();
        if (avg >= 90) return 'A';
        if (avg >= 80) return 'B';
        if (avg >= 70) return 'C';
        if (avg >= 60) return 'D';
        return 'F';
    }
};

class GradeManager {
    vector<Student> students;                 // private data, hidden inside the class
public:
    void addStudent(const string& name, const vector<int>& grades) {
        students.push_back({name, grades});   // {} builds a Student in place
    }

    void printReport() {
        // Sort by average (descending) using a lambda comparator.
        sort(students.begin(), students.end(),
            [](const Student& a, const Student& b) {
                return a.average() > b.average();
            });

        cout << "=== Student Report ===" << endl;
        cout << "Rank | Name          | Avg   | Grade" << endl;
        cout << "-----+---------------+-------+------" << endl;
        for (size_t i = 0; i < students.size(); i++) {
            auto& s = students[i];
            printf("  %2zu | %-13s | %5.1f | %c\n",
                   i + 1, s.name.c_str(), s.average(), s.letterGrade());
        }

        // Class statistics
        double classAvg = 0;
        for (const auto& s : students) classAvg += s.average();
        classAvg /= students.size();
        cout << "\nClass average: " << classAvg << endl;   // ~80.6
        cout << "Top student: " << students[0].name << endl; // Carol
    }
};

int main() {
    GradeManager manager;
    manager.addStudent("Alice",   {92, 88, 95, 91});
    manager.addStudent("Bob",     {78, 82, 75, 80});
    manager.addStudent("Carol",   {95, 98, 100, 97});
    manager.addStudent("Dave",    {65, 70, 68, 72});
    manager.addStudent("Eve",     {88, 85, 90, 87});
    manager.addStudent("Frank",   {55, 60, 58, 62});

    manager.printReport();
    return 0;
}

// ✅ Expected output:
//    === Student Report ===
//    Rank | Name          | Avg   | Grade
//    -----+---------------+-------+------
//       1 | Carol         |  97.5 | A
//       2 | Alice         |  91.5 | A
//       3 | Eve           |  87.5 | B
//       4 | Bob           |  78.8 | C
//       5 | Dave          |  68.8 | D
//       6 | Frank         |  58.8 | F
//    
//    Class average: 80.4583
//    Top student: Carol

🎯 Your Turn: report the failing students

One small piece. The list of students is already there — fill in the single blank so the loop prints only the students whose average is below 60, then check your output against the comment.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Student {
    string name;
    vector<int> grades;
    double average() const {
        if (grades.empty()) return 0.0;
        double sum = 0;
        for (int g : grades) sum += g;
        return sum / grades.size();
    }
};

int main() {
    // 🎯 YOUR TURN — list everyone who is failing (average below 60).
    vector<Student> students = {
        {"Alice", {92, 88, 95}},
        {"Dave",  {45, 50, 40}},
        {"Eve",   {88, 85, 90}},
        {"Frank", {55, 30, 58}},
    };

    cout << "Failing students:" << endl;
    for (const Student& s : students) {
        // 1) Only print students whose average is below 60
        if (s.average() ___ 60) {        // 👉 replace ___ with the < operator
            cout << "  " << s.name << " (" << s.average() << ")" << endl;
        }
    }

    // ✅ Expected output:
    //    Failing students:
    //      Dave (45)
    //      Frank (47.6667)
    return 0;
}

🏆 Worked Example: Task Manager

A small project-management tool with tasks, priorities, assignees, and status tracking. Notice how enum class gives each priority and status a safe, readable name instead of a magic number.

Skills combined: enum class, switch, vectors, filtering, formatted output, state changes

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
using namespace std;

// MILESTONE 2: Command-Line Task Manager
// A mini project tool with tasks, priorities, and filtering.

enum class Priority { LOW, MEDIUM, HIGH, CRITICAL };
enum class Status { TODO, IN_PROGRESS, DONE };

string priorityStr(Priority p) {
    switch (p) {
        case Priority::LOW: return "Low";
        case Priority::MEDIUM: return "Medium";
        case Priority::HIGH: return "High";
        case Priority::CRITICAL: return "CRITICAL";
    }
    return "?";
}

string statusStr(Status s) {
    switch (s) {
        case Status::TODO: return "Todo";
        case Status::IN_PROGRESS: return "In Progress";
        case Status::DONE: return "Done";
    }
    return "?";
}

struct Task {
    int id;
    string title;
    Priority priority;
    Status status;
    string assignee;
};

class TaskManager {
    vector<Task> tasks;
    int nextId = 1;
public:
    void add(const string& title, Priority p, const string& assignee) {
        tasks.push_back({nextId++, title, p, Status::TODO, assignee});
        cout << "Created task #" << (nextId - 1) << ": " << title << endl;
    }

    void complete(int id) {
        for (auto& t : tasks) {
            if (t.id == id) { t.status = Status::DONE; cout << "Completed: " << t.title << endl; return; }
        }
        cout << "Task #" << id << " not found" << endl;
    }

    void startWork(int id) {
        for (auto& t : tasks) {
            if (t.id == id) { t.status = Status::IN_PROGRESS; cout << "Started: " << t.title << endl; return; }
        }
    }

    void listAll() {
        cout << "\n=== All Tasks ===" << endl;
        printf("%-4s %-25s %-10s %-12s %s\n", "ID", "Title", "Priority", "Status", "Assignee");
        cout << string(70, '-') << endl;
        for (const auto& t : tasks) {
            printf("%-4d %-25s %-10s %-12s %s\n",
                t.id, t.title.c_str(), priorityStr(t.priority).c_str(),
                statusStr(t.status).c_str(), t.assignee.c_str());
        }
    }

    void listByPriority(Priority p) {
        cout << "\n=== " << priorityStr(p) << " Priority Tasks ===" << endl;
        for (const auto& t : tasks)
            if (t.priority == p)
                cout << "  #" << t.id << " " << t.title << " [" << statusStr(t.status) << "]" << endl;
    }

    void summary() {
        int todo = 0, inProgress = 0, done = 0;
        for (const auto& t : tasks) {
            if (t.status == Status::TODO) todo++;
            else if (t.status == Status::IN_PROGRESS) inProgress++;
            else done++;
        }
        cout << "\n=== Summary ===" << endl;
        cout << "Todo: " << todo << " | In Progress: " << inProgress << " | Done: " << done << endl;
    }
};

int main() {
    TaskManager tm;

    tm.add("Fix login bug", Priority::CRITICAL, "Alice");
    tm.add("Update docs", Priority::LOW, "Bob");
    tm.add("Add dark mode", Priority::MEDIUM, "Carol");
    tm.add("Optimize queries", Priority::HIGH, "Alice");
    tm.add("Write unit tests", Priority::HIGH, "Dave");
    tm.add("Deploy v2.0", Priority::CRITICAL, "Eve");

    tm.startWork(1);
    tm.startWork(4);
    tm.complete(1);

    tm.listAll();
    tm.listByPriority(Priority::CRITICAL);
    tm.summary();
    return 0;
}

// ✅ Expected output:
//    Created task #1: Fix login bug
//    Created task #2: Update docs
//    Created task #3: Add dark mode
//    Created task #4: Optimize queries
//    Created task #5: Write unit tests
//    Created task #6: Deploy v2.0
//    Started: Fix login bug
//    Started: Optimize queries
//    Completed: Fix login bug
//    
//    === All Tasks ===
//    ID   Title                     Priority   Status       Assignee
//    ----------------------------------------------------------------------
//    1    Fix login bug             CRITICAL   Done         Alice
//    2    Update docs               Low        Todo         Bob
//    3    Add dark mode             Medium     Todo         Carol
//    4    Optimize queries          High       In Progress  Alice
//    5    Write unit tests          High       Todo         Dave
//    6    Deploy v2.0               CRITICAL   Todo         Eve
//    
//    === CRITICAL Priority Tasks ===
//      #1 Fix login bug [Done]
//      #6 Deploy v2.0 [Todo]
//    
//    === Summary ===
//    Todo: 4 | In Progress: 1 | Done: 1

🎯 Your Turn: filter tasks by assignee

Managers always ask "what is on my plate?" Fill in the blank so the loop prints only the tasks that belong to owner. Comparing two std::string values for equality is exactly what you need.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Task {
    int id;
    string title;
    string assignee;
};

int main() {
    // 🎯 YOUR TURN — print only the tasks that belong to one person.
    vector<Task> tasks = {
        {1, "Fix login bug",    "Alice"},
        {2, "Update docs",      "Bob"},
        {3, "Optimize queries", "Alice"},
        {4, "Deploy v2.0",      "Eve"},
    };
    string owner = "Alice";

    cout << "Tasks for " << owner << ":" << endl;
    for (const Task& t : tasks) {
        // 1) Print the task only when its assignee equals "owner"
        if (t.assignee ___ owner) {      // 👉 replace ___ with == (compare for equality)
            cout << "  #" << t.id << " " << t.title << endl;
        }
    }

    // ✅ Expected output:
    //    Tasks for Alice:
    //      #1 Fix login bug
    //      #3 Optimize queries
    return 0;
}

🏆 Worked Example: Text Analyzer

Analyze a block of text for word frequency, sentence statistics, and readability. This one leans on the STL: an unordered_map to count words, istringstream to split them, and sort to rank them.

Skills combined: string processing, unordered_map, istringstream, sorting, constructors with initializer lists

#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;

// MILESTONE 3: Text Analyzer
// Analyze text for word frequency, sentence count, and readability.

class TextAnalyzer {
    string text;
    vector<string> words;
    unordered_map<string, int> freq;

    string toLower(string s) {
        transform(s.begin(), s.end(), s.begin(), ::tolower);
        return s;
    }

    string cleanWord(const string& w) {
        string clean;
        for (char c : w)
            if (isalpha(c) || c == '\'') clean += tolower(c);
        return clean;
    }

public:
    TextAnalyzer(const string& t) : text(t) {
        istringstream iss(t);
        string word;
        while (iss >> word) {                 // pull one word at a time
            string clean = cleanWord(word);
            if (!clean.empty()) {
                words.push_back(clean);
                freq[clean]++;                // map auto-creates the key, then counts
            }
        }
    }

    void basicStats() {
        int sentences = 0;
        for (char c : text) if (c == '.' || c == '!' || c == '?') sentences++;

        cout << "=== Basic Statistics ===" << endl;
        cout << "Characters: " << text.size() << endl;
        cout << "Words: " << words.size() << endl;
        cout << "Unique words: " << freq.size() << endl;
        cout << "Sentences: " << sentences << endl;
        if (sentences > 0)
            cout << "Avg words/sentence: " << words.size() / sentences << endl;
    }

    void topWords(int n = 10) {
        vector<pair<string, int>> sorted(freq.begin(), freq.end());
        sort(sorted.begin(), sorted.end(),
             [](const auto& a, const auto& b) { return a.second > b.second; });

        cout << "\n=== Top " << min(n, (int)sorted.size()) << " Words ===" << endl;
        for (int i = 0; i < min(n, (int)sorted.size()); i++)
            cout << "  " << sorted[i].first << ": " << sorted[i].second << endl;
    }

    void searchWord(const string& w) {
        string target = toLower(w);
        cout << "\nSearch '" << w << "': ";
        if (freq.count(target))
            cout << "found " << freq[target] << " times" << endl;
        else
            cout << "not found" << endl;
    }

    void readability() {
        double totalLen = 0;
        for (const auto& w : words) totalLen += w.size();
        double avgLen = totalLen / words.size();

        int longWords = 0;
        for (const auto& w : words) if (w.size() > 6) longWords++;

        cout << "\n=== Readability ===" << endl;
        cout << "Avg word length: " << avgLen << " chars" << endl;
        cout << "Long words (>6 chars): " << longWords
             << " (" << (100.0 * longWords / words.size()) << "%)" << endl;
        if (avgLen < 4.5) cout << "Level: Easy" << endl;
        else if (avgLen < 5.5) cout << "Level: Medium" << endl;
        else cout << "Level: Advanced" << endl;
    }
};

int main() {
    string sample =
        "The quick brown fox jumps over the lazy dog. "
        "The dog barked loudly at the fox. The fox ran quickly "
        "through the forest and disappeared into the darkness. "
        "Programming in C++ is challenging but rewarding. "
        "Templates and smart pointers make modern C++ powerful. "
        "Understanding memory management is essential for "
        "writing efficient and safe C++ applications.";

    TextAnalyzer analyzer(sample);
    analyzer.basicStats();
    analyzer.topWords(8);
    analyzer.searchWord("the");
    analyzer.searchWord("C++");
    analyzer.searchWord("python");
    analyzer.readability();
    return 0;
}

// ⚠️ No expected-output panel for this one, on purpose.
// An unordered container iterates in whatever order its hash function and
// bucket count produce. That order is not part of the standard, so it can
// differ between compilers and between library versions. Words tied on the
// same count can come out in either order.
//
// One real run on the machine that builds this site printed:
//    === Basic Statistics ===
//    Characters: 350
//    Words: 55
//    Unique words: 41
//    Sentences: 6
//    Avg words/sentence: 9
//    
//    === Top 8 Words ===
//      the: 7
//      c: 3
//      fox: 3
//      and: 3
//      dog: 2
//      is: 2
//      programming: 1
//      rewarding: 1
//    
//    Search 'the': found 7 times
//    
//    Search 'C++': not found
//    
//    Search 'python': not found
//    
//    === Readability ===
//    Avg word length: 5.16364 chars
//    Long words (>6 chars): 16 (29.0909%)
//    Level: Medium

Stretch Challenge: Mini Bank Report

No blanks this time — just a brief and an outline. You've seen loops, cout, and running totals in every milestone above; now wire them together yourself. Build it, run it, and check your output against the example in the comments.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Account {
    string owner;
    double balance;
};

int main() {
    // 🎯 STRETCH CHALLENGE: Mini bank report
    // The accounts are given. Print a report and the grand total yourself.
    vector<Account> accounts = {
        {"Alice", 1200.50},
        {"Bob",   340.00},
        {"Carol", 9875.25},
    };

    // 1. Print a header line:  cout << "=== Bank Report ===" << endl;
    // 2. Loop over every account and print: "  <owner>: $<balance>"
    // 3. Keep a running double "total" and add each balance to it
    // 4. After the loop, print: "Total deposits: $" << total
    //
    // ✅ Expected output:
    //    === Bank Report ===
    //      Alice: $1200.5
    //      Bob: $340
    //      Carol: $9875.25
    //    Total deposits: $11415.8

    // your code here
    return 0;
}

Common Pitfalls (and the fix)

📋 Quick Reference

TaskCodeNotes
Plain data bundlestruct Student { ... };members public by default
Class with hidden stateclass GradeManager { ... };members private by default
Add to a vectorv.push_back(x);grows the list
Loop by referencefor (const auto& t : v)no copies
Sort with a rulesort(b, e, [](a,b){ return a > b; });lambda comparator
Named constantsenum class Priority { LOW, HIGH };type-safe
Count occurrencesfreq[word]++;map auto-creates key
Split a string into wordsistringstream iss(t); iss >> word;one word per read
Compile cleang++ -std=c++17 -Wall -Wextrawarnings catch bugs

What You Can Do with C++

Career PathCore SkillsSalary Range (USD)
Systems ProgrammerOS, drivers, embedded, memory management$80,000 – $160,000
Game DeveloperUnreal Engine, ECS, rendering, physics$70,000 – $150,000
Embedded EngineerMicrocontrollers, RTOS, hardware interfaces$75,000 – $140,000
Quantitative DeveloperLow-latency trading, algorithms, networking$120,000 – $300,000+
Graphics/VFX EngineerOpenGL, Vulkan, shaders, rendering pipelines$90,000 – $170,000
Infrastructure EngineerDatabases, compilers, distributed systems$100,000 – $200,000

🪙 Business Opportunities

🚀 Capstone Starter Template

Use this template as a starting point for your own project. Pick an idea from the list below and build it from scratch!

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <map>
#include <algorithm>
#include <fstream>
using namespace std;

// === YOUR C++ CAPSTONE PROJECT STARTER ===
// Pick a project idea and build it!
//
// Beginner:
//   - Calculator with history
//   - Quiz game with scoring
//   - Simple address book
//
// Intermediate:
//   - Bank account system (OOP)
//   - File-based inventory manager
//   - Markdown to HTML converter
//
// Advanced:
//   - HTTP request parser
//   - JSON parser from scratch
//   - Thread pool implementation
//
// Expert:
//   - Custom allocator benchmark suite
//   - Mini compiler (tokenizer -> parser -> codegen)
//   - ECS game engine framework

// Example structure for a Bank Account project:
class Account {
    string owner;
    double balance;
    vector<string> history;
public:
    Account(const string& name, double initial = 0)
        : owner(name), balance(initial) {
        log("Account created with $" + to_string(initial));
    }

    bool deposit(double amount) {
        if (amount <= 0) return false;
        balance += amount;
        log("Deposited $" + to_string(amount));
        return true;
    }

    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        log("Withdrew $" + to_string(amount));
        return true;
    }

    void printStatement() const {
        cout << "\n=== " << owner << "'s Account ===" << endl;
        cout << "Balance: $" << balance << endl;
        cout << "Transaction history:" << endl;
        for (const auto& entry : history)
            cout << "  " << entry << endl;
    }

private:
    void log(const string& msg) {
        history.push_back(msg);
    }
};

int main() {
    cout << "=== C++ Capstone Project ===" << endl;
    cout << "This is a starter template. Expand it into" << endl;
    cout << "a full project using everything you've learned!" << endl;

    Account acc("Alice", 1000);
    acc.deposit(500);
    acc.deposit(250);
    acc.withdraw(100);
    acc.withdraw(75);
    acc.printStatement();

    cout << "\n=== Next Steps ===" << endl;
    cout << "1. Add multiple account types (Savings, Checking)" << endl;
    cout << "2. Add transfers between accounts" << endl;
    cout << "3. Add file persistence (save/load)" << endl;
    cout << "4. Add interest calculation" << endl;
    cout << "5. Add unit tests for all operations" << endl;

    return 0;
}

// ✅ Expected output:
//    === C++ Capstone Project ===
//    This is a starter template. Expand it into
//    a full project using everything you've learned!
//    
//    === Alice's Account ===
//    Balance: $1575
//    Transaction history:
//      Account created with $1000.000000
//      Deposited $500.000000
//      Deposited $250.000000
//      Withdrew $100.000000
//      Withdrew $75.000000
//    
//    === Next Steps ===
//    1. Add multiple account types (Savings, Checking)
//    2. Add transfers between accounts
//    3. Add file persistence (save/load)
//    4. Add interest calculation
//    5. Add unit tests for all operations

Project Ideas by Skill Level

🟢 Beginner

🔵 Intermediate

🟣 Advanced

🔴 Expert

Development Tips

📚 Free Resources to Continue Learning

Frequently Asked Questions

🎉 Congratulations!

You've completed the entire LearnCodingFast C++ course — from "Hello, World!" to custom allocators, game engines, and production architecture. You now have the knowledge to build high-performance systems in one of the world's most powerful programming languages.

What's next? Pick a project above, build something real, and add it to your GitHub portfolio. The best way to learn is to build.

Practice quiz

What is the only real difference between struct and class in C++?

  • struct cannot have methods
  • class cannot be copied
  • struct members are public by default; class members are private by default
  • struct lives on the stack, class on the heap

Answer: struct members are public by default; class members are private by default. They are almost identical; convention uses struct for plain data bundles and class for protected internal state.

In sort(v.begin(), v.end(), [](const Student& a, const Student& b){ return a.average() > b.average(); }), what does the lambda do?

  • Acts as a comparator that sorts highest-average students first (descending)
  • Sorts students alphabetically
  • Removes students with low averages
  • Computes the class average

Answer: Acts as a comparator that sorts highest-average students first (descending). The lambda is the comparator returning true when a should come before b; using > sorts highest averages first.

Why pass strings as const string& instead of string?

  • It allows the function to modify the caller's string
  • string& is required by the compiler
  • const string& makes the string mutable
  • It avoids copying the whole string on every call while keeping it read-only

Answer: It avoids copying the whole string on every call while keeping it read-only. Passing by value copies the string; const string& passes a read-only reference with no copy.

What is the bug in writing if (t.assignee = owner) to compare strings?

  • It is correct C++
  • = assigns instead of comparing, so the condition is always true; use ==
  • It throws a runtime exception
  • Strings cannot be compared at all

Answer: = assigns instead of comparing, so the condition is always true; use ==. A single = assigns and evaluates as the assigned value (always true here); use == to compare for equality.

In the Text Analyzer, what does freq[clean]++ do for an unordered_map?

  • Auto-creates the key (initialised to 0) if absent, then increments its count
  • Throws if the key is missing
  • Removes the key
  • Returns the key's position

Answer: Auto-creates the key (initialised to 0) if absent, then increments its count. Indexing a map with a new key default-constructs the value (0 for int), so ++ counts occurrences cleanly.

Why does for (const auto& t : tasks) beat for (auto t : tasks) in a loop over a vector<Task>?

  • It runs in parallel
  • It allows modifying each Task
  • It loops by reference and avoids copying every Task
  • There is no difference

Answer: It loops by reference and avoids copying every Task. for (auto t : tasks) copies every element; const auto& loops by reference with no needless copies.

In double average() const, what does the trailing const mean?

  • The function returns a constant
  • The method promises not to modify the object it is called on
  • The function can only be called once
  • The parameters are constant

Answer: The method promises not to modify the object it is called on. A const member function does not change the object's state, so it can be called on const objects.

Why does the Student::average() keep its running sum as a double rather than an int?

  • double is faster than int
  • int cannot hold large grade sums
  • It is required by std::vector
  • Integer division would truncate the fractional part of the average

Answer: Integer division would truncate the fractional part of the average. If sum were an int, sum / grades.size() would truncate; a double sum keeps the average's fraction.

Why does the lesson recommend compiling with -Wall -Wextra?

  • They make the program run faster
  • The warnings catch bugs for free, like a missing switch case or assignment-in-condition
  • They are required for C++17
  • They strip debug symbols

Answer: The warnings catch bugs for free, like a missing switch case or assignment-in-condition. Warning flags surface real bugs at compile time — missing enum cases, accidental assignments, and more.

What makes a small capstone project appealing to recruiters?

  • Making it as large and unfinished as possible
  • Avoiding version control so the history stays clean
  • A focused, correct project on GitHub with a clear README, small commits, warning-clean build, and one feature of your own
  • Copying a tutorial exactly with no changes

Answer: A focused, correct project on GitHub with a clear README, small commits, warning-clean build, and one feature of your own. A polished small project with a clear README, incremental commits, and an original feature beats a sprawling unfinished one.

Continue this course

Frequently asked questions

How big should my capstone project be?

Aim for one focused thing done well, not five things half-built. A single class with three or four methods, a clear main() that exercises them, and one extension you added yourself is plenty for a portfolio piece. It is far better to ship a small, correct Grade Manager than to abandon a half-finished game engine.

Why use struct in some places and class in others?

They are almost identical in C++ — the only real difference is that struct members are public by default and class members are private by default. Convention is to use struct for plain data bundles with no hidden state (like Student or Task here) and class when you want to protect internal data behind methods (like GradeManager or Account).

What does the [](const Student& a, const Student& b){ ... } in sort mean?

That is a lambda — an unnamed function written inline. sort uses it as the comparator: it returns true when a should come before b. Returning a.average() > b.average() sorts highest-average students first (descending). Swap the > for a < to sort ascending.

Why pass strings as const string& instead of string?

Passing by value (string) copies the whole string on every call, which is wasteful. const string& passes a read-only reference, so no copy is made and the function still cannot modify the caller's string. This is the standard way to take strings (and other large objects) as parameters in modern C++.

How do I make a project recruiters actually look at?

Put it on GitHub with a clear README that says what it does, how to build it (g++ -std=c++17 -Wall ...), and a sample run. Commit in small steps so the history shows your process, compile with -Wall -Wextra so it is warning-clean, and add one feature that is genuinely yours beyond the tutorial. A polished small project beats a sprawling unfinished one every time.