Typescript Cheat Sheet


Beginners To Experts


The site is under development.

Typescript Cheat Sheet

1. Variables (let, const, var)

Syntax: let variable_name: type = value, const variable_name: type = value, var variable_name: type = value

In TypeScript, let and const are used for declaring variables, while var is also available (though generally not recommended due to scope issues).

let x: number = 10; // 'let' allows reassignment
const name: string = "Alice"; // 'const' does not allow reassignment
var isActive: boolean = true; // 'var' declares a variable with function-level scope
console.log(x, name, isActive); // Output: 10 Alice true
Output:
10 Alice true

2. Primitive Types (string, number, boolean, null, undefined, symbol, bigint)

Syntax: let variable_name: type

TypeScript provides various primitive types to define the type of a variable.

let num: number = 42; // number
let name: string = "John"; // string
let isActive: boolean = true; // boolean
let empty: null = null; // null
let notAssigned: undefined = undefined; // undefined
let uniqueId: symbol = Symbol("id"); // symbol
let largeNum: bigint = 100n; // bigint
console.log(typeof num, typeof name, typeof isActive, typeof empty, typeof notAssigned, typeof uniqueId, typeof largeNum); // Output: number string boolean object undefined symbol bigint
Output:
number string boolean object undefined symbol bigint

3. Type Annotations (let x: string = "hello")

Syntax: let variable_name: type = value

Type annotations allow you to specify the type of a variable explicitly in TypeScript.

let name: string = "Alice"; // 'name' is explicitly declared as a string
let age: number = 25; // 'age' is explicitly declared as a number
console.log(name, age); // Output: Alice 25
Output:
Alice 25

4. Type Inference (Auto-detected types)

Syntax: let variable_name = value

TypeScript can automatically infer the type of a variable based on the value assigned to it, so type annotations are not always required.

let name = "Alice"; // TypeScript infers that 'name' is a string
let age = 25; // TypeScript infers that 'age' is a number
console.log(typeof name, typeof age); // Output: string number
Output:
string number

5. Type Assertions (as or value)

Syntax: value as Type | value

Type assertions tell TypeScript to treat a value as a specific type. This is useful when you know more about the type than TypeScript can infer.

let someValue: any = "This is a string";
let strLength: number = (someValue as string).length; // Using 'as' for type assertion
console.log(strLength); // Output: 17

let anotherValue: any = "Hello, world!";
let strLengthAlt: number = (anotherValue).length; // Using '' for type assertion
console.log(strLengthAlt); // Output: 13
Output:
17
13

6. Template Literals (`Hello ${name}`)

Syntax: `Hello, ${name}!`

Template literals allow you to embed expressions inside string literals using the ${} syntax. They can span multiple lines and allow string interpolation.

let name: string = "Alice";
let greeting: string = `Hello, ${name}!`; // Using template literals
console.log(greeting); // Output: Hello, Alice!

let multiLineString: string = `This is a string that spans multiple lines.`;
console.log(multiLineString); // Output: This is a string
that spans multiple
lines.
Output:
Hello, Alice!
This is a string
that spans multiple
lines.

1. Objects ({ name: string; age: number })

Syntax: { key: type; key: type; }

In TypeScript, you can define an object with specific types for each property. This ensures that each property is of the expected type.

let person: { name: string; age: number } = { name: "Alice", age: 30 };
console.log(person); // Output: { name: "Alice", age: 30 }
console.log(person.name); // Output: Alice
console.log(person.age); // Output: 30
Output:
{ name: "Alice", age: 30 }
Alice
30

2. Arrays (number[], Array)

Syntax: number[] or Array

Arrays in TypeScript can be defined using either the array literal [] or the Array syntax. Both are valid ways to define arrays.

let nums: number[] = [1, 2, 3, 4, 5]; // Using array literal syntax
let names: Array = ["Alice", "Bob", "Charlie"]; // Using Array syntax
console.log(nums); // Output: [1, 2, 3, 4, 5]
console.log(names); // Output: ["Alice", "Bob", "Charlie"]
Output:
[1, 2, 3, 4, 5]
["Alice", "Bob", "Charlie"]

3. Tuples ([string, number])

Syntax: [type, type]

Tuples in TypeScript are arrays with a fixed number of elements, each of which has a specific type.

let person: [string, number] = ["Alice", 30]; // Tuple with a string and a number
console.log(person); // Output: ["Alice", 30]
console.log(person[0]); // Output: Alice
console.log(person[1]); // Output: 30
Output:
["Alice", 30]
Alice
30

4. Readonly (readonly string[])

Syntax: readonly type[]

Readonly arrays in TypeScript ensure that the array cannot be modified after its creation. Any attempt to modify it will result in a compile-time error.

let fruits: readonly string[] = ["Apple", "Banana", "Cherry"];
console.log(fruits); // Output: ["Apple", "Banana", "Cherry"]
// fruits.push("Orange"); // Error: Property 'push' does not exist on type 'readonly string[]'
Output:
["Apple", "Banana", "Cherry"]

5. Index Signatures ({ [key: string]: number })

Syntax: { [key: string]: type }

Index signatures allow objects to have dynamic keys with a specified type for the values. This is useful when you don’t know the exact keys ahead of time.

let scores: { [key: string]: number } = { "Alice": 90, "Bob": 85 };
console.log(scores); // Output: { Alice: 90, Bob: 85 }
console.log(scores["Alice"]); // Output: 90
console.log(scores["Bob"]); // Output: 85
Output:
{ Alice: 90, Bob: 85 }
90
85

