October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk8 min

Learn Python Basics by Building a Real-World Currency Converter

Build a beginner Python currency converter in two stages: a fixed-rate command-line version, then an API-backed version using requests and JSON, with validation, error handling, and Decimal for money.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A currency converter is one of the most useful first Python projects because it forces you to use the core tools in a realistic way: storing values, reading user input, converting types, writing functions, making decisions, and handling bad data. Build it in two stages. First, write a command-line converter that uses a small fixed table of rates. Then replace that table with live data from an exchange-rate API that you request over HTTP and read as JSON. The fixed version teaches the logic; the API version teaches how real programs get information from other services.

What the project teaches you

Each part of a converter maps to a basic Python idea you will use in almost every program:

  • Values and variables: the amount, the two currency codes, and the rate table are all values stored under names.
  • User input: input() returns text, so you must convert it before doing arithmetic.
  • Numeric conversion: float() turns text such as "25.50" into a number and raises an error when the text is not numeric.
  • Functions: the calculation can live in its own function, separate from the code that prompts the user.
  • Conditionals: checks such as “is this currency supported?” and “is this amount positive?” decide what the program does next.
  • HTTP requests and JSON: in the second stage, your program asks a service for data and reads the structured reply.
  • Error handling: bad input, unknown codes, and network failures each need a clear, predictable response.

Stage 1: A converter with fixed rates

Start without any network code. Store one rate for each currency, expressed as units of that currency per one US dollar. The numbers below are illustrative placeholders for the exercise, not current market rates, and they will go stale the moment you stop updating them by hand. That is acceptable at this stage because the goal is to understand the logic.

Step 1: Store the rates

RATES_PER_USD = {
    "USD": 1.0,
    "EUR": 0.92,
    "GBP": 0.79,
    "JPY": 149.50,
}

Each key is a currency code and each value is how many units of that currency equal one US dollar. Because every rate uses the same base, you can convert between any two listed currencies by going through dollars.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 2: Write the calculation as a function

def convert(amount, source, target, rates):
    amount_in_usd = amount / rates[source]
    return amount_in_usd * rates[target]

This function does no input and no printing. You can test it on its own: convert(100, "USD", "EUR", RATES_PER_USD) should return 92.0 with the table above. Keeping calculation separate from input and output is the habit that makes later changes, such as swapping in an API, far easier.

Step 3: Read and validate the amount

import math

def read_amount():
    text = input("Amount: ").strip()
    try:
        amount = float(text)
    except ValueError:
        raise ValueError("Enter a number such as 25.50")
    if not math.isfinite(amount) or amount <= 0:
        raise ValueError("Amount must be a positive number")
    return amount

The math.isfinite() check matters. float() accepts strings such as "nan" and "inf", which are valid floats but meaningless as money amounts.

Step 4: Normalize and check the currency codes

Users type usd, Usd, and USD interchangeably, so normalize with .strip().upper() before checking the table. Then confirm both codes exist in RATES_PER_USD. Checking membership with in is simpler and clearer than letting a KeyError crash the program.

Step 5: Connect the pieces

def main():
    source = input("From currency code: ").strip().upper()
    target = input("To currency code: ").strip().upper()
    try:
        amount = read_amount()
    except ValueError as error:
        print(error)
        return
    if source not in RATES_PER_USD or target not in RATES_PER_USD:
        print("Unsupported code. Choose from:", ", ".join(RATES_PER_USD))
        return
    result = convert(amount, source, target, RATES_PER_USD)
    print(f"{amount:,.2f} {source} = {result:,.2f} {target}")

if __name__ == "__main__":
    main()

Run the file and try a valid conversion, a negative amount, the text abc, and an unknown code such as XYZ. Each bad input should produce a readable message and no traceback. If one of them crashes, find which check is missing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Stage 2: Replacing the table with an exchange-rate API

A fixed table cannot tell you today’s rate. To fetch rates from a provider, your program sends an HTTP request, checks the response, and reads the data it needs. Two things are worth knowing before you start.

  • Some providers need no account or key for basic requests. Frankfurter’s Python guide shows a plain requests call without an SDK or API key.
  • Other providers require a free account and API key. ExchangeRate-API’s Python guide describes a GET request and states that you need an account to get a key. If you use a key, keep it out of the source file you share, for example by reading it from an environment variable.

Step 1: Install requests and choose an endpoint

Install the library with pip install requests. Then copy the endpoint address from the provider’s own Python guide. Use the address as published there and confirm the current terms and address on the provider’s site, because endpoints and plan rules change.

Step 2: Request and check the response

import requests

def fetch_rates(url):
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
    except requests.exceptions.RequestException as error:
        raise ValueError(f"Could not reach the rate service: {error}")
    data = response.json()
    rates = data.get("rates")
    if not rates:
        raise ValueError("The service response did not include a rates table")
    return data, rates

Three checks happen here. The timeout keeps the program from waiting forever. raise_for_status() turns HTTP error codes into an exception you can handle. The rates check confirms the response has the structure your code expects. The key name "rates" is shown for illustration; use the name that appears in the sample response from the provider you choose, because providers name their fields differently.

