Loading your tools...
Loading your tools...
Learn how to build a loan amortization schedule in JavaScript. Understand the math behind principal, interest, and the power of the 'for' loop.
A mortgage calculator tells you the monthly payment. An Amortization Calculator tells you the story of the loan.
It shows exactly how much of your hard-earned money goes to the bank (interest) vs. your actual debt (principal) every single month for 30 years.
In this guide, we'll go beyond simple formulas and build the Schedule Generator—a loop that calculates the loan's life month by month.
Unlike a simple mortgage calculator, we need to track state over time.
The most important variable is balance. It starts equal to the Loan Amount and must reach $0 by the end.
let balance = 200000; // Loan Amount
const monthlyRate = 0.005; // 6% annual / 12 months
const monthlyPayment = 1199.10; // Calculated via formula
We need to run a calculation for every month of the loan (e.g., 360 months for a 30-year mortgage).
We use a for loop.
for (let month = 1; month <= 360; month++) {
// Magic happens here
}
Every month, the bank charges you interest on the current balance. This is why payments at the start are mostly interest!
const interestPayment = balance * monthlyRate;
Whatever is left of your payment after interest goes to paying down the debt.
const principalPayment = monthlyPayment - interestPayment;
Now we lower the debt. This "new" balance is used for next month's interest calculation. This is where the "Snowball" effect happens: simpler debt = less interest = more principal paid next time.
balance = balance - principalPayment;
What if you pay an extra $100? We simply subtract it from the balance directly. This skips interest entirely!
const totalPrincipal = principalPayment + extraPayment;
balance = balance - totalPrincipal;
We don't just want to console.log it. We want an array of objects to render a table.
const schedule = [];
// ... inside loop ...
schedule.push({
month: month,
interest: interestPayment,
principal: principalPayment,
remaining: balance
});
We loop through our data array to create rows.
schedule.map(row => (
<tr>
<td>Month {row.month}</td>
<td>${row.interest.toFixed(2)}</td>
<td>${row.principal.toFixed(2)}</td>
<td>${row.remaining.toFixed(2)}</td>
</tr>
))
Computers are bad at decimals (0.1 + 0.2 = 0.300000004).
To prevent ugly numbers, always use .toFixed(2) for display, but keep raw numbers for math until the very end.
Here is a complete, working React component that generates a full Amortization Schedule.
import React, { useState } from 'react';
export default function AmortizationCalculator() {
const [loan, setLoan] = useState(200000);
const [rate, setRate] = useState(6);
const [years, setYears] = useState(30);
const [schedule, setSchedule] = useState([]);
const generateSchedule = () => {
const months = years * 12;
const monthlyRate = rate / 100 / 12;
// Standard Mortgage Formula for Payment
const payment = (loan * monthlyRate * Math.pow(1 + monthlyRate, months)) /
(Math.pow(1 + monthlyRate, months) - 1);
let balance = loan;
const newSchedule = [];
for (let i = 1; i <= months; i++) {
const interest = balance * monthlyRate;
const principal = payment - interest;
// Prevent negative balance at the very end due to rounding
if (balance < principal) {
balance = 0;
} else {
balance -= principal;
}
newSchedule.push({
month: i,
payment: payment,
interest: interest,
principal: principal,
balance: balance
});
if (balance <= 0) break;
}
setSchedule(newSchedule);
};
return (
<div className="p-6 bg-white rounded-xl shadow-lg max-w-2xl mx-auto">
<h2 className="text-2xl font-bold mb-4">Amortization Schedule</h2>
<div className="grid grid-cols-3 gap-4 mb-4">
<input type="number" value={loan} onChange={e => setLoan(Number(e.target.value))} placeholder="Loan Amount" className="border p-2 rounded" />
<input type="number" value={rate} onChange={e => setRate(Number(e.target.value))} placeholder="Rate %" className="border p-2 rounded" />
<input type="number" value={years} onChange={e => setYears(Number(e.target.value))} placeholder="Years" className="border p-2 rounded" />
</div>
<button onClick={generateSchedule} className="w-full bg-blue-600 text-white p-3 rounded font-bold hover:bg-blue-700 transition">
Calculate Schedule
</button>
{schedule.length > 0 && (
<div className="mt-6 overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="bg-gray-100 uppercase">
<tr>
<th className="px-4 py-2">Month</th>
<th className="px-4 py-2">Payment</th>
<th className="px-4 py-2">Principal</th>
<th className="px-4 py-2">Interest</th>
<th className="px-4 py-2">Balance</th>
</tr>
</thead>
<tbody>
{schedule.map((row) => (
<tr key={row.month} className="border-b">
<td className="px-4 py-2">{row.month}</td>
<td className="px-4 py-2 font-mono">${row.payment.toFixed(2)}</td>
<td className="px-4 py-2 font-mono text-green-600">${row.principal.toFixed(2)}</td>
<td className="px-4 py-2 font-mono text-red-600">${row.interest.toFixed(2)}</td>
<td className="px-4 py-2 font-mono text-gray-500">${row.balance.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
Developer Tools & Resource Experts
FastTools is dedicated to curating high-quality content and resources that empower developers. With nearly 5 years of hands-on development experience, our team rigorously evaluates every tool and API we recommend, ensuring you get only the most reliable and effective solutions for your projects.