1. Parameter Types ((x: number, y: number) => number)

Syntax: (x: type, y: type) => returnType

Function parameters can be typed in TypeScript to specify what type of value is expected. The return type is also specified after the arrow (=>).

const add = (x: number, y: number): number => {
return x + y;
};
console.log(add(3, 4)); // Output: 7
Output:
7

2. Optional Parameters (x?: number)

Syntax: x?: type

Optional parameters are marked with a ?, which makes it optional for the caller to provide a value for the parameter.

const greet = (name: string, age?: number): string => {
return age ? `Hello ${name}, you are ${age} years old.` : `Hello ${name}`;
};
console.log(greet("Alice", 25)); // Output: Hello Alice, you are 25 years old.
console.log(greet("Bob")); // Output: Hello Bob
Output:
Hello Alice, you are 25 years old.
Hello Bob

3. Default Parameters (x = 10)

Syntax: x = value

Default parameters allow a function to use a default value if no value is provided by the caller.

const multiply = (x: number = 10, y: number): number => {
return x * y;
};
console.log(multiply(5, 2)); // Output: 10
console.log(multiply(3)); // Output: 30
Output:
10
30

4. Rest Parameters (...args: number[])

Syntax: ...args: type[]

Rest parameters allow a function to accept an arbitrary number of arguments, which are stored in an array.

const sum = (...args: number[]): number => {
return args.reduce((acc, num) => acc + num, 0);
};
console.log(sum(1, 2, 3, 4)); // Output: 10
console.log(sum(5, 10)); // Output: 15
Output:
10
15

5. Return Type Annotations ((): string)

Syntax: (): returnType

Return type annotations are used to specify the type of value that a function returns. This helps TypeScript ensure that the function returns the correct type.

const getName = (): string => {
return "Alice";
};
console.log(getName()); // Output: Alice
Output:
Alice

6. Arrow Functions (const fn = () => {})

Syntax: const fn = () => {}

Arrow functions provide a shorter syntax for writing functions. They also retain the this context from the surrounding code.

const square = (x: number): number => {
return x * x;
};
console.log(square(4)); // Output: 16
Output:
16

1. Interfaces (interface User { name: string })

Syntax: interface InterfaceName { property: type }

Interfaces define the structure of an object, specifying the names and types of properties it should have.

interface User {
name: string;
age: number;
}
const user: User = {
name: "Alice",
age: 30,
};
console.log(user); // Output: { name: 'Alice', age: 30 }
Output:
{ name: 'Alice', age: 30 }

2. Type Aliases (type Point = { x: number; y: number })

Syntax: type TypeName = { property: type }

Type aliases are used to create a new name for a type, which can be an object, union, or any valid TypeScript type.

type Point = {
x: number;
y: number;
}
const point: Point = {
x: 5,
y: 10,
};
console.log(point); // Output: { x: 5, y: 10 }
Output:
{ x: 5, y: 10 }

3. Extending Interfaces (interface Admin extends User { role: string })

Syntax: interface Child extends Parent { property: type }

Interfaces can be extended using the extends keyword, allowing one interface to inherit properties from another.

interface User {
name: string;
age: number;
}
interface Admin extends User {
role: string;
}
const admin: Admin = {
name: "Bob",
age: 40,
role: "Manager",
};
console.log(admin); // Output: { name: 'Bob', age: 40, role: 'Manager' }
Output:
{ name: 'Bob', age: 40, role: 'Manager' }

4. Intersection Types (A & B)

Syntax: A & B

Intersection types allow combining multiple types into one. A value of this type must satisfy all types.

type Person = {
name: string;
age: number;
}
type Address = {
street: string;
city: string;
}
type PersonWithAddress = Person & Address;
const person: PersonWithAddress = {
name: "Charlie",
age: 35,
street: "123 Main St",
city: "New York",
};
console.log(person); // Output: { name: 'Charlie', age: 35, street: '123 Main St', city: 'New York' }
Output:
{ name: 'Charlie', age: 35, street: '123 Main St', city: 'New York' }

5. Union Types (string | number)

Syntax: typeA | typeB

Union types allow a variable to hold multiple types. A value of this type can be one of the specified types.

let value: string | number;
value = "Hello";
console.log(value); // Output: Hello
value = 42;
console.log(value); // Output: 42
Output:
Hello
42

6. Literal Types ("success" | "error")

Syntax: type "value1" | "value2"

Literal types are used to specify the exact value a variable can have, rather than just a type.

type Status = "success" | "error";
let status: Status;
status = "success";
console.log(status); // Output: success
status = "error";
console.log(status); // Output: error
Output:
success
error

1. Class Syntax (class Person { ... })

Syntax: class ClassName { ... }

Classes in TypeScript are blueprints for creating objects. A class can contain properties, methods, and a constructor.

class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const person = new Person("Alice");
person.greet(); // Output: Hello, Alice!
Output:
Hello, Alice!

2. Constructor (constructor(name: string) { ... })

Syntax: constructor(parameter: type) { ... }

The constructor is a special method used to initialize objects created from a class. It is called automatically when an instance is created.

class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Bob");
console.log(person.name); // Output: Bob
Output:
Bob

3. Properties (public, private, protected)

Syntax: public propertyName: type, private propertyName: type, protected propertyName: type

Properties in a class can have different access modifiers:

  • public: Accessible from anywhere.
  • private: Accessible only within the class.
  • protected: Accessible within the class and its subclasses.
