TypeScript Arithmetic Operators: The Definitive Guide

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

OperationResultWhy It Happens
NaN + 5NaNNaN poisons nearly all operations
Infinity - InfinityNaNIndeterminate form
Infinity / InfinityNaNIndeterminate
0 / 0NaNIndeterminate
Infinity * 0NaNIndeterminate
1 / -0-InfinityNegative zero exists!
Object.is(-0, 0)falseOnly reliable way to detect -0
0.1 + 0.2 === 0.3falseFloating-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

OperatorNameExampleResultKey Notes / Gotchas
+xUnary plus+"42"42Fastest coercion, trims whitespace
-xUnary minus-"9"-9Negates after coercion
++x, x++IncrementPrefix returns new, postfix returns old
--x, x--DecrementSame prefix/postfix rules
x + yAddition"2" + 3"23"Concatenates if either is string → #1 bug source
x - ySubtraction"10" - 37Always numeric
x * yMultiplication5 * null0Always numeric
x / yDivision1 / 0InfinityNever throws
x % yRemainder-10 % 3-1Sign follows dividend
x ** yExponentiation2 ** 101024Right-associative, faster than Math.pow()
x += y etc.Compound assignmentx *= 2Same coercion rules as standalone operators

Summary – Your Actionable Takeaways

  1. + is the only operator that concatenates — everything else forces numeric context.
  2. Use unary + for speed, explicit Number() or validators for user input.
  3. Never mix bigint and number — TypeScript catches this at compile time.
  4. Turn on strict, noImplicitAny, and strictNullChecks — they prevent 90% of arithmetic bugs.
  5. Always test edge cases: NaN, Infinity, -0, empty/whitespace strings, and malformed user input.
  6. 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!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.