C++ Bank Loan Approval System Project
Score loan applications against layered rules — and explain every decision.
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
- Rules-engine design (criteria as data)
- Weighted credit scoring
- Debt-to-income and affordability calculations
- Decision audit trails
- Separating policy from code so rules can change
Feature checklist
- Application intake: income, debts, history, amount
- Hard rules: instant decline conditions
- Scoring model: points across 5+ factors
- Three outcomes: approve / decline / refer to human
- Every decision lists the reasons
- Admin can adjust thresholds without recompiling
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
- Scoring before checking hard rules, approving an applicant the bank's non-negotiables should have stopped instantly.
- A score function returning only an int, making the required 'reasons' printout impossible without duplicating the logic.
- Burying thresholds as magic numbers inside scoring code, so a policy change needs a programmer and a recompile.
Compile and run
g++ -std=c++17 -o loans loans.cppSample 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.