class Person {
public name: string;
private age: number;
protected address: string;
constructor(name: string, age: number, address: string) {
this.name = name;
this.age = age;
this.address = address;
}
}
const person = new Person("Charlie", 30, "123 Main St");
console.log(person.name); // Output: Charlie
// console.log(person.age); // Error: Property 'age' is private
Output:
Charlie

4. Readonly Properties (readonly id: number)

Syntax: readonly propertyName: type

The readonly modifier ensures that the property cannot be reassigned after it is initialized.

class Product {
readonly id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
const product = new Product(1, "Laptop");
console.log(product.id); // Output: 1
// product.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
Output:
1

5. Getters/Setters (get name(): string)

Syntax: get propertyName(): type and set propertyName(value: type)

Getters and setters allow you to define custom behavior when accessing or modifying a property.

class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
const person = new Person("Dave");
console.log(person.name); // Output: Dave
person.name = "Eve";
console.log(person.name); // Output: Eve
Output:
Dave
Eve

6. Inheritance (extends)

Syntax: class Child extends Parent

Inheritance allows a class to inherit properties and methods from another class, known as the parent class.

class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog("Rex");
dog.speak(); // Output: Rex barks.
Output:
Rex barks.

7. Abstract Classes (abstract class Animal { ... })

Syntax: abstract class ClassName { ... }

Abstract classes cannot be instantiated directly and are used to define a base class that other classes can inherit from.

abstract class Animal {
abstract sound(): void;
}
class Dog extends Animal {
sound() {
console.log("Bark!");
}
}
const dog = new Dog();
dog.sound(); // Output: Bark!
// const animal = new Animal(); // Error: Cannot instantiate an abstract class
Output:
Bark!

8. Implements (class Car implements Vehicle)

Syntax: class ClassName implements InterfaceName

The implements keyword is used to ensure that a class follows the structure of an interface.

interface Vehicle {
start(): void;
stop(): void;
}
class Car implements Vehicle {
start() {
console.log("Car started.");
}
stop() {
console.log("Car stopped.");
}
}
const car = new Car();
car.start(); // Output: Car started.
car.stop(); // Output: Car stopped.
Output:
Car started.
Car stopped.

1. Numeric Enums (enum Direction { Up, Down })

Syntax: enum EnumName { member1, member2, ... }

Numeric enums allow you to define a set of named numeric values. By default, the first member starts at 0, and the rest are incremented by 1.

enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
console.log(Direction.Up); // Output: 0
console.log(Direction.Down); // Output: 1
console.log(Direction.Left); // Output: 2
console.log(Direction.Right); // Output: 3
Output:
0
1
2
3

2. String Enums (enum Status { Success = "SUCCESS" })

Syntax: enum EnumName { member1 = "value1", member2 = "value2" }

String enums allow you to define a set of named string values. Each member is assigned a specific string value.

enum Status {
Success = "SUCCESS",
Failure = "FAILURE",
Pending = "PENDING"
}
console.log(Status.Success); // Output: SUCCESS
console.log(Status.Failure); // Output: FAILURE
console.log(Status.Pending); // Output: PENDING
Output:
SUCCESS
FAILURE
PENDING

3. Heterogeneous Enums (enum Mixed { No = 0, Yes = "YES" })

Syntax: enum EnumName { member1 = value1, member2 = value2 }

Heterogeneous enums allow a combination of numeric and string values within the same enum. This can be useful for scenarios where you need both types of values.

enum Mixed {
No = 0,
Yes = "YES"
}
console.log(Mixed.No); // Output: 0
console.log(Mixed.Yes); // Output: YES
Output:
0
YES

1. Generic Functions (function identity<T>(arg: T): T)

Syntax: function functionName<T>(arg: T): T

Generic functions allow you to define a function that works with any data type. The type is specified as a placeholder <T>, which will be replaced with a specific type when the function is called.

function identity<T>(arg: T): T {
return arg;
}

console.log(identity(5)); // Output: 5
console.log(identity("Hello")); // Output: Hello
Output:
5
Hello

2. Generic Interfaces (interface Box<T> { value: T })

Syntax: interface InterfaceName<T> { property: T }

Generic interfaces allow you to define an interface with a placeholder for a type. This can be used to enforce that specific properties in objects conform to a specific type.

interface Box<T> {
value: T;
}

const box1: Box<number> = { value: 42 };
const box2: Box<string> = { value: "Hello" };
console.log(box1.value); // Output: 42
console.log(box2.value); // Output: Hello
Output:
42
Hello

3. Generic Classes (class Queue<T> { ... })

Syntax: class ClassName<T> { ... }

Generic classes allow you to create classes that work with any data type. The placeholder type <T> will be replaced when the class is instantiated.

class Queue<T> {
private items: T[] = [];

enqueue(item: T): void {
this.items.push(item);
}

dequeue(): T | undefined {
return this.items.shift();
}

}

const queue = new Queue<number>();
queue.enqueue(10);
queue.enqueue(20);
console.log(queue.dequeue()); // Output: 10
Output:
10

4. Constraints (<T extends SomeType>)

Syntax: <T extends SomeType>

Constraints allow you to specify that the placeholder type <T> must extend or be a subtype of a given type. This ensures that only compatible types can be used with the generic code.

function merge<T extends object>(obj1: T, obj2: T): T {
return { ...obj1, ...obj2 };
}

const obj1 = { name: "Alice", age: 25 };
const obj2 = { city: "New York" };
console.log(merge(obj1, obj2)); // Output: { name: 'Alice', age: 25, city: 'New York' }
Output:
{ name: 'Alice', age: 25, city: 'New York' }

1. Partial<T> (Make all properties optional)

Syntax: Partial<T>

Partial makes all properties of a given type optional. It is useful when you need to work with an object but don’t want to specify all its properties.

interface Person {
name: string;
age: number;
}

const person: Partial<Person> = { name: "Alice" };
console.log(person); // Output: { name: 'Alice' }
Output:
{ name: 'Alice' }

2. Required<T> (Make all properties required)

Syntax: Required<T>

Required makes all properties of a given type required. It is useful when you need to ensure that all properties of an object are present.

interface Person {
name?: string;
age?: number;
}

const person: Required<Person> = { name: "Alice", age: 25 };
console.log(person); // Output: { name: 'Alice', age: 25 }
Output:
{ name: 'Alice', age: 25 }

3. Readonly<T> (Make all properties readonly)

Syntax: Readonly<T>

Readonly makes all properties of a given type immutable (readonly). You cannot modify the properties of an object once it is defined.

interface Person {
name: string;
age: number;
}

const person: Readonly<Person> = { name: "Alice", age: 25 };
// person.age = 26; // Error: Cannot assign to 'age' because it is a read-only property.
console.log(person); // Output: { name: 'Alice', age: 25 }
Output:
{ name: 'Alice', age: 25 }

4. Record<K, T> (Object with keys K and values T)

Syntax: Record<K, T>

Record constructs an object type with a specific set of keys (K) and values of type (T). It is useful for creating maps or dictionaries.

type Score = Record<string, number>;
const scores: Score = { Alice: 90, Bob: 85 };
console.log(scores.Alice); // Output: 90
Output:
90

5. Pick<T, K> (Select subset of properties)

Syntax: Pick<T, K>

Pick allows you to select a subset of properties from a given type (T). You specify the properties to pick using type K.

interface Person {
name: string;
age: number;
email: string;
}

type PersonName = Pick<Person, "name" | "email">;
const person: PersonName = { name: "Alice", email: "alice@example.com" };
console.log(person); // Output: { name: 'Alice', email: 'alice@example.com' }
Output:
{ name: 'Alice', email: 'alice@example.com' }

6. Omit<T, K> (Exclude properties)

Syntax: Omit<T, K>

Omit creates a type that excludes specific properties from the given type (T). You specify the properties to exclude using type K.

interface Person {
name: string;
age: number;
email: string;
}

type PersonWithoutEmail = Omit<Person, "email">;
const person: PersonWithoutEmail = { name: "Alice", age: 25 };
console.log(person); // Output: { name: 'Alice', age: 25 }
Output:
{ name: 'Alice', age: 25 }

7. Exclude<T, U> (Exclude types from union)

Syntax: Exclude<T, U>

Exclude removes types from a union type. It excludes types from the first type (T) that are present in the second type (U).

type T = string | number | boolean;
type U = Exclude<T, boolean>;
const value: U = "Hello";
console.log(value); // Output: Hello
Output:
Hello

8. Extract<T, U> (Extract types from union)

Syntax: Extract<T, U>

Extract creates a type that extracts the types from the union (T) that are assignable to (U).

type T = string | number | boolean;
type U = Extract<T, string | boolean>;
const value: U = "Hello";
console.log(value); // Output: Hello
Output:
Hello

9. NonNullable<T> (Remove null and undefined)

Syntax: NonNullable<T>

NonNullable removes null and undefined from a given type (T), ensuring that the type cannot be null or undefined.

type T = string | number | null | undefined;
type NonNull = NonNullable<T>;
const value: NonNull = "Hello";
console.log(value); // Output: Hello
Output:
Hello

1. Type Guards (typeof, instanceof)

Syntax: typeof, instanceof

Type Guards allow you to narrow down the type of a variable within a block of code. You can use typeof for primitive types or instanceof for class instances.

function printLength(value: string | number): void {
if (typeof value === "string") {
console.log(value.length); // Only works if value is a string
} else {
console.log(value.toFixed(2)); // Only works if value is a number
}
}

printLength("Hello"); // Output: 5
printLength(42); // Output: 42.00
Output:
5
42.00

2. Discriminated Unions (Tagged union types)

Syntax: union types with a common literal property (discriminator).

Discriminated Unions are a pattern used to narrow down types. A common property (discriminator) is used to distinguish between types in a union.

interface Bird {
type: "bird";
flySpeed: number;
}

interface Fish {
type: "fish";
swimSpeed: number;
}

type Animal = Bird | Fish;

function move(animal: Animal): void {
if (animal.type === "bird") {
console.log(`Flying at ${animal.flySpeed} km/h`);
} else {
console.log(`Swimming at ${animal.swimSpeed} km/h`);
}
}

move({ type: "bird", flySpeed: 20 }); // Output: Flying at 20 km/h
move({ type: "fish", swimSpeed: 5 }); // Output: Swimming at 5 km/h
Output:
Flying at 20 km/h
Swimming at 5 km/h

3. in Operator ("name" in obj)

Syntax: "property" in object

The in operator is used to check if a property exists in an object. It can be used as a type guard to narrow down types based on the presence of certain properties.

interface Bird {
flySpeed: number;
}

interface Fish {
swimSpeed: number;
}

type Animal = Bird | Fish;

function move(animal: Animal): void {
if ("flySpeed" in animal) {
console.log(`Flying at ${animal.flySpeed} km/h`);
} else {
console.log(`Swimming at ${animal.swimSpeed} km/h`);
}
}

move({ flySpeed: 20 }); // Output: Flying at 20 km/h
move({ swimSpeed: 5 }); // Output: Swimming at 5 km/h
Output:
Flying at 20 km/h
Swimming at 5 km/h

4. Type Predicates (isFish(pet): pet is Fish)

Syntax: function is(arg): arg is Type

Type Predicates are used to tell TypeScript the type of a variable inside a function. They provide a way to refine the type in the scope of a conditional block.

interface Fish {
swimSpeed: number;
}

function isFish(pet: any): pet is Fish {
return (pet as Fish).swimSpeed !== undefined;
}

function move(pet: Fish | { name: string }): void {
if (isFish(pet)) {
console.log(`Swimming at ${pet.swimSpeed} km/h`);
} else {
console.log(`${pet.name} is not a fish`);
}
}

move({ swimSpeed: 10 }); // Output: Swimming at 10 km/h
move({ name: "Charlie" }); // Output: Charlie is not a fish
Output:
Swimming at 10 km/h
Charlie is not a fish

1. Export (export const x = 1)

Syntax: export

The export keyword allows you to export variables, functions, classes, or objects from a module so they can be used in other files.

// file1.ts
export const x = 1;

// file2.ts
import { x } from "./file1";
console.log(x); // Output: 1
Output:
1

2. Default Export (export default class)

Syntax: export default

The export default keyword is used to export a single value from a module, such as a class, function, or object, which can be imported without using curly braces.

// file1.ts
export default class Person {
constructor(public name: string) {}
}

// file2.ts
import Person from "./file1";
const person = new Person("John");
console.log(person.name); // Output: John
Output:
John

3. Import (import { x } from "./file")

Syntax: import { x } from "./file"

The import keyword is used to bring in modules, functions, variables, or classes from other files. You must use the same name as the exported element when importing (unless using a default export).

// file1.ts
export const x = 10;

// file2.ts
import { x } from "./file1";
console.log(x); // Output: 10
Output:
10

4. Dynamic Import (const module = await import("./file"))

Syntax: import("./file")

Dynamic imports allow you to load modules on demand. This helps with lazy loading, as the module will only be loaded when it's needed. It returns a promise.

// file1.ts
export const x = 100;

// file2.ts
async function loadModule() {
const module = await import("./file1");
console.log(module.x); // Output: 100
}

loadModule();
Output:
100

1. Mapped Types ({ [P in K]: T })

Syntax: { [P in K]: T }

Mapped types allow you to create new types by transforming properties of an existing type. You can iterate over keys and define how their values should be transformed.

type Keys = "name" | "age";
type Person = { name: string; age: number; }

type ReadOnlyPerson = { [P in Keys]: string };
const person: ReadOnlyPerson = { name: "John", age: "30" };
Output:
{ name: "John", age: "30" }

2. Conditional Types (T extends U ? X : Y)

Syntax: T extends U ? X : Y

Conditional types allow you to define types based on a condition. If the condition is true, the type is X; otherwise, it is Y.

type IsString = T extends string ? "Yes" : "No";
type Test1 = IsString; // "Yes"
type Test2 = IsString; // "No"
Output:
Test1: "Yes"
Test2: "No"

3. Template Literal Types (type Event = "click" | "hover")

Syntax: type Event = "click" | "hover"

Template literal types allow you to create types that are built from string literals, including combinations of literals and expressions.

type Event = "click" | "hover";
type ButtonEvent = `button-${Event}`;
const clickEvent: ButtonEvent = "button-click"; // Valid
const invalidEvent: ButtonEvent = "button-tap"; // Error: Type '"button-tap"' is not assignable to type 'ButtonEvent'.
Output:
Valid: "button-click"
Error: Type '"button-tap"' is not assignable to type 'ButtonEvent'.

4. Infer Keyword (type ReturnType = T extends (...args: any) => infer R ? R : any)

Syntax: infer

The infer keyword is used in conditional types to infer a type within the scope of a condition. It is often used to extract the return type of functions.

type ReturnType = T extends (...args: any) => infer R ? R : any;
type MyFunction = (x: number, y: string) => boolean;
type FunctionReturn = ReturnType; // boolean
Output:
FunctionReturn: boolean

5. Distributive Conditional Types

Syntax: T extends U ? X : Y

Distributive conditional types apply the conditional type logic to each element of a union type.

type ExtractStrings = T extends string ? T : never;
type Result = ExtractStrings<"a" | "b" | 1 | 2>; // "a" | "b"
Output:
Result: "a" | "b"

6. Template Literal Types with Conditional Types

Syntax: type Event = `${string}-${number}`

Template literal types combined with conditional types allow more flexible and dynamic type creation.

type Event = `${string}-${number}`;
type IsEvent = T extends `${string}-${number}` ? "Valid" : "Invalid";
type Test1 = IsEvent<"click-1">; // "Valid"
type Test2 = IsEvent<"error">; // "Invalid"
Output:
Test1: "Valid"
Test2: "Invalid"

7. Recursive Types

Syntax: type Tree = { value: T; children?: Tree[] }

Recursive types allow you to define types that refer to themselves, useful for structures like trees.

type Tree = { value: T; children?: Tree[] };
const tree: Tree = { value: "root", children: [{ value: "child", children: [] }] };
Output:
{ value: "root", children: [{ value: "child", children: [] }] }

8. Intersection Types (A & B)

Syntax: A & B

Intersection types combine multiple types into one, requiring an object to satisfy all the types in the intersection.

type A = { name: string };
type B = { age: number };
type AB = A & B;
const ab: AB = { name: "John", age: 30 };
Output:
{ name: "John", age: 30 }

9. Union Types (T | U)

Syntax: T | U

Union types allow a value to be one of several types. The value can be any one of the types in the union.

type A = string | number;
const a: A = "Hello";
const b: A = 42;
Output:
a: "Hello"
b: 42

1. Try/Catch (try { ... } catch (e: unknown) { ... })

Syntax: try { ... } catch (e: unknown) { ... }

The try/catch statement allows you to catch runtime errors. In TypeScript, you should use unknown for the error type instead of any, to ensure type safety.

try {
throw new Error("Something went wrong!");
} catch (e: unknown) {
if (e instanceof Error) {
console.log(e.message);
} else {
console.log("Unknown error");
}
}
Output:
Something went wrong!

2. Custom Errors (class MyError extends Error)

Syntax: class MyError extends Error { ... }

Custom errors allow you to define more specific errors tailored to your application. You can create a class that extends the built-in Error class.

class MyError extends Error {
constructor(message: string) {
super(message);
this.name = "MyError";
}
}

function test() {
throw new MyError("This is a custom error!");
}

try {
test();
} catch (e: unknown) {
if (e instanceof MyError) {
console.log(e.message);
} else {
console.log("Unknown error");
}
}
Output:
This is a custom error!

3. Asynchronous Error Handling (try/catch with async/await)

Syntax: try { ... } catch (e: unknown) { ... } with async/await

Handling errors in asynchronous code with async/await is done in the same way as synchronous code, but you need to ensure you are working with promises.

async function fetchData() {
const response = await fetch("https://api.example.com");
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return await response.json();
}

async function handleData() {
try {
const data = await fetchData();
console.log(data);
} catch (e: unknown) {
if (e instanceof Error) {
console.log(e.message);
} else {
console.log("Unknown error");
}
}
}

handleData();
Output:
Failed to fetch data

4. Rethrowing Errors

Syntax: throw e

Sometimes, after catching an error, you may need to rethrow it, possibly to be handled elsewhere. This is commonly used in logging or when transforming the error.

function processData(data: string) {
if (data.length === 0) {
throw new Error("Data cannot be empty");
}
return data;
}

function handleError() {
try {
processData("");
} catch (e: unknown) {
console.log("Logging error: ", e);
throw e; // Rethrow the error to be handled at a higher level.
}
}

try {
handleError();
} catch (e: unknown) {
if (e instanceof Error) {
console.log("Final handling of the error:", e.message);
}
}
Output:
Logging error: Error: Data cannot be empty
Final handling of the error: Data cannot be empty

5. Error Boundaries (React)

Syntax: static getDerivedStateFromError(), componentDidCatch()

In React, error boundaries are used to catch JavaScript errors anywhere in the component tree and log those errors, displaying a fallback UI.

import React, { Component, ErrorInfo } from 'react';

class ErrorBoundary extends Component {
state = { hasError: false };

static getDerivedStateFromError(error: Error) {
return { hasError: true };
}

componentDidCatch(error: Error, info: ErrorInfo) {
console.log("Error occurred:", error, info);
}

render() {
if (this.state.hasError) {
return

Something went wrong!

;
}

return this.props.children;
}
}

export default ErrorBoundary;
Output:
Fallback UI shown if an error occurs within any child component.

6. Using Error Codes and Messages

Syntax: class CustomError extends Error { ... }

In some cases, it’s useful to include error codes alongside error messages for better error handling. You can extend the custom error class to include a code.

class CustomError extends Error {
code: number;
constructor(message: string, code: number) {
super(message);
this.name = "CustomError";
this.code = code;
}
}

function throwError() {
throw new CustomError("Something went wrong!", 1001);
}

try {
throwError();
} catch (e: unknown) {
if (e instanceof CustomError) {
console.log(`Error ${e.code}: ${e.message}`);
} else {
console.log("Unknown error");
}
}
Output:
Error 1001: Something went wrong!

1. Promises (Promise<T>)

Syntax: new Promise((resolve, reject) => { ... })

A Promise represents the eventual completion (or failure) of an asynchronous operation. It has three states: pending, resolved (fulfilled), and rejected.

const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched!");
}, 1000);
});

