How to Build a Weighted Grade Calculator in JavaScript
Learn the formula, JavaScript logic, validation, and DOM updates needed to calculate weighted grades accurately.

Weighted grades are common in schools, colleges, and universities because not every assignment contributes equally to a final course result.
A final exam might be worth 40% of a course, while homework could be worth only 10%. That means simply averaging all scores is not enough.
In this tutorial, we will build a weighted grade calculator using JavaScript. Along the way, we will cover the formula, input validation, DOM updates, and a few edge cases that make the calculator more reliable.
By the end, our calculator will be able to:
Accept multiple grade categories
Accept a score and weight for each category
Calculate the weighted contribution of every category
Add the contributions together
Check whether the weights total 100%
Display the final weighted grade
Understanding the Weighted Grade Formula
The basic formula is:
Weighted Grade = (Score × Weight) / 100
For several categories, we calculate each weighted contribution and add them together.
Imagine a course with these categories:
Homework: 92% × 20%
Projects: 88% × 30%
Final Exam: 84% × 50%
The weighted contributions are:
Homework: 92 × 20 / 100 = 18.4
Projects: 88 × 30 / 100 = 26.4
Final Exam: 84 × 50 / 100 = 42.0
Now add them:
18.4 + 26.4 + 42.0 = 86.8%
So the final weighted grade is:
86.8%
This is different from a normal average because the final exam has a much larger influence on the result.
Step 1: Create the HTML Structure
We will start with three grade categories.
<div class="calculator">
<h2>Weighted Grade Calculator</h2>
<div class="grade-row">
<input type="number" class="score" placeholder="Score %" min="0" step="any" />
<input type="number" class="weight" placeholder="Weight %" min="0" step="any" />
</div>
<div class="grade-row">
<input type="number" class="score" placeholder="Score %" min="0" step="any" />
<input type="number" class="weight" placeholder="Weight %" min="0" step="any" />
</div>
<div class="grade-row">
<input type="number" class="score" placeholder="Score %" min="0" step="any" />
<input type="number" class="weight" placeholder="Weight %" min="0" step="any" />
</div>
<button id="calculate">Calculate Weighted Grade</button>
<div id="result">
Enter scores and weights to calculate your grade.
</div>
</div>
Each row contains two values:
A percentage score
The percentage weight of that category
Step 2: Add Basic Styling
A simple layout is enough for this project.
body {
font-family: Arial, sans-serif;
background: #f5f7fb;
padding: 40px;
}
.calculator {
max-width: 640px;
margin: auto;
background: #ffffff;
padding: 28px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
.grade-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 12px;
}
.grade-row input {
padding: 12px;
border: 1px solid #ccd3dd;
border-radius: 7px;
font-size: 16px;
}
button {
margin-top: 10px;
padding: 12px 18px;
border: 0;
border-radius: 7px;
background: #146ef5;
color: white;
cursor: pointer;
}
#result {
margin-top: 20px;
padding: 16px;
background: #f0f6ff;
border-radius: 7px;
}
Step 3: Read the Scores and Weights
JavaScript can collect all score and weight inputs using querySelectorAll().
const scoreInputs = document.querySelectorAll(".score");
const weightInputs = document.querySelectorAll(".weight");
const calculateButton = document.getElementById("calculate");
const result = document.getElementById("result");
The score inputs and weight inputs are stored as two NodeLists.
Because the rows are in the same order, the score at index 0 matches the weight at index 0, and so on.
Step 4: Calculate the Weighted Grade
Now we can loop through all categories.
calculateButton.addEventListener("click", function () {
let weightedTotal = 0;
let totalWeight = 0;
for (let i = 0; i < scoreInputs.length; i++) {
const score = Number(scoreInputs[i].value);
const weight = Number(weightInputs[i].value);
weightedTotal += (score * weight) / 100;
totalWeight += weight;
}
result.textContent =
`Weighted grade: ${weightedTotal.toFixed(2)}%`;
});
The key calculation is:
weightedTotal += (score * weight) / 100;
Each category adds its own contribution to the final result.
Step 5: Validate the Inputs
The first version works, but it assumes every input is valid.
A reliable calculator should check for:
Missing scores
Missing weights
Negative numbers
Zero total weight
Weights that do not total 100%
Here is a safer version:
calculateButton.addEventListener("click", function () {
let weightedTotal = 0;
let totalWeight = 0;
for (let i = 0; i < scoreInputs.length; i++) {
const score = Number(scoreInputs[i].value);
const weight = Number(weightInputs[i].value);
if (!Number.isFinite(score) || score < 0) {
result.textContent =
"Please enter a valid score for every category.";
return;
}
if (!Number.isFinite(weight) || weight < 0) {
result.textContent =
"Please enter a valid weight for every category.";
return;
}
weightedTotal += (score * weight) / 100;
totalWeight += weight;
}
if (totalWeight === 0) {
result.textContent =
"The total weight must be greater than zero.";
return;
}
if (Math.abs(totalWeight - 100) > 0.01) {
result.textContent =
`Your weights total ${totalWeight.toFixed(2)}%. They should total 100%.`;
return;
}
result.textContent =
`Weighted grade: ${weightedTotal.toFixed(2)}%`;
});
Now the calculator warns users when their category weights are incomplete.
Why the Weight Total Matters
If a course contains:
Homework: 20%
Projects: 30%
Final Exam: 50%
the weights total:
100%
That means the calculator has the full course structure.
But suppose the user enters:
Homework: 20%
Projects: 30%
The weights total only:
50%
Showing the raw weighted contribution as the final course grade would be misleading because half of the course is missing.
That is why validating the total weight is important.
What If the Weights Do Not Total 100% Yet?
Sometimes a student only wants to calculate performance across completed categories.
In that case, we can normalize the grade instead of requiring 100%.
The normalized formula is:
Normalized Grade =
(Weighted Contribution / Total Weight) × 100
For example:
Homework: 90% × 20%
Projects: 80% × 30%
Weighted contribution:
18 + 24 = 42
Total weight:
50
Normalized grade:
42 / 50 × 100 = 84%
We could implement that like this:
const normalizedGrade =
(weightedTotal / totalWeight) * 100;
Whether you should normalize depends on the purpose of the calculator.
For a full-course grade, requiring 100% is usually clearer.
For an in-progress grade, normalization can be useful.
Handling Extra Credit
Some courses allow scores above 100%.
For example:
Project score: 105%
Project weight: 20%
Its contribution would be:
105 × 20 / 100 = 21
That is mathematically valid, so it is usually better not to block scores above 100%.
Instead, only reject negative values.
Adding More Grade Rows
A useful improvement is allowing users to add as many categories as they need.
You could add a button:
<button id="addRow">Add Category</button>
Then create new rows dynamically with JavaScript.
const addRowButton = document.getElementById("addRow");
const calculator = document.querySelector(".calculator");
addRowButton.addEventListener("click", function () {
const row = document.createElement("div");
row.className = "grade-row";
row.innerHTML = `
<input type="number" class="score" placeholder="Score %" min="0" step="any" />
<input type="number" class="weight" placeholder="Weight %" min="0" step="any" />
`;
calculator.insertBefore(row, calculateButton);
});
If you use dynamic rows, query the inputs inside the calculate event so newly created fields are included.
Displaying Each Category's Contribution
Another useful feature is showing how much each category adds to the final grade.
For example:
Homework contribution: 18.40%
Projects contribution: 26.40%
Final Exam contribution: 42.00%
This makes the result easier to understand and helps students see which categories have the biggest impact.
Testing the Calculator
Testing a calculator with known values is important.
A useful test case is:
Score 1: 92
Weight 1: 20
Score 2: 88
Weight 2: 30
Score 3: 84
Weight 3: 50
Expected result:
86.80%
If your calculator returns something different, check:
Whether weights were divided by 100
Whether the correct score is paired with the correct weight
Whether values were converted from strings to numbers
Whether total weight validation is working
When I test weighted-grade logic, I also compare results with a working implementation such as the Easy Grade Calculator weighted grade tool to catch formula or input-handling mistakes.
That gives a quick second check that the browser implementation is behaving as expected.
Complete JavaScript Example
Here is a complete version for three fixed categories:
const calculateButton =
document.getElementById("calculate");
const result =
document.getElementById("result");
calculateButton.addEventListener("click", function () {
const scoreInputs =
document.querySelectorAll(".score");
const weightInputs =
document.querySelectorAll(".weight");
let weightedTotal = 0;
let totalWeight = 0;
for (let i = 0; i < scoreInputs.length; i++) {
const score = Number(scoreInputs[i].value);
const weight = Number(weightInputs[i].value);
if (!Number.isFinite(score) || score < 0) {
result.textContent = "Please enter valid scores.";
return;
}
if (!Number.isFinite(weight) || weight < 0) {
result.textContent = "Please enter valid weights.";
return;
}
weightedTotal += (score * weight) / 100;
totalWeight += weight;
}
if (totalWeight === 0) {
result.textContent =
"Total weight must be greater than zero.";
return;
}
if (Math.abs(totalWeight - 100) > 0.01) {
result.textContent =
`Weights currently total ${totalWeight.toFixed(2)}%. Please make them total 100%.`;
return;
}
result.innerHTML = `
<strong>${weightedTotal.toFixed(2)}%</strong>
<br>
Final weighted grade
`;
});
Final Thoughts
A weighted grade calculator is a useful JavaScript project because it combines simple maths with practical front-end development.
It teaches you how to:
Work with multiple input fields
Loop through related data
Convert form values to numbers
Validate totals
Handle missing or invalid values
Update the DOM with useful results
Think about real-world edge cases such as extra credit
The core calculation is simple:
const contribution =
(score * weight) / 100;
The interesting part is everything around that formula.
Once the basic version works, you can extend it with dynamic rows, category names, saved results, letter grades, reset controls, or an in-progress normalized grade mode.
