<?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: Farbod F.</title>
    <description>The latest articles on DEV Community by Farbod F. (@farbodf).</description>
    <link>https://dev.to/farbodf</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%2F4185281%2Fd81958b4-48d0-4895-9e6e-478398a7fba2.png</url>
      <title>DEV Community: Farbod F.</title>
      <link>https://dev.to/farbodf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/farbodf"/>
    <language>en</language>
    <item>
      <title>I ship 135 free tools that never upload your file — as a static site</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Fri, 30 Oct 2026 20:21:30 +0000</pubDate>
      <link>https://dev.to/farbodf/i-ship-135-free-tools-that-never-upload-your-file-as-a-static-site-552i</link>
      <guid>https://dev.to/farbodf/i-ship-135-free-tools-that-never-upload-your-file-as-a-static-site-552i</guid>
      <description>&lt;p&gt;I build ToolsByUs, a suite of 135 free browser tools for JSON, PDF, images, video, audio, and text. There is no account, and no server endpoint waiting to receive your file. That last part is the whole design decision — and it shapes everything else about how the site is built.&lt;/p&gt;

&lt;p&gt;The rule is narrow, on purpose: your file's contents never leave the browser. The tool runs in your tab, on your machine. That is different from the usual promise of "we delete your files after processing." There is nothing to delete, because the contents were never sent.&lt;/p&gt;

&lt;p&gt;A worked example, with a tool you can try in seconds: the JSON Formatter. Paste this on one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"zip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;90210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"pdf"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Format it with 2-space indent and key sorting, and you get readable, deterministic output — same input, same output, no round-trip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"pdf"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"zip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90210&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing about that example is special, and that is the point. The formatter, the JSON Schema Generator (it infers Draft 2020-12 schemas from a sample payload), the regex tester, and the JWT decoder all work the same way: paste, process locally, copy the result.&lt;/p&gt;

&lt;p&gt;The surprising part is not the formatter. It is the Background Remover. It runs a neural network locally in the browser, via ONNX inference, and returns a transparent PNG — again without uploading the image. A model running in a tab feels like it should need a server. It does not; the cost is paid once, on your machine, when the model loads.&lt;/p&gt;

&lt;p&gt;Why a static site? Because a static export cannot quietly gain an upload endpoint later without someone noticing the architecture changed. The site ships in six locales (English, Spanish, German, French, Portuguese, Japanese), with 12 guides and a public changelog, from the same static build. The count breaks down honestly: 113 standard tools plus 22 creator tools, for 135 in total.&lt;/p&gt;

&lt;p&gt;Honest caveats — because "private by architecture" still has edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-side does not mean "no network at all." Your browser still fetches the page itself, and the site notes analytics is configured separately. The guarantee is about file contents, not about every byte your browser exchanges.&lt;/li&gt;
&lt;li&gt;Heavy work is bounded by your machine. A very large PDF or image will be slower, and can hit browser memory limits, where a server would just throw more hardware at it.&lt;/li&gt;
&lt;li&gt;The background-removal model has to load first. The first run is slower than later runs.&lt;/li&gt;
&lt;li&gt;No account means no sync and no saved history. That is a trade-off, not just a feature.&lt;/li&gt;
&lt;li&gt;I am not claiming this is open source, and I am not promising "free forever." The site says all tools are free today; that is the claim I can stand behind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work with contracts, payslips, or medical records — the exact cases the Merge PDF page names — the question is not whether you trust a vendor's deletion policy. It is whether your file ever needed to leave your browser at all.&lt;/p&gt;