fetchData.then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
Output:
Data fetched!

2. Async/Await (async function fetchData())

Syntax: async function functionName() { await someAsyncOperation() }

async/await is a cleaner way to work with asynchronous code. The async keyword makes a function return a promise, and the await keyword pauses the execution of the function until the promise is resolved.

async function fetchData() {
const result = await new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Data fetched with async/await!");
}, 1000);
});
console.log(result);
}

fetchData();
Output:
Data fetched with async/await!

3. Promise Chaining

Syntax: promise.then().then().catch()

Promises can be chained to execute asynchronous operations sequentially. Each then block handles the resolved value from the previous one.

function fetchData() {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Data fetched!");
}, 1000);
});
}

fetchData()
.then(result => {
console.log(result);
return "Processed Data";
})
.then(processed => {
console.log(processed);
})
.catch(error => {
console.log("Error:", error);
});
Output:
Data fetched!
Processed Data

4. Handling Errors in Async/Await

Syntax: try { await someAsyncOperation() } catch (error) { ... }

In async functions, you can handle errors using a try/catch block. This is the recommended way of handling errors when working with async/await.

async function fetchData() {
try {
const result = await new Promise<string>((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch data");
}, 1000);
});
console.log(result);
} catch (error) {
console.log("Error:", error);
}
}

