Beginner

Operators in TypeScript

Learn arithmetic, comparison, logical, and assignment operators in TypeScript, plus modern operators like nullish coalescing and optional chaining

Operators are the symbols that let you combine values into expressions—adding numbers, comparing strings, or chaining conditions. TypeScript inherits its entire operator set from JavaScript, so the runtime behavior is identical. What TypeScript adds is type checking around operators: the compiler verifies that you are using operators on compatible types, catching mistakes like multiplying a string by an object before your code ever runs.

Because TypeScript is a multi-paradigm language with static, structural typing, operators serve a dual role. At runtime they do exactly what JavaScript does. At compile time, TypeScript uses operators to narrow types—for example, after a typeof x === "string" check, the compiler knows x is a string inside that branch. This makes operators part of TypeScript’s type-safety story, not just arithmetic.

In this tutorial you will work through arithmetic, comparison, logical, and assignment operators, then explore the modern operators that make TypeScript code concise and null-safe: nullish coalescing (??), optional chaining (?.), and logical assignment.

Arithmetic, Comparison, and Logical Operators

The everyday operators behave just as they do in most C-style languages. One thing to note: TypeScript has no separate integer type. All numbers are 64-bit floating point, so / always performs floating-point division.

Create a file named operators.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const a: number = 17;
const b: number = 5;

// Arithmetic operators
console.log("Arithmetic Operators:");
console.log(`a + b = ${a + b}`);
console.log(`a - b = ${a - b}`);
console.log(`a * b = ${a * b}`);
console.log(`a / b = ${a / b}`);   // floating-point division
console.log(`a % b = ${a % b}`);   // remainder
console.log(`a ** b = ${a ** b}`); // exponentiation

// Comparison operators (use === and !== for strict checks)
console.log("\nComparison Operators:");
console.log(`a > b: ${a > b}`);
console.log(`a < b: ${a < b}`);
console.log(`a >= b: ${a >= b}`);
console.log(`a === 17: ${a === 17}`);
console.log(`a !== b: ${a !== b}`);

// Logical operators
const isLoggedIn: boolean = true;
const isAdmin: boolean = false;
console.log("\nLogical Operators:");
console.log(`AND: ${isLoggedIn && isAdmin}`);
console.log(`OR: ${isLoggedIn || isAdmin}`);
console.log(`NOT: ${!isAdmin}`);

// Assignment operators mutate a let binding
let count: number = 10;
console.log("\nAssignment Operators:");
count += 5;
console.log(`After += 5: ${count}`);
count -= 3;
console.log(`After -= 3: ${count}`);
count *= 2;
console.log(`After *= 2: ${count}`);
count **= 2;
console.log(`After **= 2: ${count}`);

// String concatenation with +
const first: string = "Type";
const second: string = "Script";
console.log("\nString Concatenation:");
console.log(`${first} + ${second} = ${first + second}`);

// Operator precedence: * binds tighter than +
console.log("\nOperator Precedence:");
console.log(`2 + 3 * 4 = ${2 + 3 * 4}`);
console.log(`(2 + 3) * 4 = ${(2 + 3) * 4}`);

// Ternary conditional operator
const age: number = 20;
const status: string = age >= 18 ? "adult" : "minor";
console.log("\nTernary:");
console.log(`Status: ${status}`);

A few TypeScript-specific notes:

  • Always prefer === and !== (strict equality) over == and !=. The strict forms do not perform type coercion. In fact, TypeScript will raise a compile error if you use == to compare two values whose types cannot overlap.
  • ** is exponentiation, so 2 ** 10 is 1024. The **= assignment form raises a variable to a power in place.
  • + is overloaded: with numbers it adds, with strings it concatenates. TypeScript checks the operand types and infers the result type accordingly.

Modern Operators: Nullish Coalescing and Optional Chaining

TypeScript shines when dealing with values that might be null or undefined. These modern operators are where typed JavaScript becomes safer and more expressive than older patterns.

Create a file named modern_operators.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Nullish coalescing (??): fall back ONLY when the left side is null/undefined
const providedName: string | null = null;
const displayName: string = providedName ?? "Anonymous";
console.log("Nullish Coalescing:");
console.log(`Name: ${displayName}`);

// Contrast ?? with || — || also rejects 0, "", and false
const score: number = 0;
console.log(`With ||: ${score || 10}`); // 0 is falsy, so we get 10
console.log(`With ??: ${score ?? 10}`); // 0 is not nullish, so we keep 0

// Optional chaining (?.): safely read nested properties
interface User {
    profile?: {
        email?: string;
    };
}

const user: User = {};
const email: string = user.profile?.email ?? "no email";
console.log("\nOptional Chaining:");
console.log(`Email: ${email}`);

// Logical assignment: ??= assigns only if the target is null/undefined
const config: { theme?: string } = {};
config.theme ??= "dark";
console.log("\nLogical Assignment:");
console.log(`Theme: ${config.theme}`);

The key distinction:

  • ?? only treats null and undefined as “missing.” The older || operator treats any falsy value—0, "", false, NaN—as missing. When 0 or an empty string is a valid value, ?? is the correct choice.
  • ?. short-circuits to undefined the moment any link in the chain is null or undefined, avoiding “cannot read property of undefined” runtime errors.
  • ??= combines the two ideas: it assigns a default only when the existing value is null or undefined.

Running with Docker

Use the official Node.js image and run the examples with ts-node, which compiles and executes TypeScript in one step.

1
2
3
4
5
6
7
8
# Pull the official Node.js image
docker pull node:22-alpine

# Run the core operators example
docker run --rm -v $(pwd):/app -w /app node:22-alpine sh -c 'npx -y ts-node operators.ts'

# Run the modern operators example
docker run --rm -v $(pwd):/app -w /app node:22-alpine sh -c 'npx -y ts-node modern_operators.ts'

Expected Output

Running operators.ts produces:

Arithmetic Operators:
a + b = 22
a - b = 12
a * b = 85
a / b = 3.4
a % b = 2
a ** b = 1419857

Comparison Operators:
a > b: true
a < b: false
a >= b: true
a === 17: true
a !== b: true

Logical Operators:
AND: false
OR: true
NOT: true

Assignment Operators:
After += 5: 15
After -= 3: 12
After *= 2: 24
After **= 2: 576

String Concatenation:
Type + Script = TypeScript

Operator Precedence:
2 + 3 * 4 = 14
(2 + 3) * 4 = 20

Ternary:
Status: adult

Running modern_operators.ts produces:

Nullish Coalescing:
Name: Anonymous
With ||: 10
With ??: 0

Optional Chaining:
Email: no email

Logical Assignment:
Theme: dark

Key Concepts

  • No integer type: every number is a 64-bit float, so / is always floating-point division (17 / 5 is 3.4, not 3).
  • Use strict equality: === and !== avoid type coercion, and TypeScript flags comparisons between incompatible types at compile time.
  • ** for exponentiation: both the binary ** and the **= assignment form are available.
  • + is overloaded: it adds numbers and concatenates strings, with TypeScript inferring the result type from the operands.
  • ?? vs ||: nullish coalescing only falls back on null/undefined, while || falls back on any falsy value—choose ?? when 0 or "" are valid.
  • Optional chaining ?. prevents runtime crashes by short-circuiting to undefined on a missing link in a property chain.
  • Logical assignment (??=, &&=, ||=) combines a logical test with assignment to set defaults concisely.
  • Operators participate in type narrowing: checks like typeof x === "string" tell the compiler the type of x inside that branch, making operators part of TypeScript’s safety model.

Running Today

All examples can be run using Docker:

docker pull node:22-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining