Sports betting odds are often displayed in different formats depending on the region or platform.
You might see:
Decimal odds: 2.50
Fractional odds: 3/2
American odds: +150
They can all represent the same underlying probability, but the numbers look completely different.
In this tutorial, we'll build a small sports odds converter with plain JavaScript. It will convert between decimal, fractional, and American odds and calculate the implied probability.
No framework. No external API. Just HTML, CSS, and JavaScript.
The goal is not to build a betting application. It's simply a useful JavaScript project for practicing input handling, calculations, validation, and DOM updates.
What we're building
The finished converter will let a user enter odds and choose the format.
For example:
Decimal: 2.50
Fractional: 3/2
American: +150
The application will then show the equivalent values.
We'll also calculate the implied probability.
For decimal odds of 2.50:
Implied probability = 1 / 2.50
= 0.40
= 40%
This is a mathematical conversion, not a prediction of whether a bet will win.
Understanding the three odds formats
Before writing JavaScript, it's worth understanding what we're converting.
Decimal odds
Decimal odds are probably the easiest format to work with programmatically.
The basic implied probability formula is:
Probability = 1 / Decimal Odds
So:
1 / 2.50 = 0.40
or:
40%
For a decimal value of 1.50:
1 / 1.50 = 0.6667
So the implied probability is approximately:
66.67%
American odds
American odds use positive and negative numbers.
Positive odds:
+150
Negative odds:
-200
For positive American odds:
Probability = 100 / (American Odds + 100)
For +150:
100 / (150 + 100)
= 100 / 250
= 0.40
So the implied probability is:
40%
For negative American odds:
Probability = -American Odds / (-American Odds + 100)
For -200:
200 / (200 + 100)
= 200 / 300
= 0.6667
That's approximately:
66.67%
Fractional odds
Fractional odds are written as:
3/2
The implied probability is:
Probability = denominator / (numerator + denominator)
For 3/2:
2 / (3 + 2)
= 2 / 5
= 0.40
So:
40%
Now we have enough information to build the converter.
Project structure
Keep the project simple:
sports-odds-converter/
│
├── index.html
├── style.css
└── script.js
You can also put everything into one HTML file while experimenting, but separating the files makes the project easier to maintain.
Step 1: Create the HTML
Start with the basic interface.
<!DOCTYPE html>
Sports Odds Converter
<label for="oddsType">Odds format</label>
<select id="oddsType">
<option value="decimal">Decimal</option>
<option value="fractional">Fractional</option>
<option value="american">American</option>
</select>
<label for="oddsInput">Odds</label>
<input
id="oddsInput"
type="text"
placeholder="Example: 2.50"
>
<button id="convertButton">
Convert
</button>
<p id="error"></p>
<section id="results">
<p>Decimal: <span id="decimalResult">-</span></p>
<p>Fractional: <span id="fractionalResult">-</span></p>
<p>American: <span id="americanResult">-</span></p>
<p>Implied Probability: <span id="probabilityResult">-</span></p>
</section>
There isn't anything complicated here.
We have:
A dropdown for the input format.
An input field.
A conversion button.
Four output fields.
An error message area.
Now let's add some basic styling.
Step 2: Add some CSS
Create style.css:
- { box-sizing: border-box; }
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f4f4f4;
color: #222;
}
.container {
max-width: 500px;
margin: 60px auto;
padding: 24px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
}
h1 {
margin-top: 0;
}
label {
display: block;
margin-top: 16px;
margin-bottom: 6px;
font-weight: bold;
}
input,
select,
button {
width: 100%;
padding: 12px;
font-size: 16px;
}
button {
margin-top: 20px;
cursor: pointer;
}
error {
color: #c00;
margin-top: 15px;
}
results {
margin-top: 25px;
padding-top: 15px;
border-top: 1px solid #ddd;
}
That's enough for this project.
The interesting part is the JavaScript.
Step 3: Write the conversion functions
Create script.js.
We'll start with decimal odds.
function decimalToProbability(decimal) {
return 1 / decimal;
}
That's all we need for the basic probability calculation.
Now let's convert decimal odds into American odds.
function decimalToAmerican(decimal) {
if (decimal >= 2) {
return Math.round((decimal - 1) * 100);
}
return Math.round(-100 / (decimal - 1));
}
For example:
2.50
becomes:
+150
And:
1.50
becomes:
-200
Next, decimal to fractional.
function decimalToFractional(decimal) {
const value = decimal - 1;
let denominator = 100;
let numerator = Math.round(value * denominator);
const divisor = gcd(numerator, denominator);
numerator /= divisor;
denominator /= divisor;
return ${numerator}/${denominator};
}
We need a small helper function to reduce the fraction.
function gcd(a, b) {
while (b !== 0) {
const remainder = a % b;
a = b;
b = remainder;
}
return Math.abs(a);
}
For example:
2.50
means a profit of:
1.50
which is:
3/2
Step 4: Convert American odds to decimal
Now let's handle American odds.
function americanToDecimal(american) {
if (american > 0) {
return 1 + american / 100;
}
return 1 + 100 / Math.abs(american);
}
So:
+150
becomes:
2.50
And:
-200
becomes:
1.50
Then we can reuse our decimal conversion functions.
That's an important design choice.
Instead of writing every possible conversion separately, we can convert the input into decimal first and then use decimal as our internal format.
The flow becomes:
Input
↓
Convert to Decimal
↓
Convert Decimal to other formats
↓
Display results
This keeps the code much easier to understand.
Step 5: Convert fractional odds to decimal
Fractional odds look like:
3/2
The decimal conversion is:
1 + numerator / denominator
So:
function fractionalToDecimal(fractional) {
const parts = fractional.split("/");
if (parts.length !== 2) {
throw new Error("Invalid fractional odds.");
}
const numerator = Number(parts[0]);
const denominator = Number(parts[1]);
if (
!Number.isFinite(numerator) ||
!Number.isFinite(denominator) ||
numerator < 0 ||
denominator <= 0
) {
throw new Error("Invalid fractional odds.");
}
return 1 + numerator / denominator;
}
Now:
3/2
becomes:
2.50
Step 6: Handle input validation
Good applications don't assume the user entered valid data.
For decimal odds, we need a value greater than 1.
function parseDecimal(value) {
const decimal = Number(value);
if (!Number.isFinite(decimal) || decimal <= 1) {
throw new Error("Decimal odds must be greater than 1.");
}
return decimal;
}
For American odds:
function parseAmerican(value) {
const american = Number(value);
if (
!Number.isInteger(american) ||
american === 0
) {
throw new Error("American odds must be a non-zero integer.");
}
return american;
}
Now we can build one function that turns any supported input into decimal odds.
function toDecimal(value, type) {
switch (type) {
case "decimal":
return parseDecimal(value);
case "american":
return americanToDecimal(parseAmerican(value));
case "fractional":
return fractionalToDecimal(value.trim());
default:
throw new Error("Unsupported odds format.");
}
}
This is where the earlier design decision pays off.
Everything eventually becomes decimal odds.
Step 7: Build the converter
Now connect everything to the page.
const oddsType = document.getElementById("oddsType");
const oddsInput = document.getElementById("oddsInput");
const convertButton = document.getElementById("convertButton");
const decimalResult = document.getElementById("decimalResult");
const fractionalResult = document.getElementById("fractionalResult");
const americanResult = document.getElementById("americanResult");
const probabilityResult = document.getElementById("probabilityResult");
const error = document.getElementById("error");
convertButton.addEventListener("click", () => {
error.textContent = "";
try {
const type = oddsType.value;
const value = oddsInput.value;
const decimal = toDecimal(value, type);
const american = decimalToAmerican(decimal);
const fractional = decimalToFractional(decimal);
const probability = decimalToProbability(decimal);
decimalResult.textContent = decimal.toFixed(2);
fractionalResult.textContent =
fractional;
americanResult.textContent =
american > 0
? `+${american}`
: american;
probabilityResult.textContent =
`${(probability * 100).toFixed(2)}%`;
} catch (err) {
error.textContent = err.message;
}
});
That's the core application.
Save the files and open index.html in your browser.
Try a few examples
Let's test some values.
Example 1: Decimal odds
Input:
2.50
Expected output:
Decimal: 2.50
Fractional: 3/2
American: +150
Implied Probability: 40.00%
Example 2: American odds
Input:
-200
Expected output:
Decimal: 1.50
Fractional: 1/2
American: -200
Implied Probability: 66.67%
Example 3: Fractional odds
Input:
3/2
Expected output:
Decimal: 2.50
Fractional: 3/2
American: +150
Implied Probability: 40.00%
Notice something important here.
Different odds formats can represent the same mathematical value.
That's exactly why a converter is useful.
One problem: bookmaker margin
There is an important limitation with implied probability.
The simple formula:
1 / decimal odds
does not automatically tell you the "true" probability of an outcome.
Sportsbooks can build a margin into their prices.
For example, imagine a two-outcome market where both outcomes have decimal odds of:
1.90
The implied probability for each is:
1 / 1.90
= 52.63%
Adding them:
52.63% + 52.63%
= 105.26%
That's more than 100%.
The difference is commonly called the overround, vig, or margin, depending on context.
So don't treat the displayed implied probability as a guaranteed prediction.
The converter is doing mathematics, not forecasting match results.
Making the code easier to maintain
One thing I like about this implementation is that each function does one job.
For example:
decimalToProbability()
handles probability.
decimalToAmerican()
handles American odds.
decimalToFractional()
handles fractional odds.
And:
toDecimal()
handles input normalization.
This is much easier to debug than putting every calculation inside one giant event listener.
If something goes wrong with American odds, you know where to look.
Add keyboard support
A small usability improvement is allowing the user to press Enter instead of clicking the button.
oddsInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
convertButton.click();
}
});
Now the converter works with both mouse and keyboard input.
It's a tiny change, but it makes the tool feel much better.
Add a reset button
We can also add a reset button.
HTML:
Reset
JavaScript:
const resetButton =
document.getElementById("resetButton");
resetButton.addEventListener("click", () => {
oddsInput.value = "";
decimalResult.textContent = "-";
fractionalResult.textContent = "-";
americanResult.textContent = "-";
probabilityResult.textContent = "-";
error.textContent = "";
});
Now users can quickly clear the calculator.
A few edge cases to think about
Small calculators often fail because developers only test the happy path.
Try entering:
hello
or:
0
or:
1
or:
3/0
or:
abc/def
The application should not crash.
Instead, it should show a useful error.
That's why validation is part of the implementation, not an optional extra.
You can also improve the fractional parser by checking that the input contains exactly one slash and that both parts are valid numbers.
Where you could take this project next
Once the basic converter works, there are several useful ways to extend it.
1. Add probability to odds conversion
Let users enter a probability such as:
40%
and convert it into the three odds formats.
2. Add a stake calculator
You could allow a user to enter a hypothetical stake and calculate the mathematical payout from the odds.
Keep this separate from any claim about expected winnings.
3. Add unit tests
For example:
console.assert(
Math.abs(americanToDecimal(150) - 2.5) < 0.0001
);
console.assert(
Math.abs(americanToDecimal(-200) - 1.5) < 0.0001
);
Testing the conversion functions independently makes future changes safer.
4. Add TypeScript
Once the JavaScript version works, converting the project to TypeScript would be a good exercise in typing function arguments and return values.
5. Build a reusable conversion module
You could move the calculation functions into:
odds.js
and keep the DOM code separate.
That would make the conversion logic reusable in another application.
The main lesson
The actual odds formulas are not the difficult part of this project.
The more useful JavaScript lessons are:
validating user input
separating calculations into small functions
normalizing different inputs into one internal format
updating the DOM
handling errors
keeping UI logic separate from calculation logic
The biggest design decision was using decimal odds as the internal representation:
Any input
↓
Decimal
↓
Fractional
American
Probability
That avoids creating six or more separate conversion paths.
If you later add another odds format, you only need to teach the application how to convert that format to and from the internal representation.
Final thoughts
A sports odds converter is a small project, but it's a surprisingly good JavaScript exercise.
You get user input, validation, mathematical formulas, string parsing, event listeners, DOM manipulation, and error handling in one compact application.
And because the entire project can run in the browser, you don't need a backend or external API to get started.
If you're learning JavaScript, don't worry about making the first version perfect.
Build the simple version first.
Then add tests.
Then improve the UI.
Then add another feature.
That's usually a better way to learn than trying to build a huge application from day one.
The important part is understanding what your code is actually doing.
Top comments (0)