Typescript Working With Multiple Types Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of TypeScript Working with Multiple Types

TypeScript Working with Multiple Types

TypeScript offers a powerful type system that allows developers to work with multiple types in a variety of ways, promoting flexibility and precision in code. Understanding how TypeScript handles multiple types is essential for writing robust, type-safe code. This section will delve into the details of how TypeScript allows different types to coexist and interact seamlessly.

Union Types

Description:

Union types allow you to express a value that can be one of several types. You use the pipe (|) character to separate the different types. For example, number | string means that the value can be either a number or a string.

Syntax:

let value: number | string;
value = 42;       // Valid
value = "Hello";  // Valid
value = true;     // Error, boolean is not a valid type

Use Cases:

Union types are particularly useful when dealing with functions that can accept multiple types:

function printId(id: number | string) {
    console.log(`ID: ${id}`);
}
printId(101);        // Valid
printId("user1");    // Valid

Intersection Types

Description:

Intersection types allow you to combine multiple types into a single type. This is achieved using the ampersand (&) character. The resulting type will have all the properties and methods of the types being intersected.

Syntax:

interface Person {
    name: string;
}

interface Employee {
    jobTitle: string;
}

type EmployeeDetails = Person & Employee;

let employee: EmployeeDetails = {
    name: "Alice",
    jobTitle: "Developer"
};

Use Cases:

Intersection types are useful for creating flexible and reusable types that combine the properties of several related types:

function extendPerson(person: Person, employee: Employee): EmployeeDetails {
    return {
        ...person,
        ...employee
    };
}

Type Guards

Description:

Type guards are used to ensure that the type of a variable within a specific block of code is known. TypeScript provides several ways to create type guards, such as typeof checks, instanceof checks, and custom type predicates.

Syntax:

function printLength(value: string | number) {
    if (typeof value === "string") {
        console.log(`Length: ${value.length}`);
    } else {
        console.log(`Number: ${value}`);
    }
}

Use Cases:

Type guards are crucial for working with union types, allowing you to safely perform operations based on the specific type of the variable:

function isString(value: any): value is string {
    return typeof value === "string";
}

function processInput(input: string | number) {
    if (isString(input)) {
        console.log(`String: ${input.toUpperCase()}`);
    } else {
        console.log(`Number: ${input * 2}`);
    }
}

Generics with Multiple Types

Description:

Generics in TypeScript are used to create components and functions that can work over a variety of types while also being type-safe. You can use generics with multiple types to create more flexible and reusable code.

Syntax:

function merge<T, U>(obj1: T, obj2: U): T & U {
    return Object.assign({}, obj1, obj2);
}

const person = { name: "Alice" };
const employee = { jobTitle: "Developer" };

const result = merge(person, employee);
console.log(result.name);       // "Alice"
console.log(result.jobTitle); // "Developer"

Use Cases:

Generics with multiple types are useful for creating utility functions and data structures that can handle various input types while maintaining type safety:

interface Tagged<T> {
    tag: string;
    data: T;
}

function createTagged<T>(tag: string, data: T): Tagged<T> {
    return { tag, data };
}

const taggedNumber = createTagged<number>("number", 42);
const taggedString = createTagged<string>("string", "Hello");

Key Takeaways

  • Union Types (|): Allow variables to hold values of multiple types, enhancing flexibility.
  • Intersection Types (&): Combine multiple types into one, useful for creating composite types.
  • Type Guards: Ensure type safety in union types by checking and narrowing down the type within control flow.
  • Generics: Create reusable components and functions that can operate across various types while maintaining type safety.

Conclusion:

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement TypeScript Working with Multiple Types

Union Types

Union types allow a variable or a function parameter to hold multiple different types.

Example 1: Basic Union Type

Step 1: Define a function that accepts either a string or a number.

// Define a function that can accept either a string or a number
function printId(id: string | number) {
    console.log("Your ID is: " + id);
}

// Call the function with different types
printId("ABC"); // Output: Your ID is: ABC
printId(123);   // Output: Your ID is: 123

Step 2: Narrow the types inside the function.

Sometimes, it's necessary to know which type is being handled. You can do this using type guards.

function printIdWithNarrowing(id: string | number) {
    if (typeof id === "string") {
        console.log("Your ID is a string: " + id.toUpperCase());
    } else {
        console.log("Your ID is a number: " + id.toFixed(2));
    }
}

