A number annotation does not validate JSON. Narrow unknown input and check Number.isSafeInteger before using remainder.
A TypeScript number parameter can still contain a fraction, infinity, or an unsafe integer. It also cannot protect a running application from incorrectly shaped JSON. For a public boundary, accept unknown, validate it, and then perform the ordinary remainder check.
Validate unknown input
type OddResult =
| { ok: true; odd: boolean }
| { ok: false; error: string };
function checkOdd(value: unknown): OddResult {
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
return { ok: false, error: 'Expected a safe integer' };
}
return { ok: true, odd: value % 2 !== 0 };
}
const result = checkOdd(-13);
if (result.ok) {
console.log(result.odd); // true
} else {
console.error(result.error);
}
Understand narrowing and result states
The typeof test gives both the runtime and the compiler useful information. In the successful branch, the compiler knows that value is a number. The safe-integer check then restricts the accepted numeric values. TypeScript's narrowing guide describes how control flow uses these checks.
The result deliberately has two levels. ok: true, odd: false means that a valid integer was even. ok: false means the supplied value did not satisfy the contract. That distinction helps a form show an error without accidentally labeling an empty field, a boolean, or a misspelled value as even.
Do not replace the validation with value as number. An assertion changes what the compiler assumes; it does not transform the value or check it at runtime. The TypeScript handbook makes that behavior explicit. The same issue applies when an HTTP client exposes a generic response type without validating the actual response.
Use a smaller internal helper
For internal code that already handles invalid input through exceptions, a simpler signature is reasonable:
function isOdd(n: number): boolean {
if (!Number.isSafeInteger(n)) throw new TypeError('Expected a safe integer');
return n % 2 !== 0;
}
This second function still checks the numeric range because the number type includes more than integers. Neither version accepts a numeric string. Keep conversion beside the form or request parser so callers can see whether whitespace, signs, or decimal notation are permitted.
Check boundaries and extensions
Test successful values such as -13, -12, 0, and the largest safe integer. Test invalid values such as undefined, '13', 13.5, and NaN. Negative odd numbers are particularly useful: a mistaken === 1 comparison often passes positive-only examples.
If you later support BigInt, add a deliberate second implementation or overload with its own validation. Mixing Number and BigInt arithmetic is an error. A broad union type alone does not decide how each branch should work, and converting a large integer through Number can destroy the information you need.
When validating an object from a request, check the outer object's shape before passing its count field to this helper. A sound field validator cannot by itself establish that the surrounding record exists.