Step 3: Reuse the same conversion function

Because you kept convert() separate, the only change needed in main() is where the table comes from. Replace RATES_PER_USD with the rates returned by fetch_rates(), and keep the same validation. Confirm the response states its base currency. If the base is listed in the table with a rate of 1.0, the dollar-based formula still works. If the base is something else, the formula still works as long as the base currency appears in the table.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 4: Show the rate date

Live rates are only meaningful with a date. Display the date or last-updated field from the response alongside the result, using the field name from that provider’s sample. currencyapi’s conversion example, for instance, includes a last-updated timestamp in its sample response, and showing it helps the reader judge how fresh the number is.

What an API rate does and does not tell you

The number from a provider is a reference rate. It is not a price anyone will give you. Keep these points in mind before you present the converter as anything more than a learning project.

  • Refresh frequency varies. Frankfurter says its latest blended rates change as providers publish them, at most a few times per working day. currencyapi’s documentation, as it described its own update frequencies at the time of writing, ranges from daily to minutely depending on the product. Check the provider’s page for the frequency that applies to your endpoint.
  • Official reference rates can lag. A pinned rate from an official source follows that source’s publication schedule and may differ from a blended latest rate on the same day.
  • Your bank or card issuer sets its own rate. Banks, exchange counters, and card networks apply their own rates, spreads, and fees. A converter that shows a reference rate will not match the amount you are charged.

Frankfurter’s guide recommends caching the latest rates for a short period and allows longer caching for historical rates pinned to a specific date. Caching is a sensible later extension, because it reduces repeated requests for data that changes slowly.

Money arithmetic: why floats and Decimal differ

Binary floating-point numbers cannot represent many decimal fractions exactly. In Python, 0.1 + 0.2 gives 0.30000000000000004. For a display-only lesson, rounding the output with an f-string is fine. For anything involving money, Frankfurter’s Python guide recommends parsing rates with Decimal from the standard library, because floats are acceptable for display but not for accounting.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from decimal import Decimal

rate = Decimal("0.92")
amount = Decimal("100.00")
print(amount * rate)   # 92.0000

Build the Stage 1 version with float so the logic stays simple. When you move to the API version, switch to Decimal for parsing and arithmetic, and convert to a string only at the output step. This is a useful habit to learn even though a learning project is not accounting software.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and how to handle them

Each row below is a failure you should expect to see while testing. The response in the right-hand column is the behavior the code should produce.

Failure Typical symptom Handling
Amount is text, such as abc float() raises ValueError Catch the error and ask the user for a number
Amount is zero, negative, nan, or inf Calculation runs but the result is meaningless Reject with math.isfinite() and amount <= 0 checks
Unknown or misspelled currency code Dictionary lookup raises KeyError Check membership first and list supported codes
Currency code is valid but the provider does not supply it Lookup fails after a successful request Check the target code against the response’s rates and report it as unavailable
Network failure or timeout requests raises a RequestException Catch it, print a plain message, and suggest trying again later
HTTP error status raise_for_status() raises an exception Report that the service returned an error rather than showing a traceback
Unexpected JSON structure Missing key returns None or fails later Confirm the expected field exists before using it

Comparing the providers mentioned in this tutorial

You do not need to choose a provider to finish the lesson, but you will need one for Stage 2. The table compares what the providers’ own documentation states at the time of writing. Plan limits and terms change, so read the current page before you build against them.

Question Frankfurter ExchangeRate-API currencyapi
API key or account needed? Not needed for the Python example in its guide Free account and API key required, per its Python guide Not stated in the pages reviewed for this article
Rate source and schedule Blended latest rates that change at most a few times per working day, per its guide Not stated in the pages reviewed for this article Update frequency ranges from daily to minutely, per its documentation
Historical rates Pinned historical rates supported, per its guide Not stated in the pages reviewed for this article Not stated in the pages reviewed for this article
Conversion method Reader multiplies the amount by the returned rate Reader makes a GET request and reads the rate Direct requests or an SDK; its documentation says the conversion endpoint is not available on the free plan
Request limits and cost tiers Not stated in the pages reviewed for this article Not stated in the pages reviewed for this article Plan details as described on its site; check current terms

Whichever provider you choose, read its terms for request limits, attribution requirements, and acceptable use before building anything you plan to share.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Optional extensions after the command-line version works

Add these only after both versions run cleanly. Each one builds on the functions you already have.

  • Caching: store the last response with its timestamp and reuse it for a short period, following the provider’s caching guidance.
  • Conversion history: append each successful conversion to a list, then print or save it to a file.
  • A graphical interface: Tkinter, which ships with most Python installers, can wrap the same convert() and fetch_rates() functions. Keep the calculation code unchanged so the interface stays thin.

The core lesson of the project is that the calculation, the data source, and the interface are three separate concerns. Keeping them separate lets you change any one without rewriting the others.

“

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.