TypeScript Expressions
last modified October 15, 2026
An expression is a piece of code that produces a value:
10 + 5, name.length, or age >= 18.
Statements, such as if or for, perform actions;
expressions compute values. TypeScript uses the same expressions as
JavaScript, but it also determines the type of every expression at compile
time. This lets the compiler reject operations that make no sense, such as
multiplying a string, before the program runs.
Arithmetic Expressions
Arithmetic expressions combine numbers with the +,
-, *, /, %, and
** operators.
const sum = 10 + 5; const difference = 10 - 5; const product = 10 * 5; const quotient = 10 / 4; const remainder = 10 % 4; const power = 2 ** 10; console.log(sum, difference, product); console.log(quotient, remainder, power); console.log((10 + 5) * 2, 10 + 5 * 2);
All six results have the number type, which TypeScript
infers without any annotation. JavaScript has a single number type for
integers and floating-point values, so 10 / 4 is
2.5, not 2. The % operator returns the remainder of
a division and ** raises a number to a power.
The last line shows operator precedence: multiplication binds
tighter than addition, so 10 + 5 * 2 is 20. Parentheses change
the order of evaluation.
$ npx tsx arithmetic.ts 15 5 50 2.5 2 1024 30 20
Type Errors in Arithmetic
In JavaScript, arithmetic operators silently convert their operands
to numbers. TypeScript allows arithmetic only on numbers,
bigint values, and any.
const price = "10"; const quantity = 3; const total = price * quantity;
In plain JavaScript, "10" * 3 evaluates to 30, because
the string is converted to a number. Such implicit conversions hide bugs, so
TypeScript reports an error. The string must be converted explicitly, for
example with Number(price).
$ npx tsc --noEmit arithmetic_error.ts(4,15): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
String Concatenation
The + operator also joins strings. If one of the
operands is a string, the result is a string.
const firstName = "John";
const lastName = "Doe";
const fullName = firstName + " " + lastName;
console.log(fullName);
const items = 5;
const extra = 3;
console.log("Total: " + items + extra);
console.log("Total: " + (items + extra));
Unlike the other arithmetic operators, + with a string
and a number is valid in TypeScript: the number is converted to a string.
Expressions are evaluated from left to right, so in
"Total: " + items + extra the number 5 is first appended to the
string, and then 3 is appended to the result. The output is
Total: 53. Parentheses make the addition happen first. Template
literals, shown later, avoid this pitfall.
$ npx tsx string_concat.ts John Doe Total: 53 Total: 8
Comparison Expressions
Comparison expressions compare two values and produce a
boolean.
const a: number = 10;
const b: number = 20;
console.log(a < b, a >= b);
console.log(a === 10, a !== b);
console.log("apple" < "banana");
console.log(0.1 + 0.2 === 0.3);
The strict equality operators === and !==
compare values without type conversion and should be preferred to
== and !=. Strings are compared character by
character, so "apple" < "banana" is true.
The last comparison is false, because floating-point
numbers cannot represent 0.1 and 0.2 exactly. TypeScript also reports
comparisons that can never be true, for example comparing a
number with a string using
===.
$ npx tsx comparison.ts true false true true true false
Logical Expressions
The logical operators &&
(and), || (or), and !
(not) combine conditions.
const isAdult = true; const hasLicense = false; const canDrive = isAdult && hasLicense; const canVote = isAdult || hasLicense; const isMinor = !isAdult; console.log(canDrive, canVote, isMinor);
The && operator is true only if both operands
are true, || if at least one of them is, and !
negates a value. All three variables have the boolean
type.
$ npx tsx logical.ts false true false
Short-Circuit Evaluation
The && and || operators do not
always return a boolean. They return one of their operands and evaluate the
right operand only if it is needed.
function getNickname(name: string): string {
console.log(`getNickname called for ${name}`);
return name.slice(0, 3);
}
const userName: string = "";
const label = userName || "anonymous";
console.log(label);
function isLoggedIn(): boolean {
return false;
}
const nick = isLoggedIn() && getNickname("Alexander");
console.log(nick);
const count = 0;
console.log(count || 10, count ?? 10);
The || operator returns its first operand if it is
truthy, otherwise the second one. The empty string is falsy, so
label is "anonymous". The
&& operator returns its first operand if it is falsy;
because isLoggedIn() returns false,
getNickname is never called. TypeScript infers the type of
nick as false | string, reflecting both
possibilities.
The ?? operator is similar to ||, but it
falls back only for null and undefined. That is why
count || 10 is 10, while count ?? 10 keeps the
valid value 0.
$ npx tsx short_circuit.ts anonymous false 10 0
Conditional (Ternary) Expressions
The conditional operator condition ? a : b is an
expression form of if/else: it evaluates to
a if the condition is true and to b otherwise.
const age = 20; const category = age >= 18 ? "adult" : "minor"; console.log(category); const score = 73; const grade = score >= 90 ? "A" : score >= 75 ? "B" : score >= 60 ? "C" : "F"; console.log(grade); const limit = Math.random() > 2 ? "none" : 100; console.log(typeof limit);
Because the ternary operator is an expression, its result can be
assigned directly to a const variable. Conditional expressions
can be chained, but long chains quickly become hard to read; a
switch statement or a lookup table is often clearer.
When the two branches have different types, the type of the whole
expression is their union. The limit variable has the type
"none" | 100; with let instead of
const, it would be widened to string | number.
Since Math.random() is never greater than 2, the value is
always 100 here.
$ npx tsx ternary.ts adult C number
Template Literals
Template literals are strings delimited by backticks. They can embed
any expression inside ${...}.
const userName = "Alice";
const items = 3;
const price = 4.5;
const greeting = `Hello, ${userName}!`;
const summary = `${items} items cost ${(items * price).toFixed(2)} EUR`;
const cartStatus = `You have ${items > 0 ? items : "no"} items in your cart`;
console.log(greeting);
console.log(summary);
console.log(cartStatus);
const report = `Name: ${userName.toUpperCase()}
Items: ${items}`;
console.log(report);
Each ${...} placeholder is evaluated and its value is
converted to a string. The placeholders may contain arithmetic, method calls,
or even a conditional expression. Unlike strings in quotes, template literals
can span several lines; the line break is part of the string.
$ npx tsx template_literals.ts Hello, Alice! 3 items cost 13.50 EUR You have 3 items in your cart Name: ALICE Items: 3
Array Expressions
An array literal creates a new array. Array methods such as
map, filter, and reduce are
expressions that produce new values, so they can be chained.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
const evens = numbers.filter(num => num % 2 === 0);
const total = numbers.reduce((acc, num) => acc + num, 0);
console.log(doubled);
console.log(evens, total);
const sumOfSquares = numbers
.filter(num => num > 2)
.map(num => num ** 2)
.reduce((acc, num) => acc + num, 0);
console.log(sumOfSquares);
const more = [0, ...numbers, 6];
console.log(more);
TypeScript infers numbers as number[].
From that, it infers the type of num in each callback and the
result types: doubled and evens are
number[] and total is number. None of
the methods change the original array.
The chained expression keeps the numbers greater than 2, squares
them, and adds the squares: 9 + 16 + 25 = 50. The spread syntax
...numbers inserts the elements of an existing array into a new
array literal.
$ npx tsx array.ts [ 2, 4, 6, 8, 10 ] [ 2, 4 ] 15 50 [ 0, 1, 2, 3, 4, 5, 6 ]
Object Expressions
An object literal creates an object. TypeScript checks it against the declared type and infers the types of its properties.
type Person = {
name: string;
age: number;
};
const person: Person = {
name: "Bob",
age: 30,
};
const city = "Bratislava";
const key = "country";
const profile = {
...person,
city,
[key]: "Slovakia",
isAdult: person.age >= 18,
};
console.log(profile);
console.log(profile.country);
The person object must have exactly the properties of
the Person type; a missing property or a misspelled one, such as
agee, is a compile-time error.
The profile literal shows other expressions inside
objects. The spread syntax copies the properties of person. The
shorthand city means city: city. A computed key in
square brackets uses the value of an expression as the property name; since
key is the constant "country", TypeScript knows that
profile has a country property. Property values can
be any expressions, such as the comparison in isAdult.
$ npx tsx object.ts
{
name: 'Bob',
age: 30,
city: 'Bratislava',
country: 'Slovakia',
isAdult: true
}
Slovakia
Function Expressions
A function can be created inside an expression and assigned to a variable or passed to another function.
const add = function (a: number, b: number): number {
return a + b;
};
const multiply = (a: number, b: number) => a * b;
type Operation = (a: number, b: number) => number;
const subtract: Operation = (a, b) => a - b;
console.log(add(5, 10));
console.log(multiply(4, 6));
console.log(subtract(10, 3));
const result = ((x: number) => x * x)(7);
console.log(result);
The add variable holds a function expression; the
multiply variable holds an arrow function, a shorter form whose
return type is inferred as number.
In subtract, the parameters have no annotations.
TypeScript takes their types from the Operation type of the
variable; this is called contextual typing. The last example defines an arrow
function and calls it immediately, which is called an immediately invoked
function expression (IIFE).
$ npx tsx function_expression.ts 15 24 7 49
The typeof Type Operator
In a type position, the typeof operator gives the type
of an expression. This lets us derive a type from a value instead of writing
it twice.
const defaults = {
theme: "dark",
fontSize: 14,
autosave: true,
};
type Settings = typeof defaults;
function applySettings(settings: Settings): string {
return `${settings.theme}, ${settings.fontSize}px, autosave: ${settings.autosave}`;
}
console.log(applySettings(defaults));
console.log(applySettings({ ...defaults, fontSize: 16 }));
console.log(typeof defaults.fontSize);
The Settings type is
{ theme: string; fontSize: number; autosave: boolean }, taken
from the defaults object. When a property is added to
defaults, the type follows automatically.
The last line uses the JavaScript typeof operator,
which works at runtime and returns a string such as "number".
TypeScript decides which one is meant from the position: in a type
annotation or a type declaration, it is the type operator.
$ npx tsx typeof_expression.ts dark, 14px, autosave: true dark, 16px, autosave: true number
Type Assertions
A type assertion, written with as, tells the compiler
to treat an expression as a more specific type. It is useful when the
developer knows more about a value than the compiler, for example about
parsed JSON.
const input: unknown = JSON.parse("[10, 20, 30]");
const values = input as number[];
console.log(values.length, values[0] + values[1]);
const text: unknown = "123";
const wrong = text as number;
console.log(typeof wrong);
const converted = Number(text);
console.log(typeof converted, converted + 1);
JSON.parse cannot know the structure of the data. The
assertion as number[] makes the result usable as an array of
numbers. The compiler does not check that the assertion is true; if the JSON
contained something else, the program would fail later.
An assertion never converts a value. The wrong
variable has the type number, but at runtime it is still the
string "123", as the output of typeof shows. To get
a real number, the value must be converted, here with the
Number function. For values of unknown type, narrowing with
checks such as typeof is safer than an assertion; see the
unknown type tutorial.
$ npx tsx type_assertion.ts 3 30 string number 124
Best Practices
- Rely on Inference: Let TypeScript infer the types of simple expressions; add annotations where they document intent, such as function parameters and return types.
- Use Strict Equality: Compare with
===and!==to avoid implicit type conversion. - Prefer Template Literals: They are easier to read than chains of
+and avoid accidental string concatenation of numbers. - Choose ?? for Defaults: Use
??instead of||when0,false, or""are valid values. - Keep Expressions Readable: Split long chains of ternary operators or method calls into named intermediate values.
- Convert, Do Not Assert: A type assertion changes only the compile-time type; use
Number,String, or checks to change or verify the value.
Source
MDN: Expressions and operators
This tutorial covered TypeScript expressions. TypeScript uses the expressions of JavaScript unchanged and adds a compile-time type to each of them.
Author
List all TypeScript tutorials.