Master TypeScript's fundamental type system including primitive types, arrays, tuples, and type annotations. Learn how to write type-safe code that prevents common programming errors.
TypeScript's type system is the foundation that makes your code more reliable, maintainable, and self-documenting. While JavaScript uses dynamic typing (types determined at runtime), TypeScript adds static typing (types checked at compile time).
In this lesson, you'll master TypeScript's fundamental types - from simple primitives like strings and numbers to complex structures like arrays and objects. You'll learn how proper type annotations can prevent bugs before they happen and make your code more expressive and easier to understand.
Think of types as contracts in your code - they specify what kind of data functions expect and what they return, making your intentions clear to both the TypeScript compiler and other developers reading your code.
TypeScript includes all JavaScript primitive types plus some additional ones for better type safety.
String Type:
1// Explicit type annotation
2let userName: string = "John Doe";
3let email: string = '[email protected]';
4let template: string = `Hello, ${userName}!`;
5
6// Template literals maintain string type
7let message: string = `User ${userName} has email ${email}`;
8
9// String methods are type-safe
10let upperName: string = userName.toUpperCase();
11let nameLength: number = userName.length; // TypeScript knows .length returns numberNumber Type:
1// All numbers in TypeScript are floating point
2let age: number = 30;
3let price: number = 19.99;
4let hexValue: number = 0xFF; // 255
5let binaryValue: number = 0b1010; // 10
6let octalValue: number = 0o744; // 484
7
8// Special number values
9let infinity: number = Infinity;
10let notANumber: number = NaN;
11
12// Math operations are type-safe
13let total: number = price * 2;
14let discountedPrice: number = price * 0.9;Boolean Type:
1let isActive: boolean = true;
2let isCompleted: boolean = false;
3
4// Derived from expressions
5let hasPermission: boolean = age >= 18;
6let isValidEmail: boolean = email.includes('@');
7
8// Common in conditional logic
9if (isActive && hasPermission) {
10 console.log("User can proceed");
11}Null and Undefined:
1// Explicit null and undefined
2let data: null = null;
3let result: undefined = undefined;
4
5// More commonly used with union types
6let optionalValue: string | null = null;
7let maybeNumber: number | undefined = undefined;
8
9// With strict null checks enabled
10let safeName: string = userName || "Anonymous";Symbol Type (ES6+):
1// Unique identifiers
2let id: symbol = Symbol("id");
3let anotherId: symbol = Symbol("id");
4
5// Symbols are always unique
6console.log(id === anotherId); // false
7
8// Used for object properties
9const SECRET_KEY: symbol = Symbol("secret");
10let config = {
11 [SECRET_KEY]: "super-secret-value"
12};BigInt Type (ES2020+):
1// For large integers beyond Number.MAX_SAFE_INTEGER
2let bigNumber: bigint = 9007199254740991n;
3let anotherBig: bigint = BigInt(9007199254740991);
4
5// BigInt operations
6let sum: bigint = bigNumber + anotherBig;Arrays and tuples provide type-safe ways to work with collections of data.
Array Types:
1// Two ways to define array types
2let numbers: number[] = [1, 2, 3, 4, 5];
3let names: Array<string> = ["Alice", "Bob", "Charlie"];
4
5// Mixed type arrays using union types
6let mixedData: (string | number)[] = ["John", 30, "Engineer", 75000];
7
8// Array methods are type-safe
9let doubled: number[] = numbers.map(n => n * 2);
10let adults: string[] = names.filter(name => name.length > 4);
11
12// Multi-dimensional arrays
13let matrix: number[][] = [
14 [1, 2, 3],
15 [4, 5, 6],
16 [7, 8, 9]
17];
18
19// Array with object elements
20interface User {
21 name: string;
22 age: number;
23}
24
25let users: User[] = [
26 { name: "Alice", age: 25 },
27 { name: "Bob", age: 30 }
28];Tuples - Fixed Length Arrays:
1// Tuple with exact types at specific positions
2let person: [string, number] = ["John", 30];
3
4// Accessing tuple elements
5let name: string = person[0]; // string
6let age: number = person[1]; // number
7
8// Tuple with optional elements
9let coordinate: [number, number, number?] = [10, 20]; // z is optional
10coordinate = [10, 20, 30]; // Also valid
11
12// Named tuple elements (TypeScript 4.0+)
13let employee: [name: string, age: number, department: string] =
14 ["Alice", 25, "Engineering"];
15
16// Rest elements in tuples
17let scores: [string, ...number[]] = ["Math", 95, 87, 92, 88];
18
19// Tuple methods
20person.push("Engineer"); // Allowed, but type becomes string | number
21let [personName, personAge] = person; // Destructuring with typesArray vs Tuple Use Cases:
1// Use arrays for homogeneous data
2let productPrices: number[] = [19.99, 29.99, 39.99];
3
4// Use tuples for heterogeneous, structured data
5let databaseRecord: [number, string, boolean] = [1, "John", true];
6
7// Function returning tuple
8function getUserInfo(): [string, number, string] {
9 return ["John Doe", 30, "[email protected]"];
10}
11
12// Destructuring the return value
13let [userName, userAge, userEmail] = getUserInfo();Readonly Arrays and Tuples:
1// Immutable arrays
2let readonlyNumbers: readonly number[] = [1, 2, 3, 4, 5];
3// readonlyNumbers.push(6); // Error: Property 'push' does not exist
4
5// Readonly tuples
6let readonlyPerson: readonly [string, number] = ["John", 30];
7// readonlyPerson[0] = "Jane"; // Error: Cannot assign to read only property
8
9// Using ReadonlyArray utility type
10let immutableList: ReadonlyArray<string> = ["a", "b", "c"];Union types and type aliases provide powerful ways to create flexible and reusable type definitions.
Union Types:
1// Basic union types
2let id: string | number = "user123";
3id = 42; // Also valid
4
5// Function with union type parameters
6function formatId(id: string | number): string {
7 if (typeof id === "string") {
8 return id.toUpperCase();
9 } else {
10 return `ID-${id}`;
11 }
12}
13
14// Union with multiple types
15let status: "pending" | "approved" | "rejected" = "pending";
16status = "approved"; // Valid
17// status = "unknown"; // Error: Type '"unknown"' is not assignable
18
19// Arrays with union types
20let mixedArray: (string | number | boolean)[] = ["text", 42, true];
21
22// Union with null/undefined
23let optionalName: string | null | undefined = null;
24
25// Complex union types
26type ApiResponse =
27 | { success: true; data: any }
28 | { success: false; error: string };
29
30function handleResponse(response: ApiResponse) {
31 if (response.success) {
32 console.log(response.data); // TypeScript knows this is available
33 } else {
34 console.error(response.error); // TypeScript knows this is available
35 }
36}Type Aliases:
1// Basic type aliases
2type UserID = string | number;
3type UserName = string;
4
5let currentUserId: UserID = "user123";
6let currentUserName: UserName = "John Doe";
7
8// Complex type aliases
9type User = {
10 id: UserID;
11 name: UserName;
12 email: string;
13 age: number;
14 isActive: boolean;
15};
16
17// Function type aliases
18type EventHandler = (event: string, data: any) => void;
19type Calculator = (a: number, b: number) => number;
20
21let add: Calculator = (a, b) => a + b;
22let multiply: Calculator = (a, b) => a * b;
23
24// Generic type aliases
25type ApiResult<T> = {
26 success: boolean;
27 data: T;
28 timestamp: Date;
29};
30
31type UserResult = ApiResult<User>;
32type ProductResult = ApiResult<{ name: string; price: number }>;
33
34// Union type aliases
35type Theme = "light" | "dark" | "auto";
36type Size = "small" | "medium" | "large";
37type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
38
39// Complex nested type aliases
40type DatabaseEntity = {
41 id: string;
42 createdAt: Date;
43 updatedAt: Date;
44};
45
46type Product = DatabaseEntity & {
47 name: string;
48 price: number;
49 category: string;
50};
51
52type Order = DatabaseEntity & {
53 userId: string;
54 products: Product[];
55 total: number;
56 status: "pending" | "processing" | "shipped" | "delivered";
57};Type Guards with Union Types:
1// Type guards help narrow union types
2function processValue(value: string | number | boolean) {
3 if (typeof value === "string") {
4 // TypeScript knows value is string here
5 console.log(value.toUpperCase());
6 } else if (typeof value === "number") {
7 // TypeScript knows value is number here
8 console.log(value.toFixed(2));
9 } else {
10 // TypeScript knows value is boolean here
11 console.log(value ? "Yes" : "No");
12 }
13}
14
15// Custom type guards
16type Fish = { swim: () => void };
17type Bird = { fly: () => void };
18
19function isFish(pet: Fish | Bird): pet is Fish {
20 return (pet as Fish).swim !== undefined;
21}
22
23function petAction(pet: Fish | Bird) {
24 if (isFish(pet)) {
25 pet.swim(); // TypeScript knows pet is Fish
26 } else {
27 pet.fly(); // TypeScript knows pet is Bird
28 }
29}TypeScript can often infer types automatically, but sometimes you need to provide explicit type information.
Type Inference:
1// TypeScript infers types automatically
2let message = "Hello, World!"; // inferred as string
3let count = 42; // inferred as number
4let isReady = true; // inferred as boolean
5
6// Function return type inference
7function add(a: number, b: number) {
8 return a + b; // return type inferred as number
9}
10
11// Array type inference
12let fruits = ["apple", "banana", "orange"]; // inferred as string[]
13let mixed = [1, "two", true]; // inferred as (string | number | boolean)[]
14
15// Object type inference
16let user = {
17 name: "John",
18 age: 30,
19 email: "[email protected]"
20}; // TypeScript infers the complete object shape
21
22// Context-based inference
23window.addEventListener("click", (event) => {
24 // event is inferred as MouseEvent
25 console.log(event.clientX, event.clientY);
26});Type Assertions:
1// When you know more than TypeScript
2let someValue: unknown = "Hello, TypeScript!";
3
4// Two syntax forms for type assertions
5let strLength1: number = (someValue as string).length;
6let strLength2: number = (<string>someValue).length; // JSX-incompatible
7
8// Common use case: DOM elements
9let inputElement = document.getElementById("myInput") as HTMLInputElement;
10inputElement.value = "New value"; // Now TypeScript knows it's an input
11
12// Type assertions with interfaces
13interface User {
14 name: string;
15 age: number;
16}
17
18let userData: unknown = { name: "John", age: 30 };
19let user = userData as User;
20console.log(user.name); // TypeScript trusts your assertion
21
22// Non-null assertion operator
23let maybeString: string | null = getValue();
24let definiteString: string = maybeString!; // Assert it's not null
25
26// Double assertion (use sparingly)
27let num = 42;
28let str = (num as unknown) as string; // Force TypeScript to accept thisWhen to Use Type Assertions:
1// 1. Working with DOM APIs
2function setupButton() {
3 const button = document.querySelector('.submit-btn') as HTMLButtonElement;
4 button.disabled = false;
5}
6
7// 2. Working with external libraries
8declare var $: any; // jQuery
9let element = $('#myElement') as JQuery;
10
11// 3. Migrating from JavaScript
12let legacyData: any = getLegacyData();
13let typedData = legacyData as { name: string; age: number };
14
15// 4. Working with JSON
16let jsonString = '{"name": "John", "age": 30}';
17let parsed = JSON.parse(jsonString) as User;
18
19// 5. Narrowing union types when you're certain
20function processData(data: string | number | boolean) {
21 if (typeof data !== "boolean") {
22 // You know it's string | number, assert to be more specific
23 let numeric = data as number;
24 return numeric * 2;
25 }
26}Best Practices:
1// ❌ Avoid unnecessary assertions
2let name: string = "John";
3let upperName = name as string; // Unnecessary
4
5// ✅ Let TypeScript infer when possible
6let inferredName = "John"; // TypeScript knows it's string
7
8// ❌ Don't override TypeScript's safety
9let num: number = 42;
10let str = num as any as string; // Dangerous
11
12// ✅ Use type guards instead
13function isString(value: unknown): value is string {
14 return typeof value === "string";
15}
16
17if (isString(someValue)) {
18 console.log(someValue.toUpperCase()); // Safe
19}
20
21// ✅ Use const assertions for literal types
22let theme = "dark" as const; // type: "dark", not string
23let config = {
24 apiUrl: "https://api.example.com",
25 timeout: 5000
26} as const; // readonly propertiesObjects are fundamental to JavaScript and TypeScript provides powerful ways to type them safely.
Object Type Annotations:
1// Inline object types
2let user: { name: string; age: number; email: string } = {
3 name: "John Doe",
4 age: 30,
5 email: "[email protected]"
6};
7
8// Optional properties
9let config: { theme: string; debug?: boolean } = {
10 theme: "dark"
11 // debug is optional
12};
13
14// Readonly properties
15let point: { readonly x: number; readonly y: number } = {
16 x: 10,
17 y: 20
18};
19// point.x = 30; // Error: Cannot assign to 'x' because it is a read-only property
20
21// Methods in object types
22let calculator: {
23 add: (a: number, b: number) => number;
24 subtract: (a: number, b: number) => number;
25} = {
26 add: (a, b) => a + b,
27 subtract: (a, b) => a - b
28};Interface Declarations:
1// Defining interfaces
2interface User {
3 id: string;
4 name: string;
5 email: string;
6 age: number;
7 isActive: boolean;
8}
9
10// Using interfaces
11let currentUser: User = {
12 id: "user123",
13 name: "John Doe",
14 email: "[email protected]",
15 age: 30,
16 isActive: true
17};
18
19// Interfaces with optional properties
20interface Product {
21 id: string;
22 name: string;
23 price: number;
24 description?: string; // Optional
25 tags?: string[]; // Optional
26}
27
28let product: Product = {
29 id: "prod123",
30 name: "Laptop",
31 price: 999.99
32 // description and tags are optional
33};Method Definitions in Interfaces:
1interface UserService {
2 // Method signature
3 getUser(id: string): User;
4 createUser(userData: Partial<User>): User;
5 updateUser(id: string, updates: Partial<User>): User;
6 deleteUser(id: string): boolean;
7
8 // Property with function type
9 onUserCreated: (user: User) => void;
10}
11
12// Implementation
13let userService: UserService = {
14 getUser(id: string): User {
15 // Implementation
16 return {} as User;
17 },
18
19 createUser(userData: Partial<User>): User {
20 // Implementation
21 return {} as User;
22 },
23
24 updateUser(id: string, updates: Partial<User>): User {
25 // Implementation
26 return {} as User;
27 },
28
29 deleteUser(id: string): boolean {
30 // Implementation
31 return true;
32 },
33
34 onUserCreated: (user: User) => {
35 console.log(`User ${user.name} created`);
36 }
37};Index Signatures:
1// For objects with dynamic properties
2interface StringDictionary {
3 [key: string]: string;
4}
5
6let translations: StringDictionary = {
7 hello: "Hola",
8 goodbye: "Adiós",
9 welcome: "Bienvenido"
10};
11
12// Mixed static and dynamic properties
13interface Config {
14 version: string;
15 debug: boolean;
16 [key: string]: any; // Allow additional properties
17}
18
19let appConfig: Config = {
20 version: "1.0.0",
21 debug: true,
22 theme: "dark",
23 timeout: 5000
24};
25
26// Numeric index signatures
27interface NumberArray {
28 [index: number]: number;
29}
30
31let fibonacci: NumberArray = [1, 1, 2, 3, 5, 8, 13];Extending Interfaces:
1// Base interface
2interface Person {
3 name: string;
4 age: number;
5}
6
7// Extended interface
8interface Employee extends Person {
9 employeeId: string;
10 department: string;
11 salary: number;
12}
13
14// Multiple inheritance
15interface ContactInfo {
16 email: string;
17 phone: string;
18}
19
20interface FullEmployee extends Person, ContactInfo {
21 employeeId: string;
22 department: string;
23}
24
25let employee: FullEmployee = {
26 name: "John Doe",
27 age: 30,
28 email: "[email protected]",
29 phone: "555-1234",
30 employeeId: "EMP001",
31 department: "Engineering"
32};Your progress is saved in this browser, on this device only.