<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: LoanPayoff</title>
    <description>The latest articles on DEV Community by LoanPayoff (@loanpayofforg).</description>
    <link>https://dev.to/loanpayofforg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4170162%2F53bb36b9-93db-48ec-b945-3662e16a1ee2.png</url>
      <title>DEV Community: LoanPayoff</title>
      <link>https://dev.to/loanpayofforg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/loanpayofforg"/>
    <language>en</language>
    <item>
      <title>Amortization Edge Cases I Hit Building Payoff Calculators: Extra Payments, Lump Sums, and Biweekly</title>
      <dc:creator>LoanPayoff</dc:creator>
      <pubDate>Sat, 17 Oct 2026 11:37:12 +0000</pubDate>
      <link>https://dev.to/loanpayofforg/amortization-edge-cases-i-hit-building-payoff-calculators-extra-payments-lump-sums-and-biweekly-10j7</link>
      <guid>https://dev.to/loanpayofforg/amortization-edge-cases-i-hit-building-payoff-calculators-extra-payments-lump-sums-and-biweekly-10j7</guid>
      <description>&lt;p&gt;My last post here covered the architecture of &lt;a href="https://loanpayoff.org/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=auto-loan-202610" rel="noopener noreferrer"&gt;LoanPayoff.org&lt;/a&gt; — one math engine, no database, static hosting. This one is about the part that took three times longer than I expected: &lt;strong&gt;modeling extra payments correctly&lt;/strong&gt;. The amortization formula is textbook material; what happens when a borrower pays $100 extra every month, or drops $3,000 on the loan in month 6, is where the real work lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The base case is 20 lines
&lt;/h2&gt;

&lt;p&gt;A level-payment loan is just an annuity. Payment = P·r / (1 − (1+r)^−n), then walk the schedule month by month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apr&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;principal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;interest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;principal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;principal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every calculator on the site calls this one loop. The interesting parts are all in what feeds it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra monthly payments: the final payment is partial
&lt;/h2&gt;

&lt;p&gt;Add $100 extra and the naive assumption is "same schedule, ends sooner." Almost. The loan ends &lt;strong&gt;mid-month&lt;/strong&gt; — the last payment is smaller than all the others. If you compute interest as &lt;code&gt;months × payment − principal&lt;/code&gt;, you overstate it.&lt;/p&gt;

&lt;p&gt;The fix is to let the loop run until &lt;code&gt;balance &amp;lt;= 0&lt;/code&gt; and count only what was actually paid. On a $28,000 auto loan at 7.4% over 60 months, the required payment is $559.73 and total interest is $5,584. Add $100 extra: the schedule closes in 50 payments, the last one short, and interest lands at $4,563. That's a real $1,021 saved — but only if the final partial payment is counted honestly.&lt;/p&gt;

&lt;p&gt;That whole scenario is the worked example on the &lt;a href="https://loanpayoff.org/auto-loan-payoff-calculator/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=auto-loan-202610" rel="noopener noreferrer"&gt;auto loan payoff calculator&lt;/a&gt;, which re-runs this exact engine in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lump sums: anchor to a month, not a date
&lt;/h2&gt;

&lt;p&gt;"Pay $3,000 at month 6" is unambiguous. "Pay $3,000 on March 15" is not — you need the disbursement date, the first-payment date, and a day-count convention to convert. I deliberately support only &lt;strong&gt;month-numbered lump sums&lt;/strong&gt;. It sidesteps 30/360 vs actual/365 debates entirely, and for a payoff decision the difference is a rounding error nobody should act on anyway.&lt;/p&gt;

&lt;p&gt;One subtlety: a lump sum applied in month &lt;em&gt;k&lt;/em&gt; only changes interest from month &lt;em&gt;k&lt;/em&gt; onward, but it changes the term nonlinearly — the remaining balance re-amortizes against the same payment. The naive approach of "subtract and keep going" is correct here precisely because the loop recomputes interest off the live balance every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Biweekly is not "half a monthly payment every 14 days"
&lt;/h2&gt;

&lt;p&gt;True biweekly plans pay half the monthly payment every two weeks — 26 half-payments a year, which is 13 full payments, roughly +8% extra principal annually. Some lenders instead draft twice a month (24 half-payments), which saves almost nothing.&lt;/p&gt;

&lt;p&gt;So the calculator has a toggle, not an assumption: &lt;strong&gt;26 half-payments vs 24 half-payments vs plain monthly&lt;/strong&gt;. On the $28,000 example, the 26-cycle version cuts the term by about 3 months on its own; the 24-cycle version saves a fraction of that. Borrowers who think they're on biweekly but are actually on semimonthly are usually surprised by the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong first
&lt;/h2&gt;

