How to perform type casting in TypeScript
Tags: typescript, webdev
Published: 2023-08-08 14:06:22

**Written by [Paul Akinyemi](https://blog.logrocket.com/author/paulakinyemi/)✏️**

TypeScript introduces a robust type system that enables developers to define and enforce types for variables, function parameters, return values, and more. TypeScript’s type system provides static type checking, allowing you to identify and prevent potential errors before runtime. 

Type casting is a feature in TypeScript that allows developers to explicitly change the type of a value from one type to another. Type casting is particularly useful when you’re working with dynamic data, or when the type of a value is not correctly inferred automatically. 

In this article, we’ll explore the ins and outs of type casting in TypeScript. To follow along, you’ll need working knowledge of TypeScript and object-oriented programming. 

_Jump ahead:_

*   [Understanding type casting](#understanding-type-casting)
*   [Type casting in TypeScript with the `as` operator](#type-casting-typescript-as-operator)
*   [Limitations of the `as` operator](#limitations-as-operator)
*   [When TypeScript won't allow `as` casting](#typescript-wont-allow-as-casting)
*   [Discriminated unions](#discriminated-unions)
*   [Non-casting: The `satisfies` operator](#non-casting-satisfies-operator)

## Understanding type casting <a name="understanding-type-casting">

Type casting can happen in one of two ways: it can be implicit, which is when TypeScript handles the operation, or explicit, when the developer handles the conversion. Implicit casting occurs when TypeScript sees a type error and attempts to safely correct it. 

Type casting is essential for performing various operations, including arithmetic calculations, and data, manipulation, and compatibility checks. But before you can start using type casting effectively, you’ll need to understand some foundational concepts like subtype and supertype relationships, type widening, and type narrowing.

### Subtypes and supertypes

One way to classify types is to split them into sub- and supertypes. Generally, a subtype is a specialized version of a supertype that inherits the supertype’s attributes and behaviors. A supertype, on the other hand, is a more general type that is the basis of multiple subtypes. 

Consider a scenario where you have a class hierarchy with a superclass called "Animal" and two subclasses named "Cat" and "Dog." Here, "Animal" is the supertype, while "Cat" and "Dog" are the subtypes. Type casting comes in handy when you need to treat an object of a particular subtype as its supertype or vice versa.

### Type widening: From subtype to supertype

Type widening, or upcasting, occurs when you need to convert a variable from a subtype to a supertype. Type widening is usually implicit, meaning that it is performed by TypeScript, because it involves moving from a narrow category to a broader one. Type widening is safe, and it won’t cause any errors, because a subtype inherently possesses all the attributes and behaviors of its supertype.

### Type narrowing: From supertype to subtype

Type narrowing, or downcasting, occurs when you convert a variable from a supertype to a subtype. Type narrowing conversion is explicit and it requires a type assertion or a type check to ensure the validity of the conversion. This process can be risky because not all supertype variables hold values that are compatible with the subtype.

## Type casting in TypeScript with the `as` operator <a name="type-casting-typescript-as-operator">

The `as` operator is TypeScript's primary mechanism for explicit type casting. With its intuitive syntax, `as` allows you to inform the compiler about the intended type of a variable or expression. 

Below is the general form of the `as` operator:

```typescript
value as Type
```

Here, `value` represents the variable or expression you can cast, while `Type` denotes the desired target type. By using `as`, you explicitly assert that `value` is of type `Type`. 

The `as` operator is useful when you’re working with types that have a common ancestor, including class hierarchies or interface implementations. It allows you to indicate that a particular variable should be treated as a more specific subtype. Here’s some code to illustrate:

```typescript
class Animal {
  eat(): void {
    console.log('Eating...');
  }
}

class Dog extends Animal {
  bark(): void {
    console.log('Woof!');
  }
}

const animal: Animal = new Dog();
const dog = animal as Dog;
dog.bark(); // Output: "Woof!"
```

In this code, the `Dog` class extends the `Animal` class. The `Dog` instance is assigned to a variable `animal` of type `Animal`. By using the `as` operator, you cast `animal` as `Dog`, allowing you to access the `bark()` method specific to the `Dog` class. The code should output this: ![Type Casting With The As Operator To Access The Bark Method Specific To The Dog Class In Our Code](http://blog.logrocket.com/wp-content/uploads/2023/07/type-casting-as-operator.png) 

You can use the `as` operator to cast to specific types. This capability comes in handy when you need to interact with a type that differs from the one inferred by TypeScript's type inference system. Here's an example:

```typescript
function getLength(obj: any): number {
  if (typeof obj === 'string') {
    return (obj as string).length;
  } else if (Array.isArray(obj)) {
    return (obj as any[]).length;
  }
  return 0;
}
```

The `getLength` function accepts a parameter `obj` of type `any`. In the `getLength` function, the `as` operator casts `obj` to a string for `any[]` based on its type. This operation gives you access to the `length` property specific to strings or arrays respectively. 

Additionally, you can cast to a Union type to express that a value can be one of several types:

```typescript
function processValue(value: string | number): void {
  if (typeof value === 'string') {
    console.log((value as string).toUpperCase());
  } else {
    console.log((value as number).toFixed(2));
  }
}
```

The `processValue` function accepts a parameter `value` of type `string | number`, indicating that it can be a string or a number. By using the `as` operator, you cast `value` to `string` or `number` within the respective conditions, allowing you to apply type-specific operations such as `toUpperCase()` or `toFixed()`.

## Limitations of the `as` operator <a name="limitations-as-operator">

While the `as` operator is a powerful tool for type casting in TypeScript, it has some limitations. One limitation is that `as` operates purely at compile-time and does not perform any runtime checks. This means that if the casted type is incorrect, it may result in runtime errors. So, it is crucial to ensure the correctness of the type being cast. 

Another limitation of the `as` operator is that you can’t use it to cast between unrelated types. TypeScript's type system provides strict checks to prevent unsafe casting, ensuring type safety throughout your codebase. In such cases, consider alternative approaches, such as type assertion functions or type guards.

## When TypeScript won't allow `as` casting <a name="typescript-wont-allow-as-casting">

There are instances when TypeScript raises objections and refuses to grant permission for `as` casting. Let’s look at some situations that might cause this.

### Structural incompatibility

TypeScript's static type checking relies heavily on the structural compatibility of types. When you try to cast a value with the `as` operation, the compiler assesses the structural compatibility between the original type and the desired type. 

If the structural properties of the two types are incompatible, TypeScript will raise an error, signaling that the casting operation is unsafe. Here’s an example of type casting with structural incompatibility errors:

```typescript
interface Square {
  sideLength: number;
}

interface Rectangle {
  width: number;
  height: number;
}

const square: Square = { sideLength: 5 };
const rectangle = square as Rectangle; // Error: Incompatible types
```

TypeScript prevents the `as` casting operation because a square and a rectangle have different structural properties. Instead of relying on the `as` operator casting, a safer approach would be to create a new instance of the desired type, then manually assign the corresponding values.

### Union types

Union types in TypeScript allow you to define a value that can be multiple types. However, when attempting to cast a union type with the `as` operator, it is required that the desired type be one of the constituent types of the union. If the desired type is not included in the union, TypeScript won’t allow the casting operation:

```typescript
type Shape = Square | Rectangle;

const shape: Shape = { sideLength: 5 };
const rectangle = shape as Rectangle; // Error: Type 'Shape' is not assignable to type 'Rectangle'
```

You can handle this situation with type guards that narrow down the type of the variable based on runtime checks. By employing type guards, you can handle the various possible types within the union and perform operations accordingly.

### Type assertion limitations

Type assertions, denoted with the `as` keyword, provide functionality for overriding the inferred or declared type of a value. However, TypeScript has certain limitations on type assertions. Specifically, TypeScript prohibits `as` casting when narrowing a type through control flow analysis:

```typescript
function processShape(shape: Shape) {
  if ("width" in shape) {
    const rectangle = shape as Rectangle;
    // Process rectangle
  } else {
    const square = shape as Square;
    // Process square
  }
}
```

TypeScript will raise an error because it cannot narrow the type of `shape` based on the type assertions. To overcome this limitation, you can introduce a new variable within each branch of the control flow:

```typescript
function processShape(shape: Shape) {
  if ("width" in shape) {
    const

 rectangle: Rectangle = shape;
    // Process rectangle
  } else {
    const square: Square = shape;
    // Process square
  }
}
```

By assigning the type assertion directly to a new variable, TypeScript can correctly infer the narrowed type.

## Discriminated unions <a name="discriminated-unions">

A discriminated union is a type that represents a value that can be of several possibilities. Discriminated unions combine a set of related types under a common parent, where each child type is uniquely identified by a discriminant property. This discriminant property serves as a literal type that allows TypeScript to perform exhaustiveness checking:

```typescript
type Circle = {
  kind: 'circle';
  radius: number;
};

type Square = {
  kind: 'square';
  sideLength: number;
};

type Triangle = {
  kind: 'triangle';
  base: number;
  height: number;
};

type Shape = Circle | Square | Triangle;
```

You’ve defined three shape types: Circle, Square, and Triangle, all collectively forming the discriminated union Shape. The `kind` property is the discriminator, with a literal value representing each shape type. 

Discriminated unions become even more powerful when you combine them with type guards. A type guard is a runtime check that allows TypeScript to narrow down the possible types within the union based on the discriminant property. 

Consider this function that calculates the area of a shape:

```typescript
function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      throw new Error('Invalid shape!');
  }
}
```

TypeScript leverages the discriminant property, `kind`, in the `switch` statement to perform exhaustiveness checking. If you accidentally omit a case, TypeScript will raise a compilation error, reminding you to handle all possible shape types.

### Type casting and discriminators

You can use discriminated unions for type casting. Imagine a scenario where you have a generic `response` object that can be one of two types: `Success` or `Failure`. You can use a discriminant property, `status`, to differentiate between the two and perform type assertions accordingly:

```typescript
type Success = {
  status: 'success';
  data: unknown;
};

type Failure = {
  status: 'failure';
  error: string;
};

type APIResponse = Success | Failure;

function handleResponse(response: APIResponse) {
  if (response.status === 'success') {
    // Type assertion: response is of type Success
    console.log(response.data);
  } else {
    // Type assertion: response is of type Failure
    console.error(response.error);
  }
}

const successResponse: APIResponse = {
  status: 'success',
  data: 'Some data',
};

const failureResponse: APIResponse = {
  status: 'failure',
  error: 'An error occurred',
};

handleResponse(successResponse); // Logs: Some data
handleResponse(failureResponse); // Logs: An error occurred
```

The `status` property is the discriminator in the program above. TypeScript narrows down the type of the `response` object based on the `status` value, allowing you to safely access the respective properties without the need for explicit type checks: ![TypeScript Discriminators Allowing You To Access Properties Without Explicit Type Checks](http://blog.logrocket.com/wp-content/uploads/2023/07/typescript-discriminator.png)

## Non-casting: The `satisfies` operator <a name="non-casting-satisfies-operator">

The `satisfies` operator is a new feature in TypeScript 4.9 that allows you to check whether an expression's type matches another type without casting the expression. This can be useful for validating the types of your variables and expressions without changing their original types. 

Here’s the syntax for using the `satisfies` operator:

```typescript
expression satisfies type
```

And here’s a program that checks if a variable is greater than five with the `satisfies` operator:

```typescript
const number = 10;
number satisfies number > 5;
```

The `satisfies` operator will return `true` if the expression’s type matches, and `false` if otherwise. 

The `satisfies` operator is a powerful tool for improving the type safety of your TypeScript code. It is a relatively new feature, so it is not yet as widely used as other TypeScript features. However, it is a valuable tool that can help you to write more reliable and maintainable code.

## Conclusion

In this article, you learned about the various ways to can perform type casting in TypeScript. Type casting is particularly useful when working with dynamic data, or when the type of a value is not inferred correctly. With type casting, you can efficiently improve the type safety of your programs.

---

##[LogRocket](https://lp.logrocket.com/blg/typescript-signup): Full visibility into your web and mobile apps

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/typescript-signup)

[LogRocket](https://lp.logrocket.com/blg/typescript-signup) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

[Try it for free](https://lp.logrocket.com/blg/typescript-signup).