A currency converter looks like the simplest tool on the internet: multiply an amount by a rate, show the answer. I built one anyway — a zero-backend converter at ToolkitLoop — and the interesting problems turned out to have nothing to do with multiplication. They were about which rate you show, how old it is allowed to be, and what your users think the number means.
Disclosure up front: I built this tool. It is free, runs in the browser, and needs no signup. Everything below comes from decisions I actually made while building it.
Lesson 1: A rate is two timestamps, not one
The first design decision was what to display next to the number. A rate without a date is a claim without evidence, so the page shows two separate timestamps: the rate date (when the provider published the rate) and the fetch time (when this site retrieved it). They answer different questions. "Published yesterday, fetched a minute ago" is not the same as "published this morning, fetched a minute ago," and a traveller checking today's cash withdrawal needs to know which one they are looking at.
The data comes from Frankfurter v2, which aggregates institutional reference data, requested through a server-side cache. The default cache is one hour. If the provider is temporarily unavailable, an older usable cache is explicitly labelled as old rather than presented as new data. That labelling rule was a deliberate choice: a converter that silently serves a stale rate is worse than one that says "this is old" and lets the user decide.
// The shape of the decision, not the production code: // a rate is only trustworthy if you can answer both questions. const rate = { value: 0.85, // example only — never a live rate rateDate: "2026-11-09", // when the provider published it fetchedAt: "2026-11-09T10:00:00Z", // when we retrieved it cacheAgeMinutes: 42, isStale: false, }; function isUsable(rate, maxAgeMinutes = 60) { if (rate.isStale) return { usable: false, reason: "labelled stale cache" }; return { usable: rate.cacheAgeMinutes <= maxAgeMinutes, reason: "within cache window" }; } The debatable point, and the one I would argue about over coffee: is a labelled one-hour-old rate more honest than a silently refreshed one? I chose labelling. A rate that admits its age lets a careful user re-check; a rate that hides it invites a false sense of precision.
Lesson 2: Your amount should never leave the browser
The architecture is the privacy story. The amount you type stays in your browser; only the selected currency pair is requested to retrieve the reference rate. The server receives currency codes, never the raw amount. There is no account, no history, no "your recent conversions" feature — because there is nothing to store.
That constraint shaped the implementation more than any framework choice. The formula is trivial — amount × reference rate = converted result — and the interesting code is the boundary: what crosses the network (two currency codes) and what never does (everything else). Swapping the currencies recalculates immediately from the loaded reference rate, with no new request for the amount, because the amount was never sent in the first place.
Lesson 3: The reference rate is not the settlement price
This is the lesson most users get wrong, and the one the tool's own FAQ has to keep repeating: a reference rate is useful for comparison, but it is not your bank, card-network, or ATM settlement price. Your charge can differ because of issuer rates, markups, foreign-transaction fees, fixed fees, and timing.
I deliberately did not bake a fee percentage into the tool. Card and ATM fees vary by product, location, and transaction, so assuming a fixed percentage would manufacture a false precision. A future fee mode will let you enter your own assumptions and clearly distinguish that estimate from the reference rate. Until then, the honest workflow is two numbers: the reference result for planning, and your card's terms for the final cost. If a converter shows you one number and calls it "what you will pay," it is lying by omission.
Lesson 4: DCC is a UX problem, not just a fee problem
Dynamic currency conversion (DCC) is the option that lets a merchant or ATM charge you in your home currency. Its exchange rate and fees can be less favourable than paying in the local currency. Every traveller guide says "always pay in local currency," and yet people still accept DCC — because the terminal frames it as helpful. You see the amount in dollars. It feels transparent. The markup is invisible.
That is a design problem as much as a finance problem. The terminal optimises for the feeling of certainty ("you know exactly what you pay") over the reality of cost. Building a converter taught me to respect that framing effect: my own tool has to fight the same instinct. Showing a single converted number feels certain; showing the rate date, the cache status, and a warning that your card may differ feels fussy. I kept the fussy version, because the certain version is the one that misleads.
Practically: at a card terminal, choose the local currency if offered. At an ATM, decline the conversion offer — declining it does not cancel the withdrawal, it just lets your bank do the conversion. At a hotel desk, check the bill's currency before you sign, because a bill converted at check-in locks in the merchant's rate.
Lesson 5: Say what the tool does not do yet
The first release focuses on a transparent current/reference conversion. Historical-date and trend analysis will be added to the same tool rather than as empty or duplicate pages, and there is no fee mode yet. Writing those limits on the page felt wrong at first — why advertise what is missing? — but it is the same honesty as labelling a stale cache. A user planning an expense report around last month's rate needs to know this tool cannot give it to them, before they build a spreadsheet on it. Use your card statement's settled rate for that, not today's reference rate.
The popular pairs tell the same story about scope: USD to EUR, USD to JPY, EUR to USD, GBP to USD. Travellers check a small set of pairs obsessively and the rest rarely. Depth on the pairs people actually use beats a long list nobody opens.
The question I keep coming back to
Here is the hook I promised: should a converter round to the nearest cent, or keep full precision? Rounding feels friendly and matches what a bank statement shows. Full precision is more honest about the maths, and the rounding error compounds when you convert a budget line by line. I rounded the displayed result and kept the unrounded value for the calculation — but I am not sure that is right. If you convert a 14-line trip budget, do you want each line rounded, or the total rounded once at the end?
Tell me how you would build it — and what your worst DCC or settlement-day surprise was. The comments on my last few posts turned into the best code reviews I have had, and this one has a genuinely open question in it.