fetchData();
Output:
Error: Failed to fetch data

5. Parallel Async Operations (Promise.all)

Syntax: Promise.all([promise1, promise2])

When you need to perform multiple asynchronous operations in parallel, you can use Promise.all, which waits for all promises to resolve and returns their results.

async function fetchData() {
const promise1 = new Promise<string>((resolve) => {
setTimeout(() => resolve("Data 1 fetched"), 1000);
});

const promise2 = new Promise<string>((resolve) => {
setTimeout(() => resolve("Data 2 fetched"), 500);
});

const results = await Promise.all([promise1, promise2]);
console.log(results);
}

fetchData();
Output:
[ 'Data 1 fetched', 'Data 2 fetched' ]

6. Async Iteration (for await ... of)

Syntax: for await (const item of asyncIterable)

Async iteration allows you to loop through an asynchronous iterable (like an async generator) using the for await ... of loop. This is helpful when dealing with asynchronous data sources such as streams.

async function* generateData() {
yield "Data 1";
yield "Data 2";
yield "Data 3";
}

async function fetchData() {
for await (const data of generateData()) {
console.log(data);
}
}

fetchData();
Output:
Data 1
Data 2
Data 3

7. Performance Considerations in Async Programming

In asynchronous programming, performance can be impacted by how you manage the concurrency of your async operations. Consider the following:

  • Use Promise.all for parallel operations when tasks are independent.
  • Use async/await when tasks depend on the result of others.
  • Use for await ... of for handling async data streams or large data sets efficiently.

