Examples
Ten small problems where a person already knows the answer and the job is to write it down. No training data, no weights — just facts in, rules in the middle, a decision out.
Each example follows the same shape: define what you are deciding, sketch how facts flow through rules, then encode it in TypeScript. That is the innermost ring of Artificial Intelligence (AI) — human judgment turned into code you can read and test.
Loan approval rules
Define the problem
A bank receives a loan application with income, credit score, and debt-to-income ratio. An underwriter already knows the policy: deny obvious risks, fast-track strong files, send the rest to a human. Your job is to encode that policy so every application gets the same verdict.
Visualize
Solve with code
type Application = { income: number; creditScore: number; debtRatio: number };
function decideLoan(app: Application): 'approve' | 'review' | 'deny' {
if (app.creditScore < 580) return 'deny';
if (app.income < 40_000) return 'deny';
if (app.debtRatio > 0.45) return 'review';
if (app.creditScore >= 720 && app.debtRatio <= 0.36) return 'approve';
return 'review';
}The order of the if statements is the policy. Swap two lines and you change who gets money — which is why rule-based systems are easy to audit and painful to maintain when the market moves.
Spam filter keyword rules
Define the problem
An inbox needs to flag junk before a human opens it. You cannot describe “spam” in one sentence, but you can list phrases that almost never appear in mail people want. Count how many fire on a message; two or more and it goes to the junk folder.
Visualize
Solve with code
const SPAM_SIGNALS = ['winner', 'free money', 'click here', 'unsubscribe'];
function isSpam(subject: string, body: string): boolean {
const text = `${subject} ${body}`.toLowerCase();
const hits = SPAM_SIGNALS.filter((word) => text.includes(word)).length;
return hits >= 2;
}This is how filters worked for years. Legitimate mail that happens to contain two trigger phrases becomes a false positive — the cost of a rule you can explain in a stand-up.
Support ticket routing
Define the problem
Support queues back up when every ticket lands in one inbox. Billing questions need invoices; free-plan users asking about upgrades should see sales first. Read the subject and body, check the plan tier, and assign a team before a human picks it up.
Visualize
Solve with code
type Ticket = { subject: string; body: string; plan: 'free' | 'pro' };
function routeTicket(ticket: Ticket): 'billing' | 'sales' | 'support' {
const text = `${ticket.subject} ${ticket.body}`.toLowerCase();
if (/invoice|refund|charge/.test(text)) return 'billing';
if (ticket.plan === 'free' && /upgrade|pricing/.test(text)) return 'sales';
return 'support';
}Symptom triage (toy medical rules)
Define the problem
A nurse hotline must sort callers before a doctor is free. Chest pain or trouble breathing means call an ambulance now; fever alone can wait for same-day care; everything else is routine. This is a toy ruleset — real triage is far richer — but the shape is the same: symptoms in, urgency out.
Visualize
Solve with code
type Symptoms = { fever: boolean; chestPain: boolean; breathingHard: boolean };
function triage(symptoms: Symptoms): 'emergency' | 'urgent' | 'routine' {
if (symptoms.chestPain || symptoms.breathingHard) return 'emergency';
if (symptoms.fever) return 'urgent';
return 'routine';
}Expert systems in medicine worked like this for decades: a knowledge engineer interviews specialists and types the ladder. The weakness is coverage — a symptom you forgot to ask about never reaches a rule.
Shopping discount and promo engine
Define the problem
Checkout applies stacked promotions: members get ten percent off, a coupon code takes another ten percent, and three or more items earn a flat fifteen-dollar rebate. Rules can combine; order matters when you multiply then subtract. Encode the marketing sheet so every cart gets the same arithmetic.
Visualize
Solve with code
type Cart = { subtotal: number; itemCount: number; member: boolean; code?: string };
function finalPrice(cart: Cart): number {
let total = cart.subtotal;
if (cart.member) total *= 0.9;
if (cart.code === 'SAVE10') total *= 0.9;
if (cart.itemCount >= 3) total -= 15;
return Math.max(0, Math.round(total * 100) / 100);
}Promo engines are rule-based AI in a checkout button. Product teams change the sheet weekly; engineering redeploys when the rules change — there is nothing to retrain.
HVAC thermostat decision
Define the problem
A thermostat reads the current temperature and whether anyone is home. When occupied, hold seventy-two degrees; when empty, let it drift to sixty-five to save money. If the room is more than two degrees below target, call for heat; more than two above, call for cool; otherwise leave the system off.
Visualize
Solve with code
type Reading = { tempF: number; humidity: number; occupied: boolean };
function hvacAction(reading: Reading): 'heat' | 'cool' | 'off' {
const target = reading.occupied ? 72 : 65;
if (reading.tempF < target - 2) return 'heat';
if (reading.tempF > target + 2) return 'cool';
return 'off';
}The AI overview uses this exact device as the smallest example of “a job a person used to do.” Nothing learned — someone chose the numbers and typed the band.
FAQ chatbot keyword match
Define the problem
A help widget should answer the same twenty questions without a human. Each FAQ entry is a pattern and a canned reply. Scan the user’s message for the first matching pattern; if none hit, fall back to a generic handoff. No language model — just ordered rules.
Visualize
Normalize the text, walk the FAQ list in order (specific patterns before broad ones), then return the canned reply — or escalate when nothing matches.
Solve with code
const FAQ: Array<{ match: RegExp; answer: string }> = [
{ match: /password|reset/i, answer: 'Settings → Security → Reset password.' },
{ match: /billing|invoice/i, answer: 'Invoices live under Account → Billing.' },
{ match: /cancel/i, answer: 'Cancel from Account → Plan before renewal day.' },
];
function faqReply(message: string): string {
const hit = FAQ.find((entry) => entry.match.test(message));
return hit?.answer ?? 'Try /help or contact support.';
}Fraud flagging rules
Define the problem
A payment processor must block obvious fraud before money moves. Large amount, foreign country mismatch, and many attempts in one minute each add points to a risk score. High score blocks; medium score sends to review; low score clears. The points table is the entire “model”.
Visualize
Solve with code
type Txn = { amount: number; country: string; cardCountry: string; velocity: number };
function fraudScore(txn: Txn): 'clear' | 'review' | 'block' {
let score = 0;
if (txn.amount > 2_000) score += 2;
if (txn.country !== txn.cardCountry) score += 2;
if (txn.velocity > 5) score += 3;
if (score >= 5) return 'block';
if (score >= 2) return 'review';
return 'clear';
}Banks ran on scorecards like this long before gradient boosting. The trade-off is the same as every rule system: criminals learn the thresholds, and tuning means editing weights by hand.
Tic-tac-toe win check
Define the problem
A game engine needs to know when someone has won. Eight lines can complete on a three-by-three board — three rows, three columns, two diagonals. After each move, check whether any line is three of the same mark. This is the kind of micro-rule chess engines were built from before anyone said “machine learning”.
Visualize
Solve with code
type Board = ('X' | 'O' | null)[];
const WINS = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
];
function winner(board: Board): 'X' | 'O' | null {
for (const [a, b, c] of WINS) {
const p = board[a];
if (p && p === board[b] && p === board[c]) return p;
}
return null;
}Plant-care advisor (expert system)
Define the problem
A gardening app asks three questions — species, dry soil or not, hours of light — and returns care advice. A succulent, a fern, and a tomato each follow different rules written by someone who grows them. Classic expert-system shape: narrow domain, explicit knowledge, no training step.
Visualize
Solve with code
type Plant = { species: 'succulent' | 'fern' | 'tomato'; soilDry: boolean; hoursLight: number };
function advise(plant: Plant): string {
if (plant.species === 'succulent') {
return plant.soilDry ? 'Water lightly — soak dries fast.' : 'Wait — succulents hate wet roots.';
}
if (plant.species === 'fern') {
return plant.hoursLight < 4 ? 'Move to brighter indirect light.' : 'Mist daily; keep soil damp.';
}
return plant.soilDry ? 'Deep water now — tomatoes drink a lot.' : 'Hold — check again tonight.';
}MYCIN and early agricultural advisors worked this way: interview the expert, encode the branches, ship. When a new species appears, you add a branch — you do not retrain a network.