let firstName: string = "Dylan";
Explicit type assignments are easier to read and more intentional.
let firstName = "Dylan";
With implicit type assignment TypeScript will "guess" the type, based on the assigned value.
let firstName: string = "Alice";
        let age: number = 25;
        
        let price: number = 99.99;
        
        let hex: number = 0xff; // Hexadecimal
        
        let binary: number = 0b1010; // Binary
        
        let octal: number = 0o744; // Octal
      
let isDone: boolean = true;
        let bigNumber: bigint = 9007199254740991n; // Adding 'n' makes it a bigint
        
        let anotherBigInt: bigint = BigInt(12345678901234567890);
      
let uniqueKey: symbol = Symbol("key");
let emptyValue: null = null;
let notAssigned: undefined;
        let person: { name: string; age: number } = {
          
          name: "Alice",
          
          age: 25,
          
        };
      
An object type defines the shape of an object, specifying its properties and their types.
        type Person = {
          
          name: string;
          
          age: number;
          
        };
        
        
        let user: Person = { name: "Bob", age: 30 };
      
Instead of defining the object inline, you can create a type alias for reuse.
        interface Car {
          
          brand: string;
          
          year: number;
          
        }
        
        
        let myCar: Car = { brand: "Toyota", year: 2022 };
      
An interface is similar to a type, but it can be extended and implemented in classes. Interfaces are preferred for defining object shapes, especially in OOP.
        interface User {
          
          readonly id: number; // Cannot be changed
          
          name: string;
          
          age?: number; // Optional
          
        }
        
        
        let user1: User = { id: 1, name: "Charlie" };
        
        user1.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
      
Optional properties (?) make a property optional. Readonly properties (readonly) prevent modification.
        interface Address {
          
          city: string;
          
          country: string;
          
        }
        
        
        interface Employee {
          
          name: string;
          
          address: Address; // Nested object
          
        }
        
        
        let emp: Employee = {
          
          name: "David",
          
          address: {
            
            city: "New York",
            
            country: "USA",
            
          },
          
        };
        
        
        console.log(emp.address.city); // Output: New York        
      
Objects can have other objects inside them.
        interface Calculator {
          
          add(a: number, b: number): number;
          
        }
        
        
        let myCalc: Calculator = {
          
          add: (x, y) => x + y,
          
        };
        
        
        console.log(myCalc.add(5, 10)); // Output: 15        
      
Objects can have methods as properties.
        interface Animal {
          
          species: string;
          
        }
        
        
        interface Dog extends Animal {
          
          breed: string;
          
        }
        
        
        let myDog: Dog = { species: "Canine", breed: "Labrador" };
      
You can extend an interface to create a new one.
        type A = { x: number };
        
        type B = { y: number };
        
        
        type AB = A & B; // Combined type
        
        
        let point: AB = { x: 10, y: 20 };
      
Instead of extending, you can combine types using &.
        type StudentGrades = Record<string, number>;
        
        let grades: StudentGrades = {
          
          Alice: 90,
          
          Bob: 85,
          
        };
      
Record<K, V> utility type defines an object type where keys (K) map to values (V).
        type Person = {
          
          name: string;
          
          age: number;
          
          email: string;
          
        };
        
        
        type Employee = {
          
          id: number;
          
          department: string;
          
        };
        
        
     // Combine Person and Employee but remove `email`
        
        type EmployeeWithoutEmail = Omit<Person, "email"> & Employee;
        
        
        let employee: EmployeeWithoutEmail = {
          
          name: "Alice",
          
          age: 30,
          
          id: 101,
          
          department: "Engineering",
          
        };
      
        Omit
type EmployeeWithoutEmail = Pick<Person, "name" | "age"> & Employee;
This method is useful when you want to be explicit about the properties you keep.
        let numbers: number[] = [1, 2, 3, 4, 5]; // Using []
        
        let names: Array<string> = ["Alice", "Bob", "Charlie"]; // Using Array<T>
      
Both approaches are valid, but number[] is more commonly used.
        const colors: readonly string[] = ["red", "green", "blue"];
        
        colors.push("yellow"); // Error: Cannot modify a readonly array.
      
