ZetCode

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).

arithmetic_operators.ts
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.

increment.ts
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.

assignment_operators.ts
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.

logical_assignment.ts
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.

comparison_operators.ts
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.

comparison_error.ts
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.

logical_operators.ts
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.

ternary_operator.ts
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.

type_operators.ts
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.

type_level.ts
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.

bitwise_operators.ts
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.

nullish_coalescing.ts
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.

optional_chaining.ts
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.

precedence.ts
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

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.