What You'll Build
A loan EMI calculator that takes a principal amount, annual interest rate, and loan tenure, then computes the monthly EMI (Equated Monthly Installment), total interest paid, and a complete amortization schedule showing how each payment splits between principal and interest.
- Three inputs: loan amount, annual interest rate, tenure in years
- Instant EMI calculation using the standard formula
- Total interest and total payment displayed
- Visual breakdown bar (principal vs. interest)
- Full amortization schedule table (all months)
- Live updates as you type
🏦 Loan EMI Calculator
Calculate monthly payments and total interest
What You'll Learn
- Financial formula: the EMI equation used by every bank
- Amortization schedules: how loans are actually paid off month by month
- Dynamic table generation: building hundreds of table rows from a loop
- Currency formatting: displaying numbers like
$1,234usingtoLocaleString() - Data visualization: showing a breakdown ratio with a simple CSS bar
The formula: EMI = [P × r × (1+r)^n] ÷ [(1+r)^n − 1]
P = principal, r = monthly interest rate (annual ÷ 12 ÷ 100), n = total months
Build the HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Loan EMI Calculator</title>
</head>
<body>
<div class="card">
<h1>Loan EMI Calculator</h1>
<p class="sub">Calculate monthly payments and total interest</p>
<div class="field">
<label>Loan Amount ($)</label>
<input type="number" id="amount" value="200000" step="1000">
</div>
<div class="field">
<label>Annual Interest Rate (%)</label>
<input type="number" id="rate" value="6.5" step="0.1">
</div>
<div class="field">
<label>Loan Tenure (years)</label>
<input type="number" id="years" value="20" step="1">
</div>
<div class="result-box">
<div class="label">Monthly EMI</div>
<div class="num" id="emi">$0</div>
</div>
<div class="stats">
<div class="stat">
<div class="lbl">Principal</div>
<div class="val" id="principal">$0</div>
</div>
<div class="stat interest">
<div class="lbl">Total Interest</div>
<div class="val" id="interest">$0</div>
</div>
<div class="stat total">
<div class="lbl">Total Payment</div>
<div class="val" id="total">$0</div>
</div>
</div>
<div class="bar">
<div class="principal" id="barPrincipal"></div>
<div class="interest" id="barInterest"></div>
</div>
<div class="bar-labels">
<span><span class="dot blue"></span>Principal</span>
<span><span class="dot red"></span>Interest</span>
</div>
<div class="schedule" id="schedule"></div>
</div>
<script src="script.js"></script>
</body>
</html>
Style It with CSS
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f1f5f9;
padding: 20px;
color: #0f172a;
}
.card {
max-width: 520px;
margin: 0 auto;
background: #fff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 10px 40px rgba(15,23,42,0.08);
}
h1 { font-size: 1.3rem; margin-bottom: 4px; }
.sub { color: #64748b; font-size: 0.85rem; margin-bottom: 16px; }
.field { margin-bottom: 12px; }
label {
display: block;
font-size: 0.78rem;
font-weight: 600;
color: #334155;
margin-bottom: 4px;
}
input[type="number"] {
width: 100%;
padding: 11px 13px;
border: 2px solid #e2e8f0;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
font-family: inherit;
outline: none;
transition: 0.2s;
}
input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
}
.result-box {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
border-radius: 12px;
padding: 20px;
text-align: center;
color: #fff;
margin: 16px 0;
}
.result-box .label {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.9;
font-weight: 700;
}
.result-box .num {
font-size: 2rem;
font-weight: 900;
font-family: "Courier New", monospace;
line-height: 1;
margin-top: 6px;
}
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 16px;
}
.stat {
background: #f8fafc;
border-radius: 10px;
padding: 12px;
text-align: center;
}
.stat .lbl {
font-size: 0.68rem;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
margin-bottom: 4px;
}
.stat .val {
font-size: 1.1rem;
font-weight: 800;
color: #1e293b;
font-family: "Courier New", monospace;
}
.stat.interest .val { color: #ef4444; }
.stat.total .val { color: #1d4ed8; }
.stat.total { grid-column: 1 / -1; }
.bar {
height: 12px;
background: #e2e8f0;
border-radius: 999px;
overflow: hidden;
display: flex;
margin-bottom: 16px;
}
.bar .principal { background: #3b82f6; }
.bar .interest { background: #ef4444; }
.bar-labels {
display: flex;
justify-content: space-between;
font-size: 0.72rem;
color: #64748b;
margin-bottom: 16px;
}
.bar-labels span { display: flex; align-items: center; gap: 6px; }
.bar-labels .dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.bar-labels .dot.blue { background: #3b82f6; }
.bar-labels .dot.red { background: #ef4444; }
.schedule {
max-height: 320px;
overflow-y: auto;
border: 1px solid #e2e8f0;
border-radius: 10px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
th {
background: #f1f5f9;
color: #334155;
padding: 8px;
text-align: right;
font-weight: 700;
position: sticky;
top: 0;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
th:first-child { text-align: left; }
td {
padding: 6px 8px;
border-bottom: 1px solid #f1f5f9;
text-align: right;
font-family: "Courier New", monospace;
color: #475569;
}
td:first-child {
text-align: left;
color: #0f172a;
font-weight: 600;
}
tr:last-child td { border-bottom: none; }
Write the JavaScript — EMI Formula
// ============================================================
// GRAB DOM ELEMENTS
// ============================================================
var amountEl = document.getElementById("amount");
var rateEl = document.getElementById("rate");
var yearsEl = document.getElementById("years");
// ============================================================
// CALCULATE EMI
// ============================================================
// Formula: EMI = [P x r x (1+r)^n] / [(1+r)^n - 1]
// P = principal (loan amount)
// r = monthly interest rate = (annual / 12) / 100
// n = total months = years x 12
function calc() {
var P = parseFloat(amountEl.value);
var annualRate = parseFloat(rateEl.value);
var years = parseFloat(yearsEl.value);
// Guard clause: ignore invalid input
if (!P || !annualRate || !years || P <= 0 || annualRate <= 0 || years <= 0) {
return;
}
var r = annualRate / 12 / 100;
var n = years * 12;
// The magic formula — compound interest solved for a fixed payment
var emi = (P * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
var totalPayment = emi * n;
var totalInterest = totalPayment - P;
// Write results
document.getElementById("emi").textContent = fmt(emi);
document.getElementById("principal").textContent = fmt(P);
document.getElementById("interest").textContent = fmt(totalInterest);
document.getElementById("total").textContent = fmt(totalPayment);
// Update the principal/interest bar
var pct = (P / totalPayment) * 100;
document.getElementById("barPrincipal").style.width = pct + "%";
document.getElementById("barInterest").style.width = (100 - pct) + "%";
// Render the amortization schedule
renderSchedule(P, r, emi, n);
}
Why this formula works: It comes from solving the compound interest equation for a fixed monthly payment. If you lent $200,000 at 6.5%/year for 20 years, the bank calculates a payment where the present value of all future payments equals $200,000. That payment is $1,491/month.
Write the JavaScript — Amortization & Events
// ============================================================
// AMORTIZATION SCHEDULE
// ============================================================
// Each month, part of the payment goes to interest and part
// goes to principal. Early on, most is interest. Later, most
// is principal. This function simulates every month.
function renderSchedule(P, r, emi, n) {
var schedule = document.getElementById("schedule");
var rows = [];
rows.push('<table><thead><tr>' +
'<th>Month</th><th>Principal</th>' +
'<th>Interest</th><th>Balance</th>' +
'</tr></thead><tbody>');
var balance = P;
for (var i = 1; i <= n; i++) {
var interestPart = balance * r; // interest this month
var principalPart = emi - interestPart; // principal this month
balance = balance - principalPart; // remaining balance
if (balance < 0) balance = 0; // clean final row
rows.push('<tr>' +
'<td>' + i + '</td>' +
'<td>' + fmt(principalPart) + '</td>' +
'<td>' + fmt(interestPart) + '</td>' +
'<td>' + fmt(balance) + '</td>' +
'</tr>');
}
rows.push('</tbody></table>');
schedule.innerHTML = rows.join("");
}
// ============================================================
// NUMBER FORMATTING — money format
// ============================================================
function fmt(n) {
if (!isFinite(n)) return "$0";
return "$" + Math.round(n).toLocaleString();
}
// ============================================================
// LIVE UPDATES
// ============================================================
document.querySelectorAll("input").forEach(function(el) {
el.addEventListener("input", calc);
el.addEventListener("change", calc);
});
// Boot
calc();
Save and open index.html. Change the loan amount, rate, or tenure — the EMI, totals, breakdown bar, and full schedule all update live.
Understand the Big Ideas
1. Financial formulas in code
The EMI formula is a classic compound interest calculation. The Math.pow(1 + r, n) term raises the monthly rate to the power of the number of months. This grows exponentially, which is why 20 years of payments is much more than 20 × 12 × the principal.
2. Amortization — the compounding in reverse
In month 1, you pay interest on the full $200,000 balance. In month 240, you pay interest on just a few hundred dollars. The schedule makes this visible — early payments are mostly interest, late payments are mostly principal.
This is why overpaying early in a loan saves far more than overpaying late. Every extra dollar you put toward principal in month 1 saves you interest for the next 239 months.
3. Building tables from loops
We never write a single <tr> by hand. The loop builds an array of strings, then join("") merges them into one HTML string that gets injected with innerHTML. This is the standard pattern for rendering lists of any size.
Performance note: For 240 rows this is instant. For 100,000 rows, you'd want a proper virtual-DOM library — but that's a problem for another day. For real loan schedules, this approach is perfect.
4. Money formatting
Math.round(n).toLocaleString() rounds to whole dollars and adds thousands separators using the user's locale. In the US, this gives 1,491. In Germany, 1.491. Same code, right output everywhere.
Practice Challenges
🟢 Beginner
- Add a currency selector — USD, EUR, GBP, PKR, INR
- Add a print schedule button
- Show total interest as a % of principal
🟡 Intermediate
- Add extra payment input — show how much you save by paying extra
- Add a yearly summary view — collapse months into years
- Add a CSV download for the full schedule
🔴 Advanced
- Add a chart (using Chart.js) showing principal vs. interest over time
- Add loan comparison — compare two loans side by side
- Add prepayment scenarios — what if you pay $X extra per month?
Compare your solution: We built the production version with these features — see the live Loan EMI Calculator.
Full Source Code
Common Mistakes & Fixes
❌ "EMI is way too high"
You're probably using the annual rate as the monthly rate. Divide by 12 first, then by 100. So 6.5% annual = 6.5 / 12 / 100 = 0.00542 monthly.
❌ "Schedule shows negative balances"
Floating-point rounding can push the last balance slightly below zero. The if (balance < 0) balance = 0 line cleans this up.
❌ "Infinity or NaN in the result"
Check for zero in the rate or years. The guard clause at the top of calc() handles this — make sure it's not been removed.
❌ "Page is slow with 30-year loans"
30 years = 360 rows. Should be instant. If it's slow, you're probably re-rendering the whole table on every keystroke instead of on change. Consider debouncing the input.