TypeScript Operators
last modified October 15, 2026
Operators are symbols that perform operations on values, called
operands. TypeScript has all the operators of JavaScript, with the same
behavior at runtime. What TypeScript adds is type checking: the compiler
knows the type of each operand and rejects operations that do not make sense,
such as subtracting a string. In addition, TypeScript has a few operators of
its own that work only on types, such as keyof.
Arithmetic Operators
The arithmetic operators are +, -,
*, /, % (remainder), and
** (exponentiation).
const a: number = 10; const b: number = 3; console.log(a + b, a - b, a * b); console.log(a / b); console.log(a % b, -a % b); console.log(a ** b, 2 ** 0.5); console.log(a / 0, 0 / 0);
Division always produces a floating-point result, so
10 / 3 is 3.3333333333333335. The remainder operator keeps the
sign of the left operand, so -10 % 3 is -1. The
** operator raises to a power; a fractional exponent such as 0.5
computes a square root.
Dividing by zero does not throw an error. It produces
Infinity, and 0 / 0 produces NaN
(not a number). Both are values of the number type.
$ npx tsx arithmetic_operators.ts 13 7 30 3.3333333333333335 1 -1 1000 1.4142135623730951 Infinity NaN
Increment and Decrement Operators
The ++ and -- operators add or subtract
one. They can be written before or after the variable.
let count = 5; console.log(count++); console.log(count); console.log(++count); console.log(count--, --count);
The postfix form count++ returns the old value and then
increments the variable, so the first line prints 5 and the variable becomes
6. The prefix form ++count increments first and returns the new
value, 7. In the last line, count-- returns 7 and leaves 6, and
--count then decrements to 5 and returns it. Because this
difference is easy to miss, the operators are best used as separate
statements.
$ npx tsx increment.ts 5 6 7 7 5
Assignment Operators
Compound assignment operators combine an operation with an assignment.
let num: number = 10; num += 5; num -= 3; num *= 2; num /= 4; num **= 2; num %= 7; console.log(num); let label = "Total"; label += ": 42"; console.log(label);
Each line is a shorter form of num = num op value. The
variable goes through 15, 12, 24, 6, and 36, and the remainder of
36 / 7 is 1. With strings, += appends text. The
compiler checks the result type: num += "5" would be an error,
because it would turn the number into a string.
$ npx tsx assignment_operators.ts 1 Total: 42
Logical Assignment Operators
The logical assignment operators ||=,
&&=, and ??= assign a value only under a
condition.
type Options = {
title?: string;
retries?: number;
verbose?: boolean;
};
const options: Options = { retries: 0, verbose: true };
options.title ??= "Untitled";
options.retries ??= 3;
options.verbose &&= false;
console.log(options);
let nickname = "";
nickname ||= "anonymous";
console.log(nickname);
The ??= operator assigns only if the property is
null or undefined, so the missing
title gets a default value while retries keeps 0.
The &&= operator assigns only if the current value is
truthy, and ||= only if it is falsy, which includes the empty
string.
$ npx tsx logical_assignment.ts
{ retries: 0, verbose: false, title: 'Untitled' }
anonymous
Comparison Operators
Comparison operators return a boolean. The strict
operators === and !== compare without type
conversion.
const x: number = 10; const y: number = 20; console.log(x === y, x !== y); console.log(x > y, x < y, x >= 10, y <= 10); const input: unknown = "10"; console.log(input == 10, input === 10); console.log(null == undefined, null === undefined); const notANumber = 0 / 0; console.log(notANumber === notANumber, Number.isNaN(notANumber));
The loose operator == converts its operands before
comparing them, so the string "10" equals the number 10. The
strict operator === also requires the same type. The only common
use of == is value == null, which matches both
null and undefined.
NaN is not equal to anything, not even to itself. It
is detected with Number.isNaN. Writing NaN === NaN
directly is rejected by the compiler, which knows that the comparison is
always false.
$ npx tsx comparison_operators.ts false true false true true false true false true false false true
Comparisons the Compiler Rejects
TypeScript reports comparisons between types that have no values in common, because they are always false.
const count: number = 5;
const text: string = "5";
if (count === text) {
console.log("equal");
}
A number can never be strictly equal to a string, so the condition
is a bug. In JavaScript, such code runs silently and never enters the
if block.
$ npx tsc --noEmit comparison_error.ts(4,5): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.
Logical Operators
The logical operators &&, ||, and
! combine conditions.
const isMember: boolean = true;
const hasCoupon: boolean = false;
const total: number = 80;
const freeShipping = isMember && total > 50;
const discount = isMember || hasCoupon;
console.log(freeShipping, discount, !hasCoupon);
function check(label: string, result: boolean): boolean {
console.log(`checking ${label}`);
return result;
}
console.log(check("a", false) && check("b", true));
console.log(check("c", true) || check("d", true));
The && operator is true when both operands are
true, || when at least one of them is, and !
negates a value.
The second part shows short-circuit evaluation. When the left
operand of && is false, the result is already known and
the right operand is not evaluated, so check("b") is never
called. The same happens for || when the left operand is true.
With non-boolean operands, the operators return one of the operands; see the
expressions tutorial.
$ npx tsx logical_operators.ts true true true checking a false checking c true
Ternary Operator
The conditional operator condition ? a : b chooses
between two values.
const age: number = 18; const category = age >= 18 ? "Adult" : "Minor"; const fee = age < 12 ? 0 : age < 65 ? 10 : 5; console.log(category, fee);
The type of category is
"Adult" | "Minor", the union of both branches. Ternary
operators can be nested, as in fee, but more than two levels are
hard to read.
$ npx tsx ternary_operator.ts Adult 10
typeof and instanceof
The typeof and instanceof operators come
from JavaScript and inspect values at runtime. TypeScript uses them to narrow
types.
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
bark(): string {
return `${this.name} barks`;
}
}
const values: unknown[] = ["text", 42, true, null, [1, 2], new Dog("Rex")];
for (const value of values) {
console.log(typeof value);
}
const pet: Animal = new Dog("Rex");
console.log(pet instanceof Dog, pet instanceof Animal);
if (pet instanceof Dog) {
console.log(pet.bark());
}
The typeof operator returns a string such as
"string" or "number". Arrays, objects, and
null all return "object". The
instanceof operator checks whether an object was created by a
class or its subclass, so the dog is an instance of both classes. Inside the
if block, pet is narrowed to Dog and
bark can be called.
$ npx tsx type_operators.ts string number boolean object object object true true Rex barks
Type Operators
TypeScript adds operators that work on types instead of values. In
a type position, typeof gives the type of a variable and
keyof gives the union of the keys of a type.
const config = {
host: "localhost",
port: 8080,
secure: false,
};
type Config = typeof config;
type ConfigKey = keyof Config;
function getSetting(key: ConfigKey): string {
return String(config[key]);
}
console.log(getSetting("host"), getSetting("port"));
// getSetting("timeout"); // Error: '"timeout"' is not assignable to type 'ConfigKey'
The Config type is
{ host: string; port: number; secure: boolean }, taken from the
object. ConfigKey is "host" | "port" | "secure", so
getSetting accepts only existing keys. These operators exist only
at compile time and leave no trace in the JavaScript output.
$ npx tsx type_level.ts localhost 8080
Bitwise Operators
Bitwise operators work on the binary representation of numbers, treated as 32-bit integers.
const a: number = 5; // 0101 const b: number = 3; // 0011 const bin = (n: number): string => (n >>> 0).toString(2).padStart(4, "0").slice(-4); console.log(a & b, bin(a & b)); console.log(a | b, bin(a | b)); console.log(a ^ b, bin(a ^ b)); console.log(~a, bin(~a)); console.log(a << 1, a >> 1); const READ = 1; const WRITE = 2; const EXECUTE = 4; const permissions = READ | WRITE; console.log((permissions & WRITE) !== 0, (permissions & EXECUTE) !== 0);
AND (&) keeps the bits set in both numbers, OR
(|) the bits set in either, and XOR (^) the bits set
in exactly one. NOT (~) inverts all 32 bits; the last four bits
of ~5 are 1010, and the value is -6. The shift
operators move the bits: 5 << 1 is 10 and
5 >> 1 is 2.
A practical use is a set of flags stored in one number: OR combines permissions and AND tests whether a permission is present.
$ npx tsx bitwise_operators.ts 1 0001 7 0111 6 0110 -6 1010 10 2 true false
Nullish Coalescing Operator
The ?? operator returns its right operand when the
left one is null or undefined.
function getTimeout(value: number | null | undefined): number {
return value ?? 1000;
}
console.log(getTimeout(500));
console.log(getTimeout(0));
console.log(getTimeout(null), getTimeout(undefined));
const port: number | undefined = 0 as number | undefined;
console.log(port || 8080, port ?? 8080);
Only missing values are replaced, so a timeout of 0 is kept. The
|| operator replaces every falsy value, which is why
port || 8080 returns 8080 although 0 is a valid port number in
this context. The return type of getTimeout is
number, because ?? removes null and
undefined from the type.
$ npx tsx nullish_coalescing.ts 500 0 1000 1000 8080 0
Optional Chaining Operator
The ?. operator accesses a property or calls a method
only if the value on its left is not null or
undefined.
type User = {
name: string;
address?: { city: string };
contact?: { phone: string };
greet?: () => string;
};
const user: User = {
name: "John",
address: { city: "New York" },
greet: () => "Hi, I'm John",
};
console.log(user.address?.city);
console.log(user.contact?.phone);
console.log(user.greet?.());
console.log(user.contact?.phone ?? "no phone");
Both address and contact are optional.
Since contact is missing, user.contact?.phone
evaluates to undefined instead of throwing a
TypeError. The ?.() form calls an optional method.
Optional chaining is often combined with ?? to provide a
default value.
$ npx tsx optional_chaining.ts New York undefined Hi, I'm John no phone
Operator Precedence
When an expression contains several operators, precedence decides which one is applied first.
console.log(2 + 3 * 4); console.log((2 + 3) * 4); console.log(2 ** 3 ** 2); console.log(10 - 4 - 3); const isAdmin: boolean = true; const isOwner: boolean = true; const isLocked: boolean = true; console.log(isAdmin || isOwner && !isLocked); console.log((isAdmin || isOwner) && !isLocked);
Multiplication binds tighter than addition, so the first line is 14.
Exponentiation is evaluated from right to left: 2 ** 3 ** 2 is
2 ** 9, which is 512. Subtraction is evaluated from left to
right. Among the logical operators, ! comes first, then
&&, then ||. The first condition is
therefore isAdmin || (isOwner && !isLocked), which is true
for an administrator, while the parenthesized version also requires the
resource to be unlocked and is false. Parentheses make the intended order
explicit and are recommended whenever operators are mixed.
$ npx tsx precedence.ts 14 20 512 3 true false
Best Practices
- Use Strict Equality: Prefer
===and!==; use== nullonly to test for bothnullandundefined. - Prefer ?? for Defaults: Use
??and??=when0,false, or""are valid values. - Use Parentheses: Make the order of evaluation explicit when mixing operators.
- Keep ++ and -- Simple: Use them as separate statements, not inside larger expressions.
- Leverage Type Operators: Derive types with
typeofandkeyofinstead of repeating them. - Use Bitwise Operators Only for Bits: They convert numbers to 32-bit integers and are rarely needed outside of flags and low-level code.
Source
MDN: Expressions and operators
This tutorial covered the operators available in TypeScript. They behave exactly as in JavaScript at runtime; TypeScript checks that their operands have suitable types.
Author
List all TypeScript tutorials.