Not every unusual input is invalid
An empty grade list can mean a student has not been assessed. A grade of -1 violates the scale. Treating both as an average of zero invents information. For this lesson: an empty valid list returns null; malformed input throws.
function calculateAverage(grades) {
if (!Array.isArray(grades)) throw new Error("Grades must be an array");
if (!grades.every((grade) => Number.isFinite(grade) && grade >= 0 && grade <= 20)) {
throw new Error("Each grade must be between 0 and 20");
}
if (grades.length === 0) return null;
const sum = grades.reduce((total, grade) => total + grade, 0);
return sum / grades.length;
}
console.assert(calculateAverage([]) === null);
console.assert(calculateAverage([0, 20]) === 10);
Array.isArray checks the container. Every checks its entries. The empty-list branch occurs before division. JavaScript division by zero can produce Infinity or NaN instead of throwing, so try/catch alone is not sufficient.
Build a case matrix
| Input | Meaning | Expected behavior |
|---|---|---|
| [12, 16] | Normal valid data | Return 14 |
| [0, 20] | Valid scale boundaries | Return 10 |
| [] | No assessment yet | Return null |
| null or undefined | Missing required collection | Throw |
| [-1] or [21] | Outside the scale | Throw |
| ["12"] | Wrong entry type | Throw |
Independent exercise
A business asks to ignore invalid grades instead. What changes beyond the code? Would silently filtering them out be enough?
Correction
The policy changes the meaning of the average. Specify how rejected entries are reported and what happens when none remain. A filtered average without a rejection count can conceal data-quality problems. Keep the stricter rule until the alternative is explicitly approved for the exercise.
Review
Null, zero, empty array and error are different outcomes. Use a small vocabulary of explicit outcomes and test each one. A missing object property should receive the same careful policy decision, not an automatic zero.