&lt;p&gt;You can try the full suite at ToolsByUs: &lt;a href="https://toolsbyus.com" rel="noopener noreferrer"&gt;https://toolsbyus.com&lt;/a&gt; — 135 free tools, no signup, file contents processed in your browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>javascript</category>
      <category>tools</category>
    </item>
    <item>
      <title>I priced my side project's LLM bill in 20 lines of JavaScript — here's the token math</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Fri, 30 Oct 2026 19:24:15 +0000</pubDate>
      <link>https://dev.to/farbodf/i-priced-my-side-projects-llm-bill-in-20-lines-of-javascript-heres-the-token-math-59on</link>
      <guid>https://dev.to/farbodf/i-priced-my-side-projects-llm-bill-in-20-lines-of-javascript-heres-the-token-math-59on</guid>
      <description>&lt;h1&gt;
  
  
  I priced my side project's LLM bill in 20 lines of JavaScript — here's the token math
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Adding an AI summary feature to a side project cost me $156 a month at a volume I would have guessed was "basically free." The formula behind that bill fits in one function. The part I got wrong was not the formula — it was which tokens I was counting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every LLM API bills the same way: dollars per million tokens, with input and output priced separately. Output costs several times more than input, because generating text is the expensive operation and reading your prompt is cheap by comparison. That much is on every pricing page. What the pricing page does not show you is how fast the "cheap" side adds up when a fixed block of text rides along on every single request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The billing function
&lt;/h2&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;monthlyCost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputPrice&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;inputMillions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&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;outputMillions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputMillions&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;inputPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;outputMillions&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;outputPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputMillions&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;inputPrice&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;outputMillions&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;outputPrice&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two counters, two prices. The engineering work is not this function — it is getting honest numbers for &lt;code&gt;inputTokens&lt;/code&gt; and &lt;code&gt;outputTokens&lt;/code&gt; before you ship, instead of discovering them on the invoice. A rough token estimate for English prose is characters divided by 4; code and JSON tokenize denser, so measure with your provider's tokenizer on real payloads rather than trusting the heuristic for production estimates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: 20,000 summaries a month
&lt;/h2&gt;

&lt;p&gt;Say a small app adds an AI summary button. Each summary sends a document plus instructions and gets a short summary back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20,000 requests a month&lt;/li&gt;
&lt;li&gt;2,500 input tokens per request → 50,000,000 input tokens&lt;/li&gt;
&lt;li&gt;350 output tokens per request → 7,000,000 output tokens&lt;/li&gt;
&lt;li&gt;Illustrative prices: $2.00 per million input tokens, $8.00 per million output tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Input cost: 50 × $2.00 = &lt;strong&gt;$100.00&lt;/strong&gt;&lt;br&gt;
Output cost: 7 × $8.00 = &lt;strong&gt;$56.00&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total: $156.00 a month — $0.0078 per summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Less than a cent per summary sounds trivial until you multiply it: at 200,000 summaries the same feature is $1,560 a month, and at that point the line item has a manager. The per-unit cost is the number to design around, not the first month's total.&lt;/p&gt;

&lt;h2&gt;
  
  
  The figure that surprised me: the system prompt is 20% of the bill
&lt;/h2&gt;

&lt;p&gt;Here is the breakdown I did not expect. Of those 2,500 input tokens per request, 800 are the system prompt — the fixed instructions, formatting rules, and few-shot examples prepended to every call. They never change, they are never the user's content, and they cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;800 tokens × 20,000 requests = &lt;strong&gt;16,000,000 tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;At $2.00 per million: &lt;strong&gt;$32.00 a month — 20.5% of the entire bill&lt;/strong&gt; — for text I wrote once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That fixed block is the cheapest money in the whole system to save. Most providers now offer prompt caching: repeated prefix tokens are billed at a fraction of the full input price, commonly around 10%. At 10%:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cached system prompt: 16 × $0.20 = &lt;strong&gt;$3.20&lt;/strong&gt; instead of $32.00&lt;/li&gt;
&lt;li&gt;Saving: &lt;strong&gt;$28.80 a month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;New monthly total: &lt;strong&gt;$127.20 — an 18.5% cut&lt;/strong&gt; from one configuration change, no model swap, no quality trade-off&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general rule: any token you send on every request deserves scrutiny proportional to your request count, not its size. A 2,000-token instruction block at 1,000 requests a month is 2 million tokens of pure overhead. The prompt you treat as free because you wrote it once is the most expensive text in your system per word.&lt;/p&gt;

&lt;p&gt;Two other multipliers worth modeling before you ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regenerations.&lt;/strong&gt; Every "try again" is a full new request at full price. If 15% of summaries get regenerated once, that adds 0.15 × $156.00 = &lt;strong&gt;$23.40 a month&lt;/strong&gt; — a 15% surcharge for a button users barely notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History growth.&lt;/strong&gt; Chat-style features resend prior turns with each new message, so input tokens per request grow with conversation length. A feature that looks cheap in a one-turn demo can cost several times more in a real twenty-turn session. If your feature keeps history, model the long session, not the demo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prices move.&lt;/strong&gt; Model prices drop, tiers change, and cached-input pricing differs by provider. Treat every dollar figure here as an illustrative snapshot, not a quote — plug in your provider's current prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token counts are estimates until measured.&lt;/strong&gt; The characters-divided-by-4 heuristic is fine for a first pass; real payloads through the real tokenizer are the only honest input to a budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The calculator estimates; the dashboard decides.&lt;/strong&gt; Your provider's usage page is ground truth. Reconcile your estimate against it monthly, and investigate when they diverge — a divergence usually means a prompt grew or a retry loop appeared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small volumes, small stakes.&lt;/strong&gt; At $156 a month, none of this is a crisis. The point is that the structure scales: the same ratios at 100× volume are a budget line, and the fixes (caching, trimming fixed prompts, modeling regenerations) are cheapest to build in before volume arrives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run your own numbers
&lt;/h2&gt;