// Call the function with different types
printIdWithNarrowing("abc"); // Output: Your ID is a string: ABC
printIdWithNarrowing(456);   // Output: Your ID is a number: 456.00

Example 2: Union Type in Arrays

Step 1: Create a mixed-type array.

// Define an array that can hold either strings or numbers
const data: (string | number)[] = ["apple", 2, "banana", 4];

// Loop through the array and print each elements
data.forEach(item => {
    console.log(item);
});
// Output:
// apple
// 2
// banana
// 4

Intersection Types

Intersection types combine multiple types into one, creating a new type that has all the properties of each of the types involved.

Example 1: Basic Intersection Type

Step 1: Define two separate interfaces.

interface Employee {
    employeeId: number;
    name: string;
}

interface Manager {
    managerId: number;
    department: string;
}

Step 2: Combine those interfaces into one intersection type.

// Define an intersection type that merges both Employee and Manager properties
type ManagerEmployee = Employee & Manager;

const ceo: ManagerEmployee = {
    employeeId: 1,
    name: "Alice Smith",
    managerId: 1,
    department: "General Management"
};

console.log(ceo.name + " works in the " + ceo.department); 
// Output: Alice Smith works in the General Management

Example 2: Intersection Types in Classes

Step 1: Define two separate classes.

Using intersection types doesn’t directly work on classes, however, you can use the concept in inheritance.

class Vehicle {
    drive() {
        console.log("Driving...");
    }
}

class Boat {
    sail() {
        console.log("Sailing...");
    }
}

class Amphicar extends Vehicle implements Boat {
    sail() {
        console.log("Amphicar sailing...");
    }

    swim() {
        console.log("Amphicar swimming...");
    }
}

const myVehicle: Amphicar = new Amphicar();
myVehicle.drive(); // Output: Driving...
myVehicle.sail();  // Output: Amphicar sailing...
myVehicle.swim();  // Output: Amphicar swimming...

Step 2: Mix behaviors at runtime:

Instead, intersection types can be used when dealing with objects and functions at runtime.

const vehicleInstance: Vehicle = new Vehicle();
const boatInstance: Boat = {
    sail: () => {
        console.log("Boat sailing...");
    }
}

const combinedVehicle = Object.assign({}, vehicleInstance, boatInstance);

function operate(vehicle: Vehicle & Boat) {
    vehicle.drive();
    vehicle.sail();
}

operate(combinedVehicle);
// Output:
// Driving...
// Boat sailing...

Type Guards

Type guards are a pattern of working with union types that leverages type predicates to narrow the type of variables during runtime.

Example 1: Type Guard Function

Step 1: Define an interface and a function that checks if an object adheres to that interface.

interface Fish {
    swim: () => void;
}

interface Bird {
    fly: () => void;
}

// Type guard function
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

Step 2: Use this type guard to safely work with the union types.

const fish: Fish = {
    swim: () => {
        console.log("Fish swimming...");
    }
}

const bird: Bird = {
    fly: () => {
        console.log("Bird flying...");
    }
}

function interactWithPet(pet: Fish | Bird) {
    if (isFish(pet)) {
        // Inside here, TypeScript knows that 'pet' is of type 'Fish'
        pet.swim();
    } else {
        // Inside here, TypeScript knows that 'pet' is of type 'Bird'
        pet.fly();
    }
}

interactWithPet(fish); // Output: Fish swimming...
interactWithPet(bird); // Output: Bird flying...

Example 2: in Keyword as Type Guard

You can also use the in keyword as a type guard to check if a particular property exists in an object.

function move(pet: Fish | Bird) {
    if ("swim" in pet) {
        // Inside here, TypeScript knows that 'pet' must be of type 'Fish'
        pet.swim();
    } else {
        // Inside here, TypeScript knows that 'pet' must be of type 'Bird'
        pet.fly();
    }
}

move(fish); // Output: Fish swimming...
move(bird); // Output: Bird flying...

Example 3: Discriminated Unions

A powerful pattern in TypeScript is to have a common field among union types that helps identify which variant of the type is currently being used.

Step 1: Define the discriminated union.

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type Shape = Circle | Square;

Step 2: Use the switch statement to narrow down the type.

Top 10 Interview Questions & Answers on TypeScript Working with Multiple Types

Top 10 Questions and Answers: TypeScript Working with Multiple Types

1. What are union types in TypeScript, and how do you use them?

let id: number | string;
id = 101;
id = "TS101";