Example of performance bottleneck:

async function fetchData() {
const result1 = await new Promise<string>((resolve) => {
setTimeout(() => resolve("Data 1"), 1000);
});
const result2 = await new Promise<string>((resolve) => {
setTimeout(() => resolve("Data 2"), 1000);
});
console.log(result1, result2);
}

fetchData();
Output:
Data 1 Data 2

8. Error Handling in Promise Chains

In asynchronous code, error handling is crucial. You can handle errors in promise chains with catch or inside async functions using try/catch.

function fetchData() {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch data");
}, 1000);
});
}

fetchData()
.then(result => {
console.log(result);
})
.catch(error => {
console.log("Error:", error);
});
Output:
Error: Failed to fetch data

1. Class Decorators (@sealed)

Syntax: @sealed

A class decorator is applied to the constructor of a class. This decorator is used to modify or extend the behavior of a class. In the example below, the decorator prevents further subclassing of the class by sealing it.

function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class Person {
constructor(public name: string) {
this.name = name;
}
}

const p = new Person("John");
console.log(p);
// Person cannot be subclassed further
Output:
Person { name: 'John' }

2. Method Decorators (@logMethod)

Syntax: @logMethod

Method decorators are applied to methods of a class. They can be used to log method calls, add validation, or track method executions.