&lt;p&gt;Plug in your request count, your real per-request token counts, and your model's current per-million prices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://youcancalc.com/calculators/ai-api-cost-calculator/" rel="noopener noreferrer"&gt;AI API cost calculator&lt;/a&gt;&lt;/strong&gt; — free, no signup, runs entirely in your browser.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Count the tokens you send on every request first. They are the ones that compound.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Amortization math in 40 lines of JavaScript: building a mortgage calculator with zero backend</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Mon, 26 Oct 2026 19:29:03 +0000</pubDate>
      <link>https://dev.to/farbodf/amortization-math-in-40-lines-of-javascript-building-a-mortgage-calculator-with-zero-backend-4ig2</link>
      <guid>https://dev.to/farbodf/amortization-math-in-40-lines-of-javascript-building-a-mortgage-calculator-with-zero-backend-4ig2</guid>
      <description>&lt;p&gt;&lt;strong&gt;A mortgage calculator is one formula, a loop, and a rounding decision. The formula is the easy part. The rounding decision is the one your users will actually notice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built a mortgage calculator as a static page — no backend, everything in the browser. The core question it answers sounds trivial: given a loan amount, a rate, and a term, what fixed monthly payment brings the balance to exactly zero? The formula is closed-form, the code fits in under 40 lines, and yet the first version I shipped disagreed with every bank statement I tested it against — by cents. Cents, it turns out, are where calculator credibility lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one formula
&lt;/h2&gt;