In this example, the variable id can be either a number or a string.

2. Can you explain intersection types in TypeScript?

Answer: Intersection types allow you to combine multiple types into one. An intersection type contains all members of the types it intersects. You use the & operator to define intersection types:

type Employee = {
  name: string;
  id: number;
};

type Engineer = {
  skills: string[];
};

type FullTimeEmployee = Employee & Engineer;

const emp: FullTimeEmployee = {
  name: "John",
  id: 202,
  skills: ["TypeScript", "React"]
};

Here, FullTimeEmployee requires a combination of properties from both Employee and Engineer.

3. How do you create a type guard in TypeScript?

Answer: Type guards help TypeScript narrow down the type within a conditional block. You can implement a user-defined type guard by defining a function that returns a type predicate (parameterName is Type):

function isString(input: any): input is string {
  return typeof input === "string";
}

let sample: string | number = "Hello";

if (isString(sample)) {
  // TypeScript now knows that 'sample' is a string within this block
  console.log(sample.toUpperCase());
}

4. Can you provide an example of using the typeof type guard in TypeScript?

Answer: Yes, the typeof operator can be used as a type guard to narrow down the type of a variable:

function printValue(value: string | number) {
  if (typeof value === "string") {
    console.log(`String value: ${value}`);
  } else if (typeof value === "number") {
    console.log(`Number value: ${value}`);
  }
}

printValue("Hello");  // Output: String value: Hello
printValue(42);       // Output: Number value: 42

5. What are literal types in TypeScript, and how do they work with union types?

Answer: Literal types restrict a variable to a specific value. When combined with union types, literals allow for defining a finite set of acceptable values:

type Direction = "Up" | "Down" | "Left" | "Right";

let move: Direction;

move = "Up";    // Valid
move = "Right"; // Valid
// move = "Diagonal"; // Error: Type '"Diagonal"' is not assignable to type 'Direction'

6. How can you use the in operator as a type guard in TypeScript?

Answer: The in operator checks if a property exists within an object, serving as a type guard:

type Fish = {
  swim: () => void;
};

type Bird = {
  fly: () => void;
};

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim();
  }
  return animal.fly();
}

7. What are the benefits of using type aliases in TypeScript?

Answer: Type aliases provide a way to name a type for easier reference and readability. Benefits include:

  • Reusability: Create a reusable definition.
  • Simplification: Simplify complex types.
  • Readability: Make type definitions more understandable.

Example:

type UserID = number | string;

function printUserId(id: UserID) {
  console.log(id);
}

printUserId(101);        // Number type
printUserId("TS101");    // String type

8. Can you explain the differences between interface and type in TypeScript and when to use each?

Answer: Both interface and type in TypeScript can define object shapes, but they have key differences:

  • Extending: Interfaces can be extended using the extends keyword, while types are extended using intersections (&).
  • Declaration Merging: Interfaces can be declared multiple times and merged, while types cannot.

Interface Example:

interface User {
  name: string;
}

interface Admin extends User {
  role: string;
}

Type Example:

type User = {
  name: string;
};

type Admin = User & {
  role: string;
};

Use interfaces when you need declaration merging or intend to use extends. Use types for utility types or when creating simple object shapes.

9. How do you work with optional types in TypeScript?

Answer: Optional types are used to denote that a property or parameter may or may not be present. In TypeScript, you achieve this by appending a question mark (?) to the property name in an interface or object type:

interface Person {
  firstName: string;
  lastName?: string;
}

const person1: Person = { firstName: "John" };          // Valid
const person2: Person = { firstName: "John", lastName: "Doe" };  // Also valid

10. Can you explain how to use tuple types in TypeScript, and why they are useful?

Answer: Tuple types allow you to define an array with a fixed number of elements, where each element may have a different type. Tuples are useful for arrays of fixed sizes with known types for each index:

type UserLogin = [string, number];  // [username, userId]

const johnDoe: UserLogin = ["john.doe", 101];

function loginUser(loginDetails: UserLogin) {
  console.log(`Logged in user: ${loginDetails[0]}, ID: ${loginDetails[1]}`);
}

loginUser(johnDoe);

Tuple example:

type Response = [number, string, boolean];

const fetchData = (): Response => [200, "Success", true];

Tuples are particularly handy in APIs that return fixed-size arrays of varying types, enhancing readability and type safety.


You May Like This Related .NET Topic

Login to post a comment.