function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Method ${key} was called with args: ${args}`);
return originalMethod.apply(this, args);
};
}

class User {
@logMethod
greet(name: string) {
return `Hello, ${name}!`;
}
}

const user = new User();
console.log(user.greet("Alice"));
Output:
Method greet was called with args: [ 'Alice' ]
Hello, Alice!

3. Property Decorators (@format)

Syntax: @format("Hello")

Property decorators are applied to the properties of a class. These can be used for purposes like formatting values or validating properties before they're set.

function format(value: string) {
return function(target: any, key: string) {
let _val = value;
const getter = () => _val;
const setter = (newVal: string) => {
_val = `${value} ${newVal}`;
};
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}

class User {
@format("Hello")
name: string = "";
}

const user = new User();
user.name = "Alice";
console.log(user.name);
Output:
Hello Alice

4. Parameter Decorators (@logParam)

Syntax: @logParam

Parameter decorators allow you to add logic before or after a parameter is passed to a method. This is useful for logging or modifying input data before the method execution.

function logParam(target: any, methodName: string, parameterIndex: number) {
console.log(`Parameter at index ${parameterIndex} in method ${methodName} was decorated`);
}

class User {
greet(@logParam name: string) {
return `Hello, ${name}`;
}
}

const user = new User();
user.greet("Bob");
Output:
Parameter at index 0 in method greet was decorated

5. Accessor Decorators (@observable)

Syntax: @observable

Accessor decorators allow modification of getters and setters in a class. In the example below, the decorator tracks when the property is accessed.

function observable(target: any, key: string, descriptor: PropertyDescriptor) {
const originalGet = descriptor.get;
descriptor.get = function () {
console.log(`Property ${key} was accessed`);
return originalGet?.call(this);
};
}

class User {
private _name = "John";

@observable
get name() {
return this._name;
}

set name(name: string) {
this._name = name;
}
}

const user = new User();
console.log(user.name);
Output:
Property name was accessed
John

6. Advanced: Composite Decorators

Composite decorators can be used to apply multiple behaviors to a single class or method. By chaining multiple decorators, you can add various functionalities.

function readOnly(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}

function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Method ${key} was called with args: ${args}`);
return originalMethod.apply(this, args);
};
}

class User {
@readOnly
@logMethod
name = "John";
}

const user = new User();
user.name = "Alice"; // Error: cannot assign to read-only property
console.log(user.name);
Output:
Method name was called with args: [ 'Alice' ]
Error: cannot assign to read-only property

7. Advanced: Decorator Factories