Use readonly to prevent modification
        let nums: number[] = [10, 20, 30];
        
        
        nums.push(40); // Allowed
        
        nums.push("50"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
      
TypeScript enforces type safety on array methods.
        let person: [string, number] = ["Alice", 25];
        
        
        console.log(person[0]); // Output: Alice
        
        console.log(person[1]); // Output: 25
      
A tuple is an array with a fixed length and specific types for each position.
        let user: [string, number?] = ["Bob"];
        
        console.log(user[1]); // Output: undefined
      
You can make tuple elements optional.
        const coordinates: readonly [number, number] = [10, 20];
        
        coordinates[0] = 30; // Error: Cannot assign to index of a readonly tuple.
      
Tuples can also be readonly.
        type StringNumberPair = [string, ...number[]];
        
        
        let data: StringNumberPair = ["scores", 85, 90, 78];
        
        console.log(data); // Output: ["scores", 85, 90, 78]
      
Tuples can include rest parameters to allow variable-length data. First element must be a string, but remaining elements can be number[].
        let userInfo: [name: string, age: number] = ["Eve", 29];
        
        
        console.log(userInfo[0]); // Output: Eve
        
        console.log(userInfo[1]); // Output: 29
      
Named elements in tuples improve readability but don't change functionality.
        let value: string | number;
        
        
        value = "Hello"; // Allowed
        
        value = 42; // Allowed
        
        value = true; // Error: Type 'boolean' is not assignable to 'string | number'.
      
A union type allows a variable to hold one of multiple types. This is useful when a variable can have different types in different situations.
        type Dog = { bark: () => void };
        
        type Cat = { meow: () => void };
        
        
        let pet: Dog | Cat;
        
        
        pet = { bark: () => console.log("Woof!") }; // Allowed
        
        pet = { meow: () => console.log("Meow!") }; // Allowed
      
You can define a union of object types.
        function makeSound(animal: Dog | Cat) {
          
          if ("bark" in animal) {
            
            animal.bark(); // TypeScript knows it's a Dog
            
          } else {
            
            animal.meow(); // TypeScript knows it's a Cat
            
          }
          
        }
        
        
        makeSound({ bark: () => console.log("Woof!") }); // Output: Woof!
        
        makeSound({ meow: () => console.log("Meow!") }); // Output: Meow!        
      
To narrow down a union type, you can use type guards. Using in ensures we call the right method.
        type Person = { name: string };
        
        type Employee = { company: string };
        
        
        type Worker = Person & Employee; // Must have both properties
        
        
        let worker: Worker = {
          
          name: "Alice",
          
          company: "Tech Corp",
          
        };
      
An intersection type combines multiple types into one, meaning an object must satisfy all combined types. Worker has properties from both Person and Employee.
        type A = { id: string };
        
        type B = { id: number };
        
        
        type C = A & B; // Error: Type 'string' and 'number' are incompatible.
      
If the same property exists in multiple types with different types, TypeScript will throw an error. Solution: Ensure the types are compatible or use union (|) instead.
        interface HasName {
          
          name: string;
          
        }
        
        
        interface HasAge {
          
          age: number;
          
        }
        
        
        type PersonDetails = HasName & HasAge;
        
        
        let user: PersonDetails = { name: "Bob", age: 30 };
      
Intersections are useful for merging multiple interfaces. Works like multiple inheritance in object-oriented programming.
        enum Direction {
          
          Up,
          
          Down,
          
          Left,
          
          Right,
          
        }
        
        
        let move: Direction = Direction.Up;
        
        console.log(move); // Output: 0
      
An enum (enumeration) is a way to give names to a set of related values (usually numbers or strings). This makes the code more meaningful and maintainable. In the example above Direction.Up is 0, Direction.Down is 1, and so on.
        enum Status {
          
          Pending, // 0
        
          Approved, // 1
        
          Rejected, // 2
        
        }
        
        
        console.log(Status.Pending); // Output: 0
        
        console.log(Status.Approved); // Output: 1        
      
Numeric enums automatically assign values starting from 0.
        enum Status {
          
          Pending = 1,
          
          Approved = 5,
          
          Rejected = 10,
          
        }
        
        
        console.log(Status.Pending); // Output: 1
        
        console.log(Status.Approved); // Output: 2
        
        console.log(Status.Rejected); // Output: 3        
      
It is possible to assign custom values. If the first value is assigned, the rest auto-increments.
        enum Role {
          
          Admin = "ADMIN",
          
          User = "USER",
          
          Guest = "GUEST",
          
        }
        
        
        let userRole: Role = Role.Admin;
        
        console.log(userRole); // Output: "ADMIN"        
      
String enums allow you to assign string values instead of numbers.
        enum Sizes {
          
          Small = 10,
          
          Medium = Small * 2, // 20
          
          Large = Medium * 2, // 40
          
        }
        
        
        console.log(Sizes.Large); // Output: 40        
      
With computed enums you can use expressions to set values. The values auto-increment when not explicitly defined.
        enum Status {
          
          Active = "ACTIVE",
          
          Inactive = 0,
          
          Pending = 1,
          
        }
        
        
        console.log(Status.Active); // Output: "ACTIVE"
        
        console.log(Status.Pending); // Output: 1        
      
Enums can mix string and number values and form a heterogeneous enum, though it's not common.
        enum Color {
          
          Red = 1,
          
          Green,
          
          Blue,
          
        }
        
        
        console.log(Color.Green); // Output: 2
        
        console.log(Color[2]); // Output: Green        
      
With numeric enums, TypeScript supports reverse mapping, meaning you can access both the name and value. This allows accessing both value by name (Color.Green) and name by value (Color[2]).
        const enum Size {
          
          Small = "S",
          
          Medium = "M",
          
          Large = "L",
          
        }
        
        
        let tshirtSize: Size = Size.Medium;
        
        console.log(tShirt); // Output: "M"        
      
const enum provides a more optimized version that does not generate extra JavaScript code. This way no extra object is created in the compiled JavaScript, making the code more efficient.
        function add(x: number, y: number): number {
          
          return x + y;
          
        }
        
        
        console.log(add(5, 10)); // Output: 15        
      
You can specify types for function parameters and return values. The parameters x and y must be numbers and the function must return a number.
        function greet(name: string, message?: string): string {
          
          return `Hello, ${name}! ${message || "How are you?"}`;
          
        }
        
        
        console.log(greet("Alice")); // Hello, Alice! How are you?
        
        console.log(greet("Bob", "Welcome!")); // Hello, Bob! Welcome!        
      
In TypeScript, function parameters are required by default. You can make parameters optional using ? or provide default values. The message? makes it optional, so if it's not provided, a default value can be used.
        function sum(...numbers: number[]): number {
          
          return numbers.reduce((total, num) => total + num, 0);
          
        }
        
        
        console.log(sum(1, 2, 3, 4)); // Output: 10
        
        console.log(sum(5, 10));   // Output: 15        
      
Rest parameters allow a function to accept any number of arguments and treat them as an array. The ...numbers parameter allows passing multiple values.
        function format(input: number): number;
        
        function format(input: string): string;
        
        
        function format(input: string | number): string | number {
          
          if (typeof input === "string") {
            
            return input.toUpperCase();
            
          } else {
            
            return input * 2;
            
          }
          
        }
        
        
        console.log(format("hello")); // Output: "HELLO"
        
        console.log(format(10)); // Output: 20
        
        console.log(format(true)); // Error: Argument of type 'boolean' is not assignable to 'string | number'
      
Function overloading allows you to define multiple function signatures for different argument types. TypeScript allows overloaded signatures to describe different ways a function can be called.
        let multiply: (x: number, y: number) => number;
        
        multiply = (a, b) => a * b;
        
        console.log(multiply(4, 5)); // Output: 20
      
You can store functions in variables with explicit type definitions. This ensures the function always accepts two number arguments and returns a number.
        interface MathOperation {
          
          (x: number, y: number): number;
          
        }
        
        
        let multiply: MathFunction = (a, b) => a * b;
        
        console.log(multiply(3, 4)); // Output: 12        
      
Instead of using function types directly, you can define an interface. This makes the function signature reusable.
        function logMessage(message: string): void {
          
          console.log(message);
          
        }
        
        
        logMessage("Hello!"); // Works fine        
      
If a function does not return a value, use void.
        function throwError(message: string): never {
          
          throw new Error(message);
          
        }
        
      
The never type is used when a function never returns (e.g., throws an error or has an infinite loop).
        type GreetFunction = (name: string) => string;
        
        const sayHello: GreetFunction = (name) => `Hello, ${name}!`;
        
        console.log(sayHello("Alice")); // Output: "Hello, Alice!"
      
You can define function types using the type keyword:
        let value: any;
        
        
        value = 10;     // Number
        
        value = "Hello"; // String
        
        value = true;   // Boolean
      
The any type disables type checking, allowing a variable to hold any value. Useful when migrating JavaScript code to TypeScript. While using this you lose type safety, which can lead to runtime errors.
        let userInput: any;
        
        userInput = 5;
        
        userInput.toUpperCase(); // No error in TypeScript, but will fail at runtime!
      
Using any can be problematic, therefore it's a better alternative to use unknown instead of any if you need type safety.
        let data: unknown;
        
        data = "TypeScript";
        
        
        if (typeof data === "string") {
          
          console.log(data.toUpperCase()); // Safe operation
          
        }
        
        
        data.toUpperCase(); // Error: Property 'toUpperCase' does not exist on type 'unknown'.
      
The unknown type is similar to any, but you must check the type before using it. Useful when dealing with dynamic values (e.g., user input, API responses). It forces type checking before usage, preventing runtime errors.
        function logMessage(message: string): void {
          
          console.log(message);
          
        }
        
        
        logMessage("Hello!"); // Works fine        
      
A function that does not return a value should have a return type of void. Useful when a function performs an action but doesn't return anything.
        function example(): undefined {
          
          return undefined;
          
        }
      
A function with return type undefined must explicitly return undefined.
        function throwError(message: string): never {
          
          throw new Error(message);
          
        }
        
        
        function infiniteLoop(): never {
          
          while (true) {
            
            console.log("Running forever...");
            
          }
          
        }
      
The never type represents functions that never complete. Used by functions that always throw errors and functions that never return (e.g., infinite loops, server listeners).
        let someValue: unknown = "Hello, TypeScript!";
        
        
     // Method 1: Using 'as' syntax (Recommended)
        
        let strLength: number = (someValue as string).length;
        
        
     // Method 2: Using angle-bracket syntax
        
        let strLength2: number = (<string>someValue).length;
      
Type assertion is a way to manually override TypeScript's type inference. It does not change the runtime value, only the TypeScript type checking. The as syntax is preferred because angle brackets (<>) can cause issues in JSX/React.
        function getUserInput(): unknown {
          
          return "Hello World";
          
        }
        
        
        let input = getUserInput();
        
        
        console.log(input.toUpperCase()); // TypeScript Error: Property 'toUpperCase' does not exist on type 'unknown'.
        
        console.log((input as string).toUpperCase()); // Works fine
      
When receiving dynamic data (e.g., from an API), TypeScript treats it as unknown. To access properties safely, you must assert the type. Without type assertion, TypeScript won't allow accessing string methods on unknown.
        let inputElement = document.getElementById("username") as HTMLInputElement;
        
        
        inputElement.value = "TypeScript is awesome!";
      
When working with the DOM, TypeScript may return a HTMLElement | null. You can assert a more specific type (e.g., HTMLInputElement). In the above example we tell TypeScript that inputElement is an <input> and has a value property.
let myCanvas = document.getElementById("canvas")!;
We can also use ! (Non-null assertion operator) to tell TypeScript "this value will never be null".
        function getFormattedValue(value: string | number) {
          
          if ((value as string).toUpperCase) {
            
            return (value as string).toUpperCase();
            
          }
          
          return value;
          
        }
        
        
        console.log(getFormattedValue("hello")); // Output: HELLO
        
        console.log(getFormattedValue(42)); // Output: 42
      
If a variable has multiple possible types, TypeScript doesn't know which one to use. Type assertions can help narrow it down. In the above example TypeScript assumes value is string within the if block.
        let num = "123" as number; // TypeScript Error! Can't convert string to number
        
        let num: number = Number("123"); // Works fine
      
Type assertion only exists in TypeScript, not JavaScript. It does not change the actual valueโjust how TypeScript treats it.
        let value: unknown = "Hello";
        
        console.log((value as any) as number); // No error, but incorrect!
      
In some cases, TypeScript may not allow a type assertion. You can force it using any, but this is risky. This can lead to unexpected behavior at runtime.
        const user = {
          
          name: "Alice",
          
          age: 30,
          
        } satisfies User;
      
With the as keyword TypeScript trusts you and may not warn about extra properties, or might lose the literal types ("Alice" becomes a string etc). But using the satisfies keyword ensures that a value matches a specific type, but without changing the inferred type of that value. It enforces shape, keeps literal types, detects extra properties and infers actual value types.
        const user = {
          
          name: "Alice",
          
          age: 30,
          
        } as const satisfies User;
      
Same as "satisfies User", but makes the user object immutable.
        type TypeOfObject = {
          
          firstName: string,
          
          lastName: string
          
        };
        
        
        const myObject: TypeOfObject = {
          
          firstName: "John",
          
          lastName: "Doe"
          
        };
        
        
        Object.keys(myObject).map((item) => {
          
          // Gives error: "Element implicitly has an 'any' type because expression of ..."
          
          myObject[item];
          
          
          // When the type of the object is known
          
          myObject[item as keyof TypeOfObject];
          
          
          // When the type of the object is not known
          
          myObject[item as keyof typeof myObject];
          
        });
      
In this example we explicitly tell TypeScript the type of the items of an object during iteration.
        let status: "success" | "error" | "pending";
        
        
        status = "success"; // Valid
        
        status = "error"; // Valid
        
        status = "failed"; // Error: Type '"failed"' is not assignable to type '"success" | "error" | "pending"'.
      
With string literals, a variable can only hold specific string values. This is useful for API responses, configuration settings, or state management.
        let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
        
        
        diceRoll = 3; // Valid
        
        diceRoll = 7; // Error: Type '7' is not assignable to type '1 | 2 | 3 | 4 | 5 | 6'.
      
You can restrict variables to specific numeric values. It's useful for limiting numeric values to predefined constants (e.g., dice rolls, user roles, response codes).
        let isEnabled: true | false;
        
        
        isEnabled = true; // Valid
        
        isEnabled = false; // Valid
        
        isEnabled = "yes"; // Error: Type '"yes"' is not assignable to type 'true | false'.
      
Boolean literals allow values to be strictly true or false. It helps enforce strict boolean checks in function parameters or conditions.
        function setStatus(status: "success" | "error" | "pending") {
          
          console.log(`Status set to: ${status}`);
          
        }
        
        
        setStatus("success"); // Works
        
        setStatus("error"); // Works
        
        setStatus("failed"); // Error: Argument of type '"failed"' is not assignable to parameter type.        
      
Literal types can be used as function parameters to enforce specific values. It's useful in APIs where you want to restrict inputs to predefined values.
        type Status = "success" | "error" | "pending";
        
        
        let apiStatus: Status;
        
        apiStatus = "success"; // Valid
        
        apiStatus = "failed"; // Error
      
You can combine literal types into unions and use type aliases for better readability. Type aliases improve code readability and reusability.
        type Shape =
          
          { kind: "circle"; radius: number }
          
          | { kind: "square"; side: number };
        
        
        function getArea(shape: Shape): number {
          
          if (shape.kind === "circle") {
            
            return Math.PI * shape.radius * shape.radius;
            
          } else {
            
            return shape.side * shape.side;
            
          }
          
        }
        
        
        console.log(getArea({ kind: "circle", radius: 10 })); // Works
        
        console.log(getArea({ kind: "triangle", base: 5, height: 10 })); // Error
      
Literal types are great for object-based type safety. It helps create type-safe discriminated unions for structured data.
const color = "red"; // Type is "red" (literal)
If you use const, TypeScript infers a literal type automatically.
        let color = "red"; // Type is string, not "red"
        
        
        const color = "red" as const; // Now color is strictly "red"
      
If you use let, TypeScript assumes the broader string type, which can be fixed by using as const to keep literals.
        const config = {
          
          theme: "dark",
          
          language: "en"
          
        } as const;
        
        
        config.theme = "light"; // Error: Cannot assign to 'theme' because it is a read-only property.        
      
Using as const makes an object's values immutable and inferred as literals. It ensures values in configurations, API responses, and enums are not accidentally changed.
        type World = "world";
        
        type Greeting = `hello ${World}`;
      
Basic syntax.
        type Direction = "left" | "right";
        
        type Move = `move-${Direction}`;
        
        
        // Now Move is: "move-left" | "move-right"
      
Instead of manually defining both "move-left" and "move-right", you dynamically build them with a template.
        type Size = "small" | "medium" | "large";
        
        type Color = "red" | "blue";
        
        
        type Button = `btn-${Size}-${Color}`;
        
        
        // Button: "btn-small-red" | "btn-small-blue" | "btn-medium-red" | ...
      
This creates combinations of all values from both Size and Color.
        type EventName<T extends string> = `on${Capitalize<T>}`;
        
        type ButtonEvents = EventName<"click" | "hover">;
        
        // "onClick" | "onHover"          
      
You can combine template literal types with utility types or custom mapped types.
        type EventType = "onClick" | "onHover";
        
        
        type ExtractEventName<T> = T extends `on${infer Name}` ? Name : never;
          
          
        type NameOnly = ExtractEventName<EventType>;
        
        // "Click" | "Hover"
      
You can also extract parts of a string using type inference with template literals (often in conditional types). This is great for parsing patterns in string types.
        type Routes = "/home" | "/about";
        
        
        function navigate(route: Routes) {
          
          console.log(`Navigating to ${route}`);
          
        }
        
        
        navigate("/home"); // This works
        
        navigate("/contact"); // Error: not in Routes
      
You can make functions type-safe using template literal types.
        type NewType<T> = {
          
          [Key in keyof T]: TypeTransformation;
          
        };
        
      
Mapped Types allow you to transform an existing type into a new type by iterating over its keys. In the above example keyof T gets all the keys of T, and [Key in keyof T] iterates over them.
        type User = {
          
          name: string;
          
          age: number;
          
        };
        
        
        type ReadonlyUser = {
          
          [K in keyof User]: Readonly<User[K]>;
          
        };
        
        
     // Equivalent to:
        
        type ReadonlyUser2 = {
          
          readonly name: string;
          
          readonly age: number;
          
        }        
      
Let's take an object type and make all its properties readonly. All properties of User are now readonly in ReadonlyUser.
type ReadonlyUser = Readonly<User>; // Same as the mapped type above
TypeScript provides built-in utility types like Readonly<T> which can be used as a shortcut.
        type OptionalUser = {
          
          [K in keyof User]?: User[K];
          
        };
        
        
     // Equivalent to:
        
        type OptionalUser2 = {
          
          name?: string;
          
          age?: number;
          
        };
        
        
     // Shortcut:
        
        type OptionalUser3 = Partial<User>; // Same as above
      
This makes all properties optional.
        type Mutable<T> = {
          
          -readonly [K in keyof T]: T[K]; // `-readonly` removes readonly
          
        };
        
        
        type MutableUser = Mutable<ReadonlyUser>; // Now it's mutable
      
In this exampple we are creating a Mutable Type (Removing readonly)
        type Fruit = "apple" | "banana" | "orange";
        
        type FruitStock = {
          
          [F in Fruit]: number;
          
        };
        
        
     // Equivalent to:
        
        type FruitStock2 = {
          
          apple: number;
          
          banana: number;
          
          orange: number;
          
        };
      
This dynamically creates a type with keys "apple" | "banana" | "orange".
        type Flexible<T> = {
          
          readonly [K in keyof T]?: T[K]; // Adds `readonly` and `?`
          
        };
        
        
        type FlexibleUser = Flexible<User>;
        
        
     // Equivalent to:
        
        type FlexibleUser2 = {
          
          readonly name?: string;
          
          readonly age?: number;
          
        };        
      
You can mix and match readonly, ?, or even change the property type.
        type RenameKeys
          [K in keyof T as `new_${string & K}`]: T[K];
          
        };
        
        
        type NewUser = RenameKeys<User>;
        
     // Equivalent to:
        
        type NewUser2 = {
          
          new_name: string;
          
          new_age: number;
          
        };        
      
You can rename keys inside a mapped type. This renames each key by prefixing it with "new_".
T extends U ? TrueType : FalseType;
Conditional types allow you to dynamically determine a type based on a condition. If T extends U, return TrueType, otherwise return FalseType.
        type IsString<T> = T extends string ? "Yes, it's a string" : "No, not a string";
        
        
        type A = IsString<string>; // "Yes, it's a string"
        
        type B = IsString<number>; // "No, not a string"          
      
This checks if T is a string.
        type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
        
        
        type ExampleFunction = () => string;
        
        type ExampleReturnType = GetReturnType<ExampleFunction>; // string          
      
You can pass any type and apply conditions. Here, infer R extracts the return type from a function.
        type NonNullable<T> = T extends null | undefined ? never : T;
        
        
        type A = NonNullable<string | null | undefined>; // string          
      
This filters out null and undefined from a type.
        type FilterNumbers<T> = T extends number ? T : never;
        
        
        type Result = FilterNumbers<string | number | boolean>; // number
      
If T is a union, TypeScript distributes the conditional type. This removes string and boolean, keeping only number.
        type Nullable<T> = {
          
          [K in keyof T]: T[K] extends string | number ? T[K] | null : T[K];
          
        };
        
        
        type UserWithNullable = Nullable<User>;
        
        
     // Equivalent to:
        
        type UserWithNullable2 = {
          
          name: string | null;
          
          age: number | null;
          
        };        
      
You can combine both to create powerful types. The above example makes string and number properties nullable.
        type User = {
          
          name: string;
          
          age: number;
          
        };
        
        
        type PartialUser = Partial<User>;
        
        
     // Equivalent to:
        
        type PartialUser2 = {
          
          name?: string;
          
          age?: number;
          
        };        
      
The Partial<T> utility makes all properties of a type optional.
        type UserOptional = {
          
          name?: string;
          
          age?: number;
          
        };
        
        
        type RequiredUser = Required<UserOptional>;
        
        
     // Equivalent to:
        
        type RequiredUser2 = {
          
          name: string;
          
          age: number;
          
        };        
      
The Required<T> utility makes all properties required, even if they were optional. It ensures objects always have complete data.
        type ReadonlyUser = Readonly<User>;
        
        
     // Equivalent to:
        
        type ReadonlyUser2 = {
          
          readonly name: string;
          
          readonly age: number;
          
        };
        
        
        const user: ReadonlyUser = { name: "John", age: 30 };
        
        user.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
      
The Readonly<T> utility makes all properties immutable. It prevents accidental modifications to objects.
        type UserBasicInfo = Pick<User, "name">;
        
        
     // Equivalent to:
        
        type UserBasicInfo2 = {
          
          name: string;
          
        };
        
        
        function getUserInfo(user: Pick<User, "name">) {
          
          console.log(`User name: ${user.name}`);
          
        }
      
The Pick<T, K> utility extracts specific properties from a type. Useful when working with APIs or displaying only selected data.
        type UserWithoutAge = Omit<User, "age">;
        
        
     // Equivalent to:
        
        type UserWithoutAge2 = {
          
          name: string;
          
        };
      
The Omit<T, K> utility removes specific properties from a type. It helps remove sensitive or unnecessary fields.
        type Role = "admin" | "user" | "guest";
        
        type Permissions = "read" | "write" | "delete";
        
        
        type RolePermissions = Record<Role, Permissions[]>;
        
        
     // Equivalent to:
        
        type RolePermissions2 = {
          
          admin: Permissions[];
          
          user: Permissions[];
          
          guest: Permissions[];
          
        };
        
        
        const permissions: RolePermissions = {
          
          admin: ["read", "write", "delete"],
          
          user: ["read"],
          
          guest: ["read"],
          
        };        
      
The Record<K, T> utility creates an object type where the keys are K and values are T. It helps define object maps with predefined keys.
        type Status = "success" | "error" | "pending";
        
        
        type ExcludedStatus = Exclude<Status, "pending">;
        
        
     // Equivalent to:
        
        type ExcludedStatus2 = "success" | "error";
      
The Exclude<T, U> utility removes types from a union. It helps remove unwanted values from a union type.
        type NullableString = string | null | undefined;
        
        type NonNullString = NonNullable<NullableString>;
        
        
     // Equivalent to:
        
        type NonNullString2 = string;
      
The NonNullable<T> utility removes null and undefined from a type. It ensures values are always defined.
        function getUser(): User {
          
          return { name: "Alice", age: 25 };
          
        }
        
        
        type UserReturnType = ReturnType<typeof getUser>;
        
        
     // Equivalent to:
        
        type UserReturnType2 = {
          
          name: string;
          
          age: number;
          
        };        
      
The ReturnType<T> utility extracts the return type of a function. It helps when inferring return types dynamically.
        function logMessage(message: string, count: number) {}
        
        
        type LogParams = Parameters<typeof logMessage>;
        
        
     // Equivalent to:
        
        type LogParams2 = [string, number];
      
The Parameters<T> utility extracts the parameter types of a function as a tuple. It helps when reusing function parameter types.
        function identity<T>(value: T): T {
          
          return value;
          
        }
        
        
        console.log(identity<string>("Hello")); // "Hello"
        
        console.log(identity<number>(42)); // 42
        
        console.log(identity<boolean>(true)); // TrueType
        
        
        console.log(identity("Auto Type")); // No need to specify <string>
      
Generics act as placeholders for types that will be determined later.
        function pair<T, U>(first: T, second: U): [T, U] {
          
          return [first, second];
          
        }
        
        
        const result = pair("Alice", 25); // result: [string, number]        
      
You can use generics in functions to ensure type safety. Works with multiple generic types <T, U>.
        interface Box<T> {
          
          content: T;
          
        }
        
        
        const stringBox: Box<string> = { content: "Hello" };
        
        const numberBox: Box<number> = { content: 100 };        
      
You can use generics in interfaces for flexible structures. In the above example the Box<T> interface can hold any type.
        class Storage<T> {
          
          private items: T[] = [];
          
          
          add(item: T) {
            
            this.items.push(item);
            
          }
          
          
          getAll(): T[] {
            
            return this.items;
            
          }
          
        }
        
        
        const numberStorage = new Storage<number>();
        
        numberStorage.add(42);
        
        console.log(numberStorage.getAll()); // [42]
        
        
        const stringStorage = new Storage<string>();
        
        stringStorage.add("TypeScript");
        
        console.log(stringStorage.getAll()); // ["TypeScript"]        
      
Classes can also use generics. In this example the class works for any type, ensuring type safety.
        function getLength<T extends { length: number }>(item: T): number {
          
          return item.length;
          
        }
        
        
        console.log(getLength("Hello")); // Works (string has .length)
        
        console.log(getLength([1, 2, 3])); // Works (array has .length)
        
        console.log(getLength(42)); // Error: number doesn't have .length        
      
Sometimes, you need to restrict the types that can be used. In this example T extends { length: number } ensures only types with a .length property can be used.
        function getProperty<T, K extends keyof T>(obj: T, key: K) {
          
          return obj[key];
          
        }
        
        
        const user = { name: "Alice", age: 25 };
        
        
        console.log(getProperty(user, "name")); // "Alice"
        
        console.log(getProperty(user, "age")); // 25
        
        console.log(getProperty(user, "email")); // Error: "email" is not a valid key        
      
You can access object properties dynamically. In the example above K extends keyof T ensures only valid object keys can be accessed.
        interface Response<T = string> {
          
          data: T;
          
        }
        
        
        const success: Response = { data: "Success" }; // Defaults to string
        
        const numbers: Response<number[]> = { data: [1, 2, 3] }; // Uses number[]        
      
You can provide default types for generics. In this example the T = string sets string as the default type.
        function swap<T, U>(a: T, b: U): [U, T] {
          
          return [b, a];
          
        }
        
        
        console.log(swap("Hello", 42)); // [42, "Hello"]        
      
Generics make utility functions more powerful. In this example the function swaps two different types.
        interface ApiResponse<T> {
          
          status: number;
          
          data: T;
          
        }
        
        
        const userResponse: ApiResponse<{ name: string; age: number }> = {
          
          status: 200,
          
          data: { name: "Alice", age: 25 },
          
        };        
      
Generics work well in complex structures. It helps with type-safe API responses.
        type Pair<T, U> = [T, U];
        
        
        const example: Pair<string, number> = ["Alice", 25];
      
You can define generic types using type. It works similarly to generic interfaces.
     // @ts-ignore
        
        const value: number = "Hello"; // No TypeScript error
      
This tells TypeScript to ignore errors on the next line. Use sparingly, as it can hide genuine issues.
     // @ts-nocheck
        
        const x = "hello";
        
        x.toFixed(2); // No error, even though x is a string
      
