Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Bank Loan Approval System Project

Advanced•1–2 weeks•rules engines, OOP, scoring

Score loan applications against layered rules — and explain every decision.

Share on X

What you will build

Applicants submit income, debts, credit history, and the system approves, declines, or refers each loan — with reasons. The challenge is designing a rules engine where criteria are data, not hard-coded ifs, and every decision can be explained to the applicant and an auditor.

What you will learn

Feature checklist

Starter code

#include <string>
#include <vector>

struct Application {
    double income, monthlyDebts, amount;
    int creditScore, yearsEmployed, missedPayments;
};

struct Rule {
    std::string name;
    bool (*fails)(const Application&);   // true = hard decline
    std::string reason;
};

std::vector<Rule> hardRules = {
    {"Min credit", [](const Application& a) { return a.creditScore < 500; },
     "Credit score below minimum"},
    {"Affordability", [](const Application& a) {
        return a.monthlyDebts > a.income * 0.5; },
     "Debt payments exceed 50% of income"},
    {"Recent defaults", [](const Application& a) {
        return a.missedPayments >= 3; }, "Three or more missed payments"},
};

int score(const Application& a) {        // soft scoring, 0-100
    int s = 0;
    s += a.creditScore >= 750 ? 40 : a.creditScore >= 650 ? 25 : 10;
    double dti = a.monthlyDebts / a.income;
    s += dti < 0.2 ? 25 : dti < 0.35 ? 15 : 5;
    s += a.yearsEmployed >= 3 ? 20 : a.yearsEmployed >= 1 ? 10 : 0;
    s += a.amount < a.income * 3 ? 15 : 5;
    return s;
}
// Verdict: any hard rule fails -> decline (with reasons);
// score >= 70 approve; 45-69 refer; below -> decline.

Full walkthrough

Step 1: Model the application as data

struct Application { name, monthlyIncome, monthlyDebts, yearsEmployed, creditHistoryScore, amountRequested, monthsRequested } — everything a decision may look at, in one place. Decisions must be reproducible: given the same Application, the system must always answer the same way, which is only testable if the inputs are one clean struct.

Step 2: Hard rules first: instant declines

struct Rule { std::string name; std::function<bool(const Application&)> fails; std::string reason; } — a list of decline conditions checked BEFORE scoring: requested amount > 60× monthly income, debts already above 60% of income, credit history in the worst band. A failed hard rule ends the evaluation: outcome DECLINE, reason attached. Order is policy: hard rules are the bank's non-negotiables, and they outrank any score.

Step 3: The scoring model: points across factors

score(app) awards points across 5+ factors, each in a readable band table: income-to-payment ratio (the monthly payment on the requested loan versus income), employment years, credit history, existing debt load, loan size relative to income. Sum the points. Each factor is a small independent function returning points + an explanation string, so the final decision can LIST its reasoning line by line — the checklist demands explained decisions, and a black-box score cannot explain itself.

int scoreIncome(const Application& a, std::string& why) {
    double payment = a.amountRequested / a.monthsRequested;
    double ratio = payment / a.monthlyIncome;
    if (ratio <= 0.20) { why = "payment is only 15% of income (+30)"; return 30; }
    // ... lower bands, fewer points ...
}

Step 4: Three outcomes from two thresholds

Score ≥ APPROVE_AT → APPROVE. Below DECLINE_AT → DECLINE. Between them → REFER (a human decides). The refer band is the honest engineering: borderline cases are genuinely ambiguous, and a system that pretends otherwise just hides its coin flips. Thresholds live in a config file or a clearly named constants block — the next step depends on it.

Step 5: Every decision carries its reasons

The outcome struct collects: hard-rule failures (if any), each factor's points with its why string, the total, and the outcome. Print the full reasoning with every decision. When an applicant asks 'why was I declined?', this printout is the answer — and when a banker asks 'why was THIS approved?', it is the audit trail.

Step 6: Admin-adjustable thresholds without recompiling

Load APPROVE_AT, DECLINE_AT, and the hard-rule limits from a small config file at startup (key=value lines). Changing policy becomes editing a text file; the admin workflow in the checklist exists to teach that policy and code change at different speeds and should live in different places.

Step 7: Test with engineered applicants

Craft applicants to hit each branch precisely: a clear approve (high income, tiny loan, clean history — verify every factor's points by hand); a hard-rule decline whose score WOULD have passed (proving rules run first); a deliberate borderline landing in REFER by a few points; then lower DECLINE_AT in the config, restart, and watch the same borderline applicant approve — thresholds are policy, and the test proves they are live.

Common mistakes

Compile and run

g++ -std=c++17 -o loans loans.cpp

Sample run

$ ./loans
Loaded policy: approve at 70+, refer 45-69, hard limits from policy.cfg
New application — name:
> Ada Lovelace
Monthly income / existing monthly debts:
> 80000 / 10000
Years employed / credit history (0-100):
> 6 / 82
Amount requested / repay over (months):
> 200000 / 12
Evaluating... hard rules: none failed
  payment 16667 is 21% of income (+25)
  employment 6 years (+20)
  credit history 82 (+25)
  existing debts 13% of income (+15)
  loan is 2.5x monthly income (+10)
Total score: 95
DECISION: APPROVE — reasons listed above.
New application — name:
> Bola Ahmed
Evaluating... hard rule FAILED: monthly debts are 65% of income (limit 60%)
DECISION: DECLINE — declined by hard rule before scoring.

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Advanced projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS