TypeScript didn’t reinvent JavaScript’s arithmetic — it just gave you a seatbelt.
Under the hood, every +, -, *, /, and ** behaves exactly like in JavaScript, including all the quirky coercion rules that have launched a thousand memes. What TypeScript adds is the ability to catch those bugs at compile time instead of 3 a.m. when a user enters “banana” in a price field.
This guide covers every arithmetic operator in TypeScript with modern, real-world examples, in-depth explanations, common pitfalls, performance notes, and a cheat-sheet you’ll actually use.
1. Unary Operators
Unary Plus +
Purpose: Explicitly converts its operand to a number using the same algorithm as Number() but much faster.
+"42"; // 42
+"3.14"; // 3.14
+""; // 0 (empty string → 0)
+" 123 "; // 123 (trims whitespace!)
+"0xFF"; // 255 (recognizes hex)
+"abc"; // NaN
+true; // 1
+false; // 0
+null; // 0
+undefined; // NaN (compile error if strictNullChecks is on)
Why it exists: It’s the fastest way to coerce to number — V8 and SpiderMonkey optimize +x heavily.
Real-world use: High-performance parsers, CSV/number crunching loops, animation timing.
Gotcha: Leading/trailing whitespace is ignored, unlike parseInt.
Unary Minus -
Purpose: Negates the value after converting it to a number.
-"5"; // -5
-"-10"; // 10 (converts first, then negates)
-"9.99"; // -9.99
-" 7 "; // -7 (yes, whitespace is stripped)
Pro tip: -"10" becoming 10 is a classic trick in golfing and minified code.
Increment ++ & Decrement --
Two flavors:
- Prefix (
++x): increments first, then returns the new value - Postfix (
x++): returns the old value, then increments
let i = 5;
console.log(++i); // 6 → i is now 6
console.log(i++); // 6 → i is now 7 after this line
console.log(i); // 7
TypeScript protection (strict mode):
let s: string = "10";
s++; // Error: The operand of an increment or decrement operator must be a variable or property
This saves you from silently turning "10" into 11 at runtime.
2. Binary Arithmetic Operators
Addition + — The Most Dangerous Operator in JavaScript/TypeScript
Rule: If either operand is a string, the result is string concatenation. Otherwise, numeric addition.
1 + 2; // 3
"1" + 2; // "12"
1 + 2 + "3"; // "33" ← left-to-right associativity
1 + (2 + "3"); // "123" ← parentheses force order
true + false; // 1 (true→1, false→0)
null + 5; // 5
undefined + 5; // NaN (compile error with strictNullChecks)
Real bug example:
const items = 3;
const message = "You have " + items + " new messages";
// → "You have 3 new messages"
// But change the order:
const message2 = "You have " + (items + 1) + " new messages";
// → "You have 4 new messages"
// Accidentally:
const message3 = "Total: $" + price + tax;
// → "Total: $1000.0808" if tax = 0.08 → string concatenation!
Fix: Always wrap the math part:
"Total: $" + (price + tax)
Subtraction -, Multiplication *, Division /
These three always coerce both operands to numbers — no concatenation surprises.
"10" - "3"; // 7
"10" * 3; // 30
"100" / "4"; // 25
"10" - true; // 9 (true → 1)
null * 10; // 0
Division by zero behavior (never throws!):
1 / 0; // Infinity
-1 / 0; // -Infinity
0 / 0; // NaN
Why this matters: In games or financial code, silently returning Infinity can corrupt entire datasets.
Remainder (Modulo) %
Returns the remainder of division. The sign of the result matches the dividend (left operand).
10 % 3; // 1
-10 % 3; // -1 ← not 2!
-10 % -3; // -1
10 % -3; // 1
Useful trick for wrapping angles:
function wrapAngle(deg: number): number {
return ((deg % 360) + 360) % 360; // always positive
}
Exponentiation ** (ES2016+)
Right-associative (unlike most operators which are left-associative).
2 ** 3 ** 2; // 2 ** (3 ** 2) = 2 ** 9 = 512
// NOT (2 ** 3) ** 2 = 64
8 ** (1/3); // ≈2.080 (cube root)
16 ** 0.5; // 4 (square root)
Performance note: In modern engines, ** is faster than Math.pow().
3. Compound Assignment Operators
Shorthand that performs the operation and assigns in one step.
let score = 100;
score += 50; // 150
score -= 20; // 130
score *= 1.5; // 195
score /= 3; // 65
score %= 12; // 5
score **= 2; // 25
All follow the same coercion rules as their non-assignment versions.
4. BigInt Arithmetic
When you need exact integers beyond Number.MAX_SAFE_INTEGER (~9 quadrillion):
const huge = 1234567890123456789012345n;
const result = huge * 987654321n; // exact, no rounding
// Mixing types is forbidden → compile error
huge + 1; // Error
huge + BigInt(1); // OK
Conversion patterns:
BigInt("9007199254740993"); // beyond safe integer → correct
Number(9007199254740993n); // may lose precision
BigInt(42); // safe conversion from number
5. Special Values & Edge Cases You Will Encounter
| Operation | Result | Why It Happens |
|---|---|---|
NaN + 5 | NaN | NaN poisons nearly all operations |
Infinity - Infinity | NaN | Indeterminate form |
Infinity / Infinity | NaN | Indeterminate |
0 / 0 | NaN | Indeterminate |
Infinity * 0 | NaN | Indeterminate |
1 / -0 | -Infinity | Negative zero exists! |
Object.is(-0, 0) | false | Only reliable way to detect -0 |
0.1 + 0.2 === 0.3 | false | Floating-point precision |
6. TypeScript-Specific Defenses (Your Superpower)
// 1. Force the type
let quantity: number = userInput; // compile error if string
// 2. Safe conversion utility
function toSafeNumber(val: unknown): number {
if (typeof val === "number" && !Number.isNaN(val)) return val;
const num = Number(val);
if (Number.isNaN(num)) {
throw new Error(`Cannot convert "${val}" to number`);
}
return num;
}
// 3. Guarded operations
function safeDivide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
const result = a / b;
if (!Number.isFinite(result)) {
throw new Error("Result is infinite or NaN");
}
return result;
}
Quick-Reference Cheat Sheet
| Operator | Name | Example | Result | Key Notes / Gotchas |
|---|---|---|---|---|
+x | Unary plus | +"42" | 42 | Fastest coercion, trims whitespace |
-x | Unary minus | -"9" | -9 | Negates after coercion |
++x, x++ | Increment | Prefix returns new, postfix returns old | ||
--x, x-- | Decrement | Same prefix/postfix rules | ||
x + y | Addition | "2" + 3 | "23" | Concatenates if either is string → #1 bug source |
x - y | Subtraction | "10" - 3 | 7 | Always numeric |
x * y | Multiplication | 5 * null | 0 | Always numeric |
x / y | Division | 1 / 0 | Infinity | Never throws |
x % y | Remainder | -10 % 3 | -1 | Sign follows dividend |
x ** y | Exponentiation | 2 ** 10 | 1024 | Right-associative, faster than Math.pow() |
x += y etc. | Compound assignment | x *= 2 | Same coercion rules as standalone operators |
Summary – Your Actionable Takeaways
+is the only operator that concatenates — everything else forces numeric context.- Use unary
+for speed, explicitNumber()or validators for user input. - Never mix
bigintandnumber— TypeScript catches this at compile time. - Turn on
strict,noImplicitAny, andstrictNullChecks— they prevent 90% of arithmetic bugs. - Always test edge cases:
NaN,Infinity,-0, empty/whitespace strings, and malformed user input. - Wrap math in small, pure, well-tested functions — your future self will thank you.
Bookmark this guide, copy the cheat sheet into your team wiki, and say goodbye to “NaN avalanche” bugs forever.
Happy (and safe) calculating! 🚀
Found this useful? Share it or drop your worst arithmetic horror story in the comments below!