&lt;p&gt;Two bugs worth confessing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Negative amortization guard.&lt;/strong&gt; If &lt;code&gt;payment &amp;lt;= interest&lt;/code&gt;, principal never falls and the loop runs forever. The cap &lt;code&gt;m &amp;lt;= months * 2&lt;/code&gt; plus an explicit error state fixed that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rounding drift.&lt;/strong&gt; Displaying rounded cents while computing on floats makes the final balance end at $0.0000001 instead of $0. The comparison view (with vs. without extra payments) made the drift visible as a one-cent discrepancy, which users absolutely will report. Fixed by tracking everything in exact cents internally.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it without trusting it
&lt;/h2&gt;

&lt;p&gt;Everything runs client-side, so you can open the &lt;a href="https://loanpayoff.org/auto-loan-payoff-calculator/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=auto-loan-202610" rel="noopener noreferrer"&gt;auto loan payoff calculator&lt;/a&gt;, plug in your own numbers, and check the totals against your lender's statement. If you run a personal-finance blog, the calculators are also &lt;a href="https://loanpayoff.org/embed/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=auto-loan-202610" rel="noopener noreferrer"&gt;embeddable&lt;/a&gt; — one iframe, no backend to trust.&lt;/p&gt;

&lt;p&gt;Edge cases I still want to handle: interest capitalization events (common on student loans after deferment) and odd first periods where the days before the first payment change accrual. If you've built loan math before, I'd like to hear how you handled those.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Student Loan Payoff Calculator Without React: One Math Engine, Zero Database</title>
      <dc:creator>LoanPayoff</dc:creator>
      <pubDate>Thu, 08 Oct 2026 04:31:20 +0000</pubDate>
      <link>https://dev.to/loanpayofforg/building-a-student-loan-payoff-calculator-without-react-one-math-engine-zero-database-4eop</link>
      <guid>https://dev.to/loanpayofforg/building-a-student-loan-payoff-calculator-without-react-one-math-engine-zero-database-4eop</guid>
      <description>&lt;h2&gt;
  
  
  The constraint that shaped everything
&lt;/h2&gt;

&lt;p&gt;When I started building a student loan payoff calculator, I gave myself one rule: &lt;strong&gt;if a feature does not need a server, it does not get one.&lt;/strong&gt; No user accounts, no API layer, no database. Loan inputs stay in the URL, results are computed in the browser, and the whole site is statically generated.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://loanpayoff.org/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=launch-202610" rel="noopener noreferrer"&gt;LoanPayoff&lt;/a&gt; — a free suite of payoff calculators: avalanche vs snowball comparison, a multi-loan planner, and extra-payment modeling. No signup wall, no "create an account to see your numbers."&lt;/p&gt;

&lt;h2&gt;
  
  
  One math engine, many calculators
&lt;/h2&gt;

&lt;p&gt;Every calculator calls the same small amortization module: given balances, rates and minimums, it produces the month-by-month schedule, total interest, and the debt-free date. The avalanche-vs-snowball view is just two runs of the same engine with different ordering rules — nothing duplicated.&lt;/p&gt;

&lt;p&gt;The part I'm most happy with: &lt;strong&gt;the same engine runs at build time.&lt;/strong&gt; The &lt;a href="https://loanpayoff.org/methodology?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=launch-202610" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; renders its worked example — a full amortization table and an SVG balance chart — from the identical functions the calculators use at runtime. The documentation literally cannot drift from the product, because it is the product's math, executed during &lt;code&gt;astro build&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  URL-state instead of a backend
&lt;/h2&gt;

&lt;p&gt;Calculator state serializes into query params. That gives three things for free:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shareable results — paste a link, the other person sees the same scenario&lt;/li&gt;
&lt;li&gt;Zero storage compliance surface — nothing about a borrower's finances ever leaves their device&lt;/li&gt;
&lt;li&gt;Free hosting — a static bundle on Cloudflare Pages costs nothing at this scale&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Made to be borrowed
&lt;/h2&gt;

&lt;p&gt;Student loan blogs explain payoff rules in prose; readers still can't map the rules to their own numbers. So the multi-loan comparison tool ships as an &lt;a href="https://loanpayoff.org/multi-loan?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=launch-202610" rel="noopener noreferrer"&gt;embeddable calculator&lt;/a&gt; — a one-line iframe any personal-finance writer can drop into a post. Sending free tools instead of asking for favors has been a surprisingly good outreach angle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I started today I'd still skip React for the math — plain functions are trivially testable and reused everywhere. The one thing I'd add sooner is the build-time report: rendering documentation from the runtime engine felt like a nicety, but it's what makes the site defensible as a YMYL source.&lt;/p&gt;

&lt;p&gt;The calculators live at &lt;a href="https://loanpayoff.org/?utm_source=devto&amp;amp;utm_medium=backlink&amp;amp;utm_campaign=launch-202610" rel="noopener noreferrer"&gt;LoanPayoff.org&lt;/a&gt;. Feedback welcome, especially on edge cases — unusual grace periods and odd day-count conventions are where amortization code goes to die.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