Place this at the top of a file to disable TypeScript type checking for the whole file. Useful for large third-party scripts or when migrating JavaScript to TypeScript.
     // @ts-expect-error
        
        const num: number = "Hello"; // This is expected to be an error
      
Unlike @ts-ignore, this will cause an error if TypeScript doesn't find an issue. Helps prevent unnecessary ignores.
        const value: any = "Hello";
        
        console.log((value as any).toFixed(2)); // No TypeScript error
        
        
        const data: unknown = "Some string";
        
        console.log((data as any).toFixed(2)); // Works, but risky
      
Casting to any disables type checking. Casting to unknown retains some safety.
The declare keyword in TypeScript is used to tell the compiler about the existence of variables, functions, classes, or modules that TypeScript doesn't have type information for. It helps when dealing with global variables, third-party JavaScript libraries, or ambient declarations without modifying existing code
     // Declare a global variable that TypeScript doesn't know about
        
        declare const API_BASE_URL: string;
        
        
        console.log(API_BASE_URL); // No TypeScript error
      
When working with JavaScript code that defines a global variable but has no TypeScript definitions, you can use declare to inform TypeScript that the variable exists. declare does not generate actual JavaScript code; it just informs the TypeScript compiler.
     // Assume this function exists in an external JS file but isn't defined in TypeScript
        
        declare function fetchData(url: string): Promise
        
        fetchData("https://api.example.com").then(console.log); // No TypeScript error
      
If you are using a JavaScript file with functions that TypeScript doesn't recognize, you can declare them.
     // tax-lib.d.ts:
        
        declare module "tax-lib" {
          
          export function calculateTax(amount: number, rate: number): number;
          
        }
        
        
     // main.ts:
        
        import { calculateTax } from "tax-lib";
        
        
        const tax = calculateTax(100, 0.2);
        
        console.log(tax); // 20
      
A TypeScript Declaration File (.d.ts) provides type definitions for existing JavaScript code. In this example a JavaScript library exposes a function called calculateTax, which has no TypeScript support, so we create a declaration file (tax-lib.d.ts) for it where we can provide our own types for the module's variables, functions, methods etc.
     // Extend the global Window object
        
        declare global {
          
          interface Window {
            
            appVersion: string;
            
          }
          
        }
        
        
     // Now you can safely use window.appVersion
        
        window.appVersion = "1.0.0";
        
        console.log(window.appVersion);
      
If you need to add additional properties to an existing type (e.g., adding properties to Window in the browser), use declare.
        {
          
          "compilerOptions": {
            
            "strict": false
            
          }
          
        }        
      
You can disable strict type checking in tsconfig.json, but this is not recommended as it reduces TypeScript's benefits.
     // Initialize a Node.js project
        
        npm init -y
        
        
     // Install TypeScript and required packages
        
        npm install --save-dev typescript ts-node @types/node
        
        
     // Initialize TypeScript - generate a tsconfig.json file
        
        npx tsc --init
      
Setup TypeScript in a Node.js project
        {
          
          "compilerOptions": {
            
            "target": "ES2022",
            
            "module": "CommonJS",
            
            "strict": true,
            
            "outDir": "./dist",
            
            "rootDir": "./src",
            
            "esModuleInterop": true,
            
            "resolveJsonModule": true,
            
            "skipLibCheck": true
            
          },
          
          "include": ["src"],                   
          
          "exclude": ["node_modules", "dist"]   
          
        }        
      
Modify the generated tsconfig.json for best TypeScript support.
     // Install dotenv
        
        npm install dotenv
        
        
     // Create a .env file
        
        PORT=5000
        
        API_KEY=abcdef
        
        
     // Create an src/config.ts files
        
        import dotenv from "dotenv";
        
        dotenv.config();
        
        export const PORT = process.env.PORT || 3000;
        
        export const API_KEY = process.env.API_KEY || "";
        
        
     // Use in src/index.ts
        
        import { PORT, API_KEY } from "./config";
        
        console.log(`Server running on port ${PORT}`);
        
        console.log(`API Key: ${API_KEY}`);
      
Handles environment variables in TypeScript. Avoids hardcoding secrets & makes configuration flexible. Make sure .env is in .gitignore to prevent leaks.
        "scripts": {
          
          "dev": "ts-node src/index.ts",
          
          "build": "tsc",
          
          "start": "node dist/index.js"
          
        }        
      
To automate tasks, update package.json. You can run scripts:
        // Install nodemon
        
        npm install --save-dev nodemon
        
        
        // Run script and restart if changes occur
        
        nodemon src/index.ts
      
The script can be rerun if changes occur using nodemon.
        This document's home with other cheat sheets you might be interested in:
        
        https://gitlab.com/davidvarga/it-cheat-sheets
      
        Sources:
        
        https://www.wikipedia.org/
        
        https://stackoverflow.com/
        
        https://www.w3schools.com/
        
        https://www.typescriptlang.org/
        
      
        License:
        
        GNU General Public License v3.0 or later