In TypeScript, the type of a variable is denoted using :<type> annotation where <type> is any valid data type. Unlike programming languages like C and C++ in which the type of a variable is declared before the variable name like int a or void someFunc(), in TypeScript, the type annotation comes after the variable name, also known as the postfix type notation.
let fruit: string = "Mango";
In this program, :string signifies that the variable fruit contains string data. If you are claustrophobic, you can add space between the colon and the data type, like let fruit: string which is generally preferred.
// intro.ts
// variable declarations
let person: string;
let age: number = null;
let car: string = "Mercedes";
let canDrive: boolean = false;
console.log(
`before => (person:${person}), (age:${age}), (car:${car}), (canDrive:${canDrive})`,
);
// override values
person = "Ross Geller";
age = 21;
car = undefined;
canDrive = null;
console.log(
`before => (person:${person}), (age:${age}), (car:${car}), (canDrive:${canDrive})`,
);
$ ts-node intro.ts
before => (person:undefined), (age:null), (car:Mercedes), (canDrive:false)
before => (person:Ross Geller), (age:21), (car:undefined), (canDrive:null)
In this example, we have defined a few variables. Some of these variables have an initial value such as age, car and canDrive while others such as person do not.
TypeScript allows undefined or null as the value for a variable even though its type might say something else. For example, person variable can hold only string data but its value is undefined at the beginning. Similarly, age can hold only number data but it was initialized with the null value.
You can also override a variable with undefined or null value after it declared at any given moment. TypeScript considers undefined and null as the Null values or empty values. Though null and undefined are types in itself as we will learn in a bit, TypeScript doesn’t allow a variable to be Null (empty) by default.
To disable this behavior, we need to set strictNullChecks compiler-option to true. This will instruct the TypeScript compiler to disallow variables to hold undefined or null value. In this mode, the variable must hold a value of its type before consumption.
// tsconfig.json
{
"files": ["./intro.ts"],
"compilerOptions": {
"strictNullChecks": true
}
}
$ tsc
intro.ts:3:5 - error TS2322: Type 'null' is not assignable to type 'number'.
3 let age: number = null;
~~~
intro.ts:6:35 - error TS2454: Variable 'person' is used before being assigned.
6 console.log(`before => (person:${person}), (age:${age}), (car:${car}), (canDrive:${canDrive})`);
~~~~~~
intro.ts:11:1 - error TS2322: Type 'undefined' is not assignable to type 'string'.
11 car = undefined;
~~~
intro.ts:12:1 - error TS2322: Type 'null' is not assignable to type 'boolean'.
12 canDrive = null;
~~~~~~~~
Found 4 errors.
As you can see from this example, once we set strictNullChecks to true in the tsconfig.json, TypeScript doesn’t allow assignment of the null or the undefined with a variable that can’t contain null or undefined value.
TypeScript allows you to declare a variable without an initial value, which means its value is undefined for now. However, it expects you to assign a valid value before using it. If you use a variable before assignment, TypeScript reports Variable <x> is used before being assigned.
💡 You can learn more about the
tsconfig.jsonand compiler-options from the Compilation lesson.
// entities.ts
// variables & constants
const fullName: string = "Ross Geller";
var age: number;
let email: string;
// function declaration
function canVote(age: number): boolean {
return age >= 18;
}
// class declaration
class Person {
firstName: string;
lastName: string;
age: number;
email: string;
constructor(fn: string, ln: string) {
this.firstName = fn;
this.lastName = ln;
}
canVote(age: number): boolean {
return age >= 18;
}
}
// object declaration
let person: {
firstName: string;
lastName: string;
age: number;
email: string;
};
TypeScript allows us to add type annotations to any entities we like whether it’s a variable or a constant or a function or a class or a simple object. We can also configure types of the internal structure of these entities such as parameters and return value of a function or properties and methods of a class or properties of a plain object.
Built-in Data Types
In later lessons, we will look at complex types such as interfaces, enums, classes, and more. For now, let’s focus on the basic types TypeScript provides out of the box.
Before we proceed, I want to point out a few things first. A primitive type is an individual type that cannot be broken down further. An abstract type is a type that is composed of primitive types. For example, Array<string> is an abstract type since it is an array composed of string primitive types.
💡 Do not compare TypeScript primitive types with JavaScript runtime-types since these are two different things. For example,
objecttype in TypeScript is primitive whileobjecttype in JavaScript is abstract.
boolean
The boolean type represents true and false values. It is one of the primitive data types in TypeScript. Its corresponding JavaScript primitive type is boolean and its JavaScript constructor is Boolean.
A value of type boolean can be presented using a literal notation or using the Boolean constructor.
// boolean.ts
let isActive: boolean = false;
console.log("isActive =>", isActive);
isActive = true;
console.log("isActive =>", isActive);
isActive = Boolean(0);
console.log("isActive =>", isActive);
console.log("typeof `isActive` at runtime =>", typeof isActive);
$ ts-node boolean.ts
isActive => false
isActive => true
isActive => false
typeof `isActive` at runtime => boolean
number
The number type represents all real numbers (integers and floating-point numbers). It is one of the primitive data types in TypeScript. Its corresponding JavaScript primitive type is number and its JavaScript constructor is Number.
A value of type number can be presented using a literal notation or using the Number constructor.
// number.ts
let size: number = 50;
console.log("size =>", size);
size = 60.5;
console.log("size =>", size);
size = Number("100");
console.log("size =>", size);
console.log("typeof `size` at runtime =>", typeof size);
$ ts-node number.ts
size => 50
size => 60.5
size => 100
typeof `size` at runtime => number
💡 You can also use the binary
0b, octal0oor the hexadecimal0xprefix to represent an integer value, for examplelet count: number = 0x65for the value of decimal101. These values are converted to the decimal numbers on the fly.
string
The string type represents textual data. It is one of the primitive data types in TypeScript. Its corresponding JavaScript primitive type is string and its JavaScript constructor is String.
A value of type string can be presented using a literal notation (_using single quotes, double quotes or _template string notation) or using the String constructor.
// string.ts
let fruit: string = "Mango";
console.log("fruit =>", fruit);
fruit = `${fruit} or Apple`;
console.log("fruit =>", fruit);
fruit = String(100);
console.log("fruit =>", fruit);
console.log("typeof `fruit` at runtime =>", typeof fruit);
$ ts-node string.ts
fruit => Mango
fruit => Mango or Apple
fruit => 100
typeof `fruit` at runtime => string
symbol
EcmaScript 2015 added the new JavaScript primitive type symbol in the JavaScript specifications just like string, number, boolean etc. However, unlike other primitive data types, a symbol does not have a literal form.
To create a symbol, we need to call the Symbol() function that always returns a new symbol. TypeScript added the symbol primitive type to represent JavaScript symbol values.
// symbol.ts
var symbol1: symbol = Symbol();
var symbol2 = Symbol();
console.log("symbol1 =>", symbol1);
console.log("typeof symbol1 =>", typeof symbol1);
console.log("symbol1 === symbol2 =>", symbol1 === symbol2);
var symbol3 = Symbol("name");
var symbol4 = Symbol("name");
console.log("symbol3 =>", symbol3);
console.log("symbol3 === symbol4 =>", symbol3 === symbol4);
var colorKey: symbol = Symbol("color");
var myCar = { model: "Audi Q5", [colorKey]: "Red" };
console.log("myCar =>", myCar);
console.log("myCar[colorKey] =>", myCar[colorKey]);
console.log("Object.keys(myCar) =>", Object.keys(myCar));
$ ts-node -O '{"target":"ES6"}' symbol.ts
symbol1 => Symbol()
typeof symbol1 => symbol
symbol1 === symbol2 => false
symbol3 => Symbol(name)
symbol3 === symbol4 => false
myCar => { model: 'Audi Q5', [Symbol(color)]: 'Red' }
myCar[colorKey] => Red
Object.keys(myCar) => [ 'model' ]
You can pass an optional argument as a label for the symbol in the Symbol() function call, however, this is only used for debugging purposes. Every Symbol() call, with or without label always produces a unique symbol value.
💡 If you do not want this behavior, use the
Symbol.for()method.
When we console.log the value of a symbol, it just prints the 'Symbol()' or 'Symbol(label)' but the internal implementation of the symbol is different that guarantees unique values.
Previously, we could only use string and number values as object keys, but JavaScript now supports symbol keys as well. Since a symbol cannot be represented in literal form, you need to use the symbol reference (colorKey in this example) to access the property value.
Object properties whose key type is symbol are not enumerable. So they won’t show up in for-in loop or Object.keys() values.
The reason we had to use
@ts-ignorecomment in this example is because of this bug in the TypeScript. To know more aboutsymboldata type in JavaScript, follow this MDN documentation.
undefined
The undefined type represents only the undefined value. It is one of the primitive data types in TypeScript. Its corresponding JavaScript primitive type is undefined. A value of type undefined can only be presented using literal notation since it doesn’t have a constructor.
// undefined.ts
let secret: undefined;
console.log("secret =>", secret);
secret = null;
console.log("secret =>", secret);
secret = undefined;
console.log("secret =>", secret);
console.log("typeof `secret` at runtime =>", typeof secret);
$ ts-node undefined.ts
secret => undefined
secret => null
secret => undefined
typeof `secret` at runtime => undefined
When strictNullChecks compiler-option is not set or its value if false, the TypeScript compiler allows a variable of type undefined to hold null value as well since they are interchangeable by default.
null
The null type represents only the null value. It is one of the primitive data types in TypeScript. Though it’s a JavaScript primitive value, its runtime type is object which is a bug in JavaScript. A value of type null can only be presented using literal notation since it doesn’t have a constructor.
// null.ts
let value: null;
console.log("value =>", value);
value = undefined;
console.log("value =>", value);
value = null;
console.log("value =>", value);
console.log("typeof `value` at runtime =>", typeof value);
$ ts-node null.ts
value => undefined
value => undefined
value => null
typeof `value` at runtime => object
When strictNullChecks compiler-option is not set or its value if false, the TypeScript compiler allows a variable of type null to hold undefined value as well since they are interchangeable by default.
You can define a variable of type undefined without an initial value and consume it since the initial value of a variable is undefined. However, you must initialize or assign the value of type null before using it just like any other type.
void
The void type represents a value without any type. It is one of the primitive data types in TypeScript. This type purely exists in the TypeScript realm since it does not represent an actual value.
This type is mainly used to represent a function return type when a function does not return any value. But as we know, even if a function doesn’t return anything, the default return value of a function is undefined, so we can store undefined in a variable of type void.
// void.ts
function sayHello(): void {
console.log("Hello World!");
}
let empty: void = undefined;
sayHello();
$ ts-node void.ts
Hello World!
never
The never type represents a value that never exists. It is one of the primitive data types in TypeScript. This type purely exists in the TypeScript realm since it does not represent an actual value.
Unlike void which represents the return value of a function without an explicit return statement, never represents the return value of a function that can never return a value as shown here.
// never.ts
function oops(): never {
throw new Error();
}
function blackHole(): never {
while (true) {}
}
💡 The comparison between the
voidand thenevertype is illustrated in the Type System lesson.
object
The object type represents the values of the non-primitive data types. Which means a value that is not number, string, boolean, symbol, null, or undefined is a valid object. It is one of the primitive data types in TypeScript. This type purely exists in the TypeScript realm since it doesn’t have a fixed data type at the runtime.
// object.ts
let obj: object = { name: "John Doe" };
obj = [1, 2, 3];
console.log("typeof obj =>", typeof obj); // object
obj = { age: 21 };
console.log("typeof obj =>", typeof obj); // object
obj = () => null;
console.log("typeof obj =>", typeof obj); // function
// illegal assignment with primitives
obj = "John Doe";
obj = 21;
obj = Symbol();
obj = true;
obj = undefined;
obj = null;
💡 In this example, the assignment of obj with undefined or null is accepted since the strictNullCheck compiler-option is not set.
any
The any type is a special type in TypeScript since it can represent virtually all possible values. It is one of the primitive data types in TypeScript. This type purely exists in the TypeScript realm since it doesn’t have a fixed data type at the runtime.
// any.ts
let value: any = null;
value = "Hello World";
console.log(value);
value = 1234;
console.log(value);
value = true;
console.log(value);
value = Symbol();
console.log(value);
value = undefined;
console.log(value);
value = null;
console.log(value);
value = { a: 1 };
console.log(value);
$ ts-node -O '{"target":"ES6"}' any.ts
Hello World
1234
true
Symbol()
undefined
null
{ a: 1 }
Since any can represent all the possible values (of any given type in TypeScript), it is called a top type or a supertype. So in nutshell, all types in TypeScript are a subset of any type.
unknown
The unknown type is another top type in TypeScript which acts exactly similar to any with a few minor differences. It is one of the primitive data types in TypeScript. This type purely exists in the TypeScript realm since it doesn’t have a fixed data type at the runtime.
// unknown.ts
let valueA: any = null;
let valueB: unknown = null;
valueA = "Hello World";
valueB = "Hello World";
let boolA: number = valueA;
let boolB: number = valueB;
valueA.toUpperCase();
valueB.toUpperCase();
valueA();
valueB();
$ tsc unknown.ts
unknown.ts:15:5 - error TS2322: Type 'unknown' is not assignable to type 'number'.
15 let boolB: number = valueB;
~~~~~
unknown.ts:19:8 - error TS2339: Property 'toUpperCase' does not exist on type 'unknown'.
19 valueB.toUpperCase();
~~~~~~~~~~~
unknown.ts:23:1 - error TS2349: This expression is not callable.
Type '{}' has no call signatures.
23 valueB();
~~~~~~
Found 3 errors.
In this example, we have declared the variable valueA of type any and valueB of type unknown. Since these are top types, they can present any values we throw at them. The unknown type differs from any type when it is assigned to a variable of definite type or when a property is accessed on it.
Since any can contain any value, the assignment of a value of the type any with a variable of type boolean if valid since the value could be boolean at runtime. This however is not possible with the value of unknown since it can’t be trusted by the TypeScript compiler.
Similarly, a property or a method can be accessed on a value of type any since it could be JavaScript object at runtime or it can be called like a function since it could be a function at runtime. This is not allowed with a value of the type unknown since its shape can’t be trusted by the TypeScript compiler.
💡 The comparison between the
anyand theunknowntype is illustrated in the Type System lesson.
Array
The Array type represents a list of items of a given data type. Since Array is composed of items of primitive or abstract types, it is an abstract type.
We can use :Type[] type annotation to represent an array that contains elements of Type type or the :Array<Type> generic notation for the same.
// array.ts
let fruits: string[] = ["Apple", "Mango", "Banana"];
fruits.push(123);
let lapTimes: Array<number> = [12.05, 13, 11.5];
lapTimes.push("orange");
let garbage: any[] = ["Mango", null, 12.5, undefined, true];
garbage.push(123);
garbage.push("orange");
$ tsc array.ts
array.ts:3:14 - error TS2345: Argument of type '123' is not assignable to parameter of type 'string'.
3 fruits.push(123);
~~~
array.ts:7:16 - error TS2345: Argument of type '"orange"' is not assignable to parameter of type 'number'.
7 lapTimes.push("orange");
~~~~~~~~
Found 2 errors.
As you can see from these results, once an array is defined with a specific type, you cannot add items to the array of the type other than the specified type.
Tuple
A tuple type represents an array of fixed length and whose elements have predefined data types. However, there is no tuple keyword in TypeScript to represent a tuple. We express a tuple by using an array of types.
// tuple.ts
let student: [string, number, boolean] = ["Ross Geller", 27, true];
student = ["Monica Geller", 25, false];
student[1] = 28;
student = [true, "Mike Doe", false];
student[1] = false;
$ tsc tuple.ts
tuple.ts:9:13 - error TS2322: Type 'true' is not assignable to type 'string'.
9 student = [true, "Mike Doe", false];
~~~~
tuple.ts:9:19 - error TS2322: Type 'string' is not assignable to type 'number'.
9 student = [true, "Mike Doe", false];
~~~~~~~~~~
tuple.ts:10:1 - error TS2322: Type 'false' is not assignable to type 'number'.
10 student[1] = false;
~~~~~~~~~~
Found 3 errors.
In this example, we create a tuple student of type [string, number, boolean]. This means student must be an array of length 3 with values in that exact order. If we assign a value that does not match, TypeScript reports a compilation error.