&lt;p&gt;The payment that amortizes a loan is the present value of an annuity, solved for the payment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;M = P × r(1 + r)ⁿ / ((1 + r)ⁿ − 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;P is the loan amount, r is the monthly rate (annual ÷ 12), n is the number of payments. In JavaScript, that is one function:&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;monthlyPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;annualRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;years&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;annualRate&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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;years&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&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;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&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;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schedule — what each payment actually does — is a loop around it:&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;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;annualRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extra&lt;/span&gt; &lt;span class="o"&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="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;annualRate&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;pay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monthlyPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;annualRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extra&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;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interestPaid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;yearly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;while &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;month&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1200&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;pay&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;interestPaid&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;month&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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="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="nx"&gt;yearly&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="na"&gt;year&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;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&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="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;round&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;interestPaid&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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interestPaid&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yearly&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;That is the whole engine: payment, interest-on-remaining-balance, principal as the remainder, repeat until zero. No backend, no library, no state beyond a balance variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: $300,000 at 6.5% for 30 years
&lt;/h2&gt;

&lt;p&gt;Inputs: P = $300,000, r = 0.065/12, n = 360.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Monthly payment: $1,896.20&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Total paid over 30 years: &lt;strong&gt;$682,633.47&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total interest: &lt;strong&gt;$382,633.47&lt;/strong&gt; — more than the loan itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first payment breaks down as &lt;strong&gt;$1,625.00 in interest&lt;/strong&gt; and only &lt;strong&gt;$271.20 in principal&lt;/strong&gt; — 85.7% interest. That is not a bank trick; it is what "interest on the remaining balance" means when the balance starts at $300,000. One month's interest on the full amount is $1,625, whatever you do. Each payment shrinks the balance slightly, so next month's interest charge is slightly smaller, and slightly more of the payment reaches principal. The ratio flips slowly, then all at once in the final years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The figure that surprises everyone
&lt;/h2&gt;

&lt;p&gt;Five years in, you have made 60 payments totalling &lt;strong&gt;$113,772.25&lt;/strong&gt;. Guess the remaining balance: &lt;strong&gt;$280,832.93&lt;/strong&gt;. You have repaid only &lt;strong&gt;$19,167.07 of principal — 6.4% of the loan&lt;/strong&gt; — while paying &lt;strong&gt;$94,605.18 in interest&lt;/strong&gt;. A sixth of the way through the loan in time, and the balance has barely moved.&lt;/p&gt;

&lt;p&gt;That front-loading is also why extra payments are so powerful &lt;em&gt;early&lt;/em&gt;. Add just $200 a month to the same loan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payoff in &lt;strong&gt;277 months (23 years 1 month)&lt;/strong&gt; instead of 360 — about 6 years 11 months early&lt;/li&gt;
&lt;li&gt;Total interest falls to &lt;strong&gt;$279,184.67&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Interest saved: &lt;strong&gt;$103,448.80&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two hundred dollars a month — roughly a 10.5% bump on the payment — erases more than a quarter of the total interest. The same $200 in year 25 mostly replaces payments you would make anyway. Extra principal attacks the balance when the interest charge is largest, which is exactly when the balance is largest: at the start.&lt;/p&gt;

&lt;p&gt;(Shortening the term does the same thing from the other direction: the same $300,000 at 6.5% over 15 years costs $2,613.32 a month — only 1.38× the 30-year payment — but total interest collapses from $382,633.47 to $170,397.98.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The cents problem
&lt;/h2&gt;

&lt;p&gt;So why did my first version disagree with bank statements? Because a bank rounds every month's balance to cents, and my loop did not. Run the loop in raw float64 and the final balance lands a few cents from zero; a statement, rounding monthly, lands exactly on zero with a slightly different interest total. Neither is "wrong" — they are different rounding policies — but a user comparing your calculator to their statement will trust the statement.&lt;/p&gt;

&lt;p&gt;The rules I shipped with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Round the displayed balance to cents every month&lt;/strong&gt;, exactly like a ledger does. Do not round intermediate interest to cents and then feed it back into a second calculation — pick one policy and document it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clamp the final payment.&lt;/strong&gt; &lt;code&gt;Math.min(pay - interest, balance)&lt;/code&gt; is the line that keeps the last payment from overshooting and the balance from going negative by $0.03. Without it, your schedule ends with a phantom final month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never compare the balance to zero with &lt;code&gt;===&lt;/code&gt;.&lt;/strong&gt; After 360 float operations, &lt;code&gt;balance === 0&lt;/code&gt; is a coin flip. Loop while &lt;code&gt;balance &amp;gt; 0.005&lt;/code&gt; (half a cent) or, better, do the whole schedule in integer cents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round once at display, or in cents throughout — never both inconsistently.&lt;/strong&gt; Mixing a cents-rounded balance with a float-rounded interest total is how you get a schedule whose column sums disagree with its own total by a dollar.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is principal and interest only.&lt;/strong&gt; A real monthly housing payment adds property tax, homeowner's insurance, and possibly PMI — often 25–40% on top of the P&amp;amp;I figure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rates here are illustrative.&lt;/strong&gt; Your rate, down payment, and term change every figure above. The &lt;em&gt;shape&lt;/em&gt; — interest-heavy early, principal-heavy late — never changes for a fixed-rate loan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extra payments are not always optimal.&lt;/strong&gt; If your rate is below what you would earn investing, or you lack an emergency fund, extra principal may not be the best use of the money. This article shows what the math &lt;em&gt;does&lt;/em&gt;, not what you &lt;em&gt;should&lt;/em&gt; do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjustable-rate loans differ.&lt;/strong&gt; Everything here assumes a fixed rate. ARMs reset, and the schedule has to be rebuilt at each reset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run your own numbers
&lt;/h2&gt;

&lt;p&gt;Plug in your price, down payment, rate, and term, and see exactly where every dollar of every payment goes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://youcancalc.com/calculators/mortgage-calculator/" rel="noopener noreferrer"&gt;Mortgage calculator&lt;/a&gt;&lt;/strong&gt; — free, no signup, runs entirely in your browser.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The bank already ran these numbers. Now you have the loop too.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>finance</category>
    </item>
    <item>
      <title>Floating-point money math: getting compound interest right in the browser</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Thu, 22 Oct 2026 19:35:33 +0000</pubDate>
      <link>https://dev.to/farbodf/floating-point-money-math-getting-compound-interest-right-in-the-browser-5663</link>
      <guid>https://dev.to/farbodf/floating-point-money-math-getting-compound-interest-right-in-the-browser-5663</guid>
      <description>&lt;p&gt;&lt;strong&gt;A compound-interest calculator is one formula and about forty lines of JavaScript. The formula isn't the hard part. The hard part is that money has cents, and your floats don't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built a compound-interest calculator as a static page — no backend, everything in the browser. The core is a single line of math. But shipping it meant answering a question I hadn't expected to care about: how do you keep 360 months of floating-point arithmetic from lying about somebody's retirement?&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line
&lt;/h2&gt;

&lt;p&gt;Future value of a monthly contribution is the standard annuity formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FV = PMT × (((1 + r)ⁿ − 1) / r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PMT is the monthly payment, r is the monthly rate (annual ÷ 12), n is the number of months. That's it. The calculator takes $500/month, 7% annual, 30 years, and evaluates this in the browser. Simple — until you check the cents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: $500 a month for 30 years
&lt;/h2&gt;

&lt;p&gt;Inputs: PMT = $500, r = 0.07/12 ≈ 0.0058333, n = 360.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Future value: $609,985.50&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Total contributed: $180,000&lt;/li&gt;
&lt;li&gt;Interest earned: &lt;strong&gt;$429,985.50&lt;/strong&gt; — more than double what was put in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the figure that surprises everyone: run the same calculation for 20 years instead of 30. Future value: &lt;strong&gt;$260,463.33&lt;/strong&gt;. So waiting ten years to start — contributing $120,000 instead of $180,000 — costs &lt;strong&gt;$349,522&lt;/strong&gt; of the final balance. The extra decade runs on only $60,000 of additional contributions and produces $349,522 of additional value. Compounding doesn't reward starting big. It rewards starting early, and the reward curve is steeper than intuition allows.&lt;/p&gt;

&lt;p&gt;(Also worth knowing: 7% "nominal" compounded monthly is actually &lt;strong&gt;7.229%&lt;/strong&gt; effective annual — &lt;code&gt;(1 + 0.07/12)¹² − 1&lt;/code&gt;. If a product quotes you a nominal rate, this conversion is where the small print lives.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The float traps I actually hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Trap 1: display.&lt;/strong&gt; Every JS developer knows &lt;code&gt;0.1 + 0.2 === 0.30000000000000004&lt;/code&gt;. In a calculator, this surfaces as &lt;code&gt;$28.999999999999996&lt;/code&gt; where a user expects &lt;code&gt;$29.00&lt;/code&gt;. The fix is old: compute in integer cents, or round exactly once at display time. Never show a raw float as money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 2: &lt;code&gt;toFixed&lt;/code&gt; rounds in ways that surprise people.&lt;/strong&gt; Try these in your console:&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="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.615&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// "0.61" — not "0.62"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.005&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// "1.00" — not "1.01"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.675&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// "2.67"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a bug in &lt;code&gt;toFixed&lt;/code&gt;. &lt;code&gt;0.615&lt;/code&gt; in binary floating point is actually &lt;code&gt;0.61499999999999999111…&lt;/code&gt;, so rounding "down" is correct for the number you really have. The bug is in the programmer's mental model: the decimal you typed is not the number you're holding. If your calculator rounds intermediate values with &lt;code&gt;toFixed&lt;/code&gt; and feeds the results back into more math, you're compounding representation error on top of interest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 3: the one I expected to be terrible, and wasn't.&lt;/strong&gt; I assumed that accumulating month by month in float64 for 360 iterations would drift meaningfully from the closed-form answer. I tested it: the loop &lt;code&gt;bal = bal * (1 + r) + 500&lt;/code&gt; ends at &lt;strong&gt;609985.4978879701&lt;/strong&gt;; the exact value is 609985.4978879664…. The drift is &lt;strong&gt;less than a millionth of a cent&lt;/strong&gt;. float64 accumulation over a human lifetime is essentially exact. The enemy was never the loop — it's display rounding and naive equality checks (&lt;code&gt;balance === target&lt;/code&gt; after 360 float operations is a coin flip; compare with a tolerance, or better, in cents).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 4: your calculator and the bank can both be right and disagree.&lt;/strong&gt; A bank ledger rounds every transaction to cents. If you model that — round the balance to integer cents each month — the 30-year result is &lt;strong&gt;$609,985.57&lt;/strong&gt;, seven cents above the continuous formula's $609,985.50. Seven cents on six hundred thousand dollars: irrelevant to the user, but if your test suite asserts exact equality against a spreadsheet, this is the kind of thing that makes a test red at 2 a.m. Decide what your source of truth is (I used the closed form, rounded once for display) and document it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules I shipped with
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do money math in integer cents.&lt;/strong&gt; Multiply inputs by 100 at the boundary, divide by 100 at display. All intermediate arithmetic stays exact for any realistic amount.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round once, at the edge.&lt;/strong&gt; Intermediate rounding compounds; display rounding doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never compare floats for equality.&lt;/strong&gt; If you must compare, use an epsilon — or compare integer cents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust &lt;code&gt;toFixed&lt;/code&gt; for financial rounding&lt;/strong&gt; unless you've verified the exact binary values involved. For display it's fine; for logic, cents are fine-er.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7% is illustrative, not a promise.&lt;/strong&gt; Historical market averages are not a forecast; inflation, taxes, and fees all take their cut, and this calculator models none of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nominal vs. effective matters.&lt;/strong&gt; Always check which one a product quotes; the 7% → 7.229% conversion above is free money for whoever does the quoting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The formula assumes clockwork.&lt;/strong&gt; Real contributions are irregular, rates vary, life happens. The closed form is a planning tool, not a prophecy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run your own numbers
&lt;/h2&gt;

&lt;p&gt;Change the contribution, the rate, the timeline — and watch how violently the "start ten years earlier" column moves compared to everything else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://youcancalc.com/calculators/compound-interest-calculator/" rel="noopener noreferrer"&gt;Compound interest calculator&lt;/a&gt;&lt;/strong&gt; — free, no signup, runs entirely in your browser.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Your floats are fine. It's your rounding you should be afraid of.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>finance</category>
    </item>
    <item>
      <title>Pricing freelance dev work: the math behind rate calculators</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Sun, 18 Oct 2026 23:22:55 +0000</pubDate>
      <link>https://dev.to/farbodf/pricing-freelance-dev-work-the-math-behind-rate-calculators-3b3j</link>
      <guid>https://dev.to/farbodf/pricing-freelance-dev-work-the-math-behind-rate-calculators-3b3j</guid>
      <description>&lt;h1&gt;
  
  
  Pricing freelance dev work: the math behind rate calculators
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Your freelance rate isn't a guess, a vibe, or what the last client offered. It's four inputs and one division. Run them honestly and most devs discover they've been undercharging by a third.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every freelance developer I know set their first rate the same way: they heard what someone else charged, rounded it, and hoped. It feels like a confidence problem. It's actually an arithmetic problem — one with a formula simple enough to fit in a tweet, and consequences big enough to cost you tens of thousands a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The algorithm
&lt;/h2&gt;

&lt;p&gt;A freelance hourly rate has exactly four inputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Target annual take-home&lt;/strong&gt; — what you need to live on, not what sounds nice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tax rate&lt;/strong&gt; — you pay both halves now; gross up for it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annual business expenses&lt;/strong&gt; — tooling, hardware, accounting, insurance, coworking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Billable hours per year&lt;/strong&gt; — not 40×52. Nobody bills that.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gross_needed&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;−&lt;/span&gt; &lt;span class="n"&gt;tax_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;
&lt;span class="n"&gt;billable_hrs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;billable_hrs_per_week&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;working_weeks&lt;/span&gt;
&lt;span class="n"&gt;hourly_rate&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gross_needed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;billable_hrs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole calculator. The interesting part is what happens when you plug in honest numbers instead of aspirational ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: $95,000 take-home
&lt;/h2&gt;

&lt;p&gt;Say you're a developer who needs &lt;strong&gt;$95,000&lt;/strong&gt; a year to live on, in a ~30% effective tax situation, spending &lt;strong&gt;$8,000/year&lt;/strong&gt; on the business (laptop amortized, IDE/services, accountant, a health plan you now buy yourself). And you're realistic about utilization: &lt;strong&gt;22 billable hours a week&lt;/strong&gt; across &lt;strong&gt;46 weeks&lt;/strong&gt; (the other 6 are admin, pitching, learning, and the gaps between contracts).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gross needed: $95,000 / (1 − 0.30) + $8,000 = &lt;strong&gt;$143,714&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Billable hours: 22 × 46 = &lt;strong&gt;1,012&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Rate: $143,714 / 1,012 = &lt;strong&gt;$142.01 → call it $142/hour&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now compare: $95,000 as a salary is &lt;strong&gt;$45.67/hour&lt;/strong&gt; (÷ 2,080). The freelance equivalent is &lt;strong&gt;3.11×&lt;/strong&gt; that. That multiplier isn't greed — it's the entire freelance cost structure made visible: no paid leave, no employer insurance match, unpaid sales work, and income volatility you have to pre-fund yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule of thumb that costs you 33%
&lt;/h2&gt;

&lt;p&gt;The folk wisdom says "take your salary and divide by 1,000." For a $95k target that gives &lt;strong&gt;$95/hour&lt;/strong&gt;. The real calculation says &lt;strong&gt;$142&lt;/strong&gt;. The rule of thumb underestimates by a third — because it silently assumes ~1,000 billable hours at zero tax gross-up and zero expenses, which describes nobody.&lt;/p&gt;

&lt;p&gt;Rules of thumb are fine for a sanity check. As a pricing method, this one donates ~$47 of every hour you work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lever nobody talks about: utilization
&lt;/h2&gt;

&lt;p&gt;Here's the surprising part. Watch what happens to the rate when only the utilization input moves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;22 h/week × 46 weeks = 1,012 h → &lt;strong&gt;$142/hour&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;30 h/week × 48 weeks = 1,440 h → &lt;strong&gt;$99.80/hour&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A realistic increase in billable hours cuts the required rate by &lt;strong&gt;$42.21/hour — nearly 30%&lt;/strong&gt;. Utilization is by far the biggest lever in the formula: bigger than cutting expenses (halving $8k of expenses moves the rate by ~$4/hour) and bigger than most people expect from "working a bit more."&lt;/p&gt;

&lt;p&gt;Which is exactly why the honest calculation matters: when a client pushes back on $142, you shouldn't just cave — you should know &lt;em&gt;which input&lt;/em&gt; you're changing. Accepting $100/hour without changing anything else means you've implicitly committed to 30 billable hours a week, 48 weeks a year. Say that out loud before you agree to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Taxes are personal.&lt;/strong&gt; 30% is illustrative; yours depends on jurisdiction, deductions, and structure. The formula doesn't care — plug in your real number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This prices your time, not your value.&lt;/strong&gt; For specialized work with measurable client ROI, value-based pricing can (and should) exceed this floor. Treat the calculation as the &lt;em&gt;minimum&lt;/em&gt;, not the target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It ignores acquisition cost.&lt;/strong&gt; Landing clients costs time and sometimes money; if 20% of your working hours go to business development, that's already inside the 22-hour billable assumption — make sure it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rates are sticky.&lt;/strong&gt; Raising rates on existing clients is harder than setting them right at the start. Run this &lt;em&gt;before&lt;/em&gt; the next proposal, not after the next invoice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run your own numbers
&lt;/h2&gt;

&lt;p&gt;Four inputs, one division — but your inputs are yours. Plug in your real target, your real tax situation, your real expenses, and your honest billable hours:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://youcancalc.com/calculators/freelance-rate-calculator/" rel="noopener noreferrer"&gt;Freelance rate calculator&lt;/a&gt;&lt;/strong&gt; — free, no signup, runs entirely in your browser.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Undercharging isn't humility. It's a math error — and math errors are fixable.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
      <category>fintech</category>
    </item>
    <item>
      <title>I Ship 61 Calculators as a Static Site in 12 Languages — Here's How</title>
      <dc:creator>Farbod F.</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:16:34 +0000</pubDate>
      <link>https://dev.to/farbodf/i-ship-61-calculators-as-a-static-site-in-12-languages-heres-how-fl</link>
      <guid>https://dev.to/farbodf/i-ship-61-calculators-as-a-static-site-in-12-languages-heres-how-fl</guid>
      <description>&lt;h1&gt;
  
  
  I Ship 61 Calculators as a Static Site in 12 Languages — Here's How
&lt;/h1&gt;

&lt;p&gt;We're a small team of software engineers, and we run &lt;a href="https://youcancalc.com/" rel="noopener noreferrer"&gt;youcancalc.com&lt;/a&gt; — a free collection of 61 calculators, localized into 12 languages. No accounts, no backend, no paywall, no tracking pixels. Every calculation happens in your browser. This is a notes-from-the-trenches post about the architecture choices that make that possible, and the ones we'd make again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing is static
&lt;/h2&gt;

&lt;p&gt;The site is a statically-exported site: every page is pre-rendered to plain HTML at build time and served as files. There is no application server, no database, no API to keep warm. A calculator page is a document with a script attached, not an app with a backend.&lt;/p&gt;

&lt;p&gt;This sounds limiting until you realize what a calculator actually needs: inputs, arithmetic, and formatted output. None of that requires a server round-trip. The result is pages that answer instantly — no spinners, no "computing..." states — and hosting that is boring in the best way.&lt;/p&gt;

&lt;h2&gt;
  
  
  All computation is client-side
&lt;/h2&gt;

&lt;p&gt;Each calculator page follows the same contract:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inputs&lt;/strong&gt; — the user types numbers, picks options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant result&lt;/strong&gt; — recomputed on every keystroke, locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How it works&lt;/strong&gt; — the formula is printed next to the answer, not hidden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worked examples&lt;/strong&gt; — concrete numbers walked through step by step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAQ&lt;/strong&gt; — the questions people actually ask about this calculation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related calculators&lt;/strong&gt; — the natural next questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because everything runs client-side, there's a privacy property we get for free and take seriously: &lt;strong&gt;your inputs never leave your browser.&lt;/strong&gt; There's nothing to leak because there's nowhere for the data to go. We don't have your numbers; we never did. In an era where every form field seems to phone home, "the architecture makes surveillance impossible" is a stronger guarantee than any privacy policy paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  i18n without a translation department
&lt;/h2&gt;

&lt;p&gt;Twelve locales: English, Spanish, French, German, Italian, Portuguese, Arabic, Dutch, Polish, Chinese, Japanese, Korean. A few things we learned doing this as a tiny team:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Locale-prefixed routes.&lt;/strong&gt; Every language lives under its own path prefix (&lt;code&gt;/es/&lt;/code&gt;, &lt;code&gt;/de/&lt;/code&gt;, …), so each locale is a fully crawlable, shareable site — not a JavaScript-swapped afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Localize everything, including the math.&lt;/strong&gt; Formulas, worked examples, and FAQs are content, not chrome — they all go through the i18n layer. A French user should read the explanation of compound interest in French, not just the buttons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers are not universal.&lt;/strong&gt; This is the bug class that keeps us humble. &lt;code&gt;Intl.NumberFormat&lt;/code&gt; exists for a reason: 1,234.56 in English is 1.234,56 in German. Decimal separators, digit grouping, and even digit shapes differ. Every numeric input and output goes through locale-aware formatting and parsing — parse with the locale, never with &lt;code&gt;parseFloat&lt;/code&gt; on raw user input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RTL is a layout commitment.&lt;/strong&gt; Arabic means right-to-left, and that affects more than text direction: form layouts, example tables, and the visual order of "inputs → result" all need to mirror correctly. We test it; it's the kind of thing that's either right or embarrassing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we deliberately don't do
&lt;/h2&gt;

&lt;p&gt;Constraints are features. We don't do accounts (nothing to personalize), we don't do paywalls (the calculators are the product, and they're free), and we don't do tracking (see above: nothing to track with). We also don't have a blog — each calculator page carries its own explanation, examples, and FAQ, so the content lives where the question is asked instead of in a separate content silo.&lt;/p&gt;

&lt;p&gt;This keeps the operational surface tiny: no user data to protect, no sessions to manage, no email infrastructure, no A/B testing apparatus watching your mouse. The site is, by construction, a very boring thing to operate. We like boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest trade-offs
&lt;/h2&gt;

&lt;p&gt;Static has costs and we won't pretend otherwise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build time grows with locales × pages.&lt;/strong&gt; 61 calculators × 12 languages is hundreds of pages per build. It's fine on modern hardware, but it's the thing we watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No personalization.&lt;/strong&gt; We can't remember your last calculation across devices, because we don't know who you are. We consider this acceptable — arguably it's the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side means client constraints.&lt;/strong&gt; Very old browsers and very slow devices get a degraded but functional experience; we keep the client-side script lean and avoid heavy client libraries rather than assuming a flagship phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation quality is an ongoing process.&lt;/strong&gt; Machine-assisted translation gets you 90% of the way; the last 10% — idioms in FAQs, culturally specific examples — needs human eyes, and with 12 locales it's never "done."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this architecture fits calculators specifically
&lt;/h2&gt;

&lt;p&gt;A calculator is a pure function with a nice UI: same inputs, same outputs, every time. There's no shared state, no feed, no real-time collaboration. The moment you notice your product is embarrassingly parallel and stateless, the correct architecture is usually "files on a CDN." We just took that seriously and stopped there instead of adding a backend out of habit.&lt;/p&gt;

&lt;p&gt;If you're building something similar — reference tools, converters, small utilities — our sincere recommendation: start static, compute locally, and only add a server when you can name the specific thing it does that the browser can't. You might be surprised how rarely that day comes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We're the team behind youcancalc — 61 free calculators, 12 languages, zero tracking. If you build tiny tools on the web, we'd genuinely like to hear how you architect them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