Decorator factories allow you to pass arguments into your decorators. This lets you create more flexible and reusable decorators.

function logWithMessage(message: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${message}: ${args}`);
return originalMethod.apply(this, args);
};
};
}

class User {
@logWithMessage("Calling greet method")
greet(name: string) {
return `Hello, ${name}`;
}
}

const user = new User();
console.log(user.greet("Alice"));
Output:
Calling greet method: Alice
Hello, Alice
--- ### Topics Covered: 1. **Class Decorators (@sealed)**: Seals the class and prevents subclassing. 2. **Method Decorators (@logMethod)**: Logs method calls and parameters. 3. **Property Decorators (@format)**: Formats property values. 4. **Parameter Decorators (@logParam)**: Logs method parameters. 5. **Accessor Decorators (@observable)**: Observes getter method calls. 6. **Composite Decorators**: Using multiple decorators together. 7. **Decorator Factories**: Passing arguments into decorators for more flexibility. This guide provides an in-depth overview of decorators in TypeScript, from basic concepts to advanced techniques like decorator factories and composite decorators.

1. tsconfig.json

The tsconfig.json file is the configuration file that helps control the TypeScript compiler's behavior. It specifies various compiler options such as target output version, module system, file inclusion/exclusion, and more.

Purpose: Controls the TypeScript compiler's behavior.

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
Output:
This configuration will compile TypeScript files from the `src` folder, outputting JavaScript to the `dist` directory and enforcing strict type checking.
  • "target": Specifies the JavaScript version to compile to (e.g., es5, es6).
  • "module": Specifies the module system (e.g., commonjs, esnext).
  • "strict": Enables strict type checking across all TypeScript features.
  • "esModuleInterop": Ensures compatibility with CommonJS modules.
  • "outDir": Defines the output directory for compiled JavaScript files.

2. ESLint & Prettier

ESLint is a tool used to ensure your code follows a consistent style and catches common programming errors, while Prettier is a code formatter that automatically formats your code to meet your style preferences.

Purpose: Linting (ESLint) and automatic code formatting (Prettier) for TypeScript projects.

// .eslintrc.json
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"env": {
"browser": true,
"node": true
},
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "double"]
}
}

// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
Output:
ESLint will enforce coding conventions, while Prettier automatically formats the code according to your preferences.

ESLint: Helps identify and fix problematic patterns in your code.

  • "extends": Inherits rules from shared configurations.
  • "parser": Specifies the parser for TypeScript.
  • "rules": Defines custom linting rules.

Prettier: Automatically formats code.

  • "semi": Whether to add semicolons.
  • "singleQuote": Prefer single quotes over double quotes.
  • "trailingComma": Controls when trailing commas should be used.

Advanced Configuration:

  • ESLint with TypeScript: Use @typescript-eslint/eslint-plugin for TypeScript-specific linting rules.
  • Prettier with TypeScript: Use prettier-plugin-typescript for better support with TypeScript-specific formatting.

TypeScript with React (@types/react)
When working with TypeScript and React, you can use the @types/react package to get the type definitions for React components, hooks, and other API elements. This enhances development by providing autocompletion, type checking, and better tooling support.

3. TypeScript with React (@types/react)

Purpose: Provides type definitions for React, enabling a seamless TypeScript experience when working with React.

// Install dependencies
npm install react react-dom
npm install --save-dev @types/react @types/react-dom

// Example Component
import React, { useState } from 'react';

interface Props {
name: string;
}

const Greeting: React.FC = ({ name }) => {
const [count, setCount] = useState(0);

return (
<div>
<h1>Hello, {name}!</h1>
<button onClick={() => setCount(count + 1)}>Click me: {count}</button>
</div>
);
};

export default Greeting;
Output:
This code defines a simple TypeScript-based React component with type-safe props and state management.

Type Definitions for React: @types/react provides TypeScript with the necessary type information to work with React.

React.FC: Type-safe function components.

useState with Types: Specifies that count is of type number, ensuring type safety.

Advanced Topics in React with TypeScript:

UseContext with TypeScript: Providing type-safe context values and consuming them within components.

Higher-Order Components (HOCs): Creating reusable component logic with types.

Generics in React Components: Using generics to make reusable components with type safety.

TypeScript with Webpack: Webpack can be used with TypeScript for module bundling and optimization.

4. TypeScript with Webpack

Purpose: Integrate TypeScript with Webpack for efficient bundling and optimization of TypeScript files.

// Install necessary dependencies
npm install --save-dev typescript ts-loader webpack webpack-cli

// webpack.config.js
module.exports = {
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
};
Output:
This configuration will bundle TypeScript files into a single output file (`bundle.js`).

5. TypeScript with Babel

TypeScript with Babel: Using Babel alongside TypeScript can improve build speed, especially when using features like React JSX.

Purpose: Use Babel to compile TypeScript and JSX code for faster builds.

// Install dependencies
npm install --save-dev @babel/core @babel/preset-env @babel/preset-typescript babel-loader

// babel.config.json
{
"presets": ["@babel/preset-env", "@babel/preset-typescript", "@babel/preset-react"]
}
Output:
Babel can compile both TypeScript and JSX syntax in a more optimized manner.
  • tsconfig.json: Manage TypeScript compiler settings.
  • ESLint & Prettier: Maintain consistent code with linting and formatting.
  • TypeScript with React: Use @types/react for type safety in React applications.
  • Advanced Tooling: Integrate TypeScript with Webpack and Babel for improved build processes and optimizations.