Open In App

TypeScript Structural Typing

Last Updated : 07 Aug, 2024
Summarize
Comments
Improve
Suggest changes
Like Article
Like
Share
Report
News Follow

TypeScript type system is based on structural typing, therefore, TypeScript’s type compatibility is determined by the structure (i.e., the properties and methods) rather than explicit declarations or names. In this article, we will learn in detail about TypeScript Structural Typing.

What is Structural Typing?

Structural typing is a type system in which type compatibility and equivalence are determined by the actual structure (or shape) of the data, rather than explicit declarations. In other words, two types are considered compatible if they have the same properties and methods with matching types, regardless of the names of the types

Principles of Structural Typing

  1. Shape Compatibility: Two shapes are compatible if the structures of the two match and this includes properties and methods as well as their respective types.
  2. Extra Properties Are Allowed: An object can have extra properties that were not initially defined in a type but, it can be still assigned to that type.
  3. Parameter Bivariance: For function types, an extra level of specificity or greater generality with respect to parameter types is allowed provided that the remaining components in the function signature are fitting.

Example 1: Basic Object Compatibility

In this example Point3D has z as an additional property when compared with Point2D and nevertheless point3D may refer to another Point2D because Point3D contains all necessary properties (x and y) to be considered a Point2D.

interface Point2D {
    x: number;
    y: number;
}

interface Point3D {
    x: number;
    y: number;
    z: number;
}

const point2D: Point2D = { x: 1, y: 2 };
const point3D: Point3D = { x: 1, y: 2, z: 3 };

// Assigning Point3D to Point2D
const anotherPoint2D: Point2D = point3D;

console.log(anotherPoint2D); // Output: { x: 1, y: 2, z: 3 }

Output

{ x: 1, y: 2, z: 3 }

Explanation : The output of console.log(anotherPoint2D) is { x: 1, y: 2, z: 3 } because point3D includes all properties of Point2D plus an extra z property. TypeScript allows this assignment based on structure, but the full point3D object is logged.

Example 2: Function Compatibility

Function Types also comply with structural typing rules, the compatibility of a function depends on its parameter and return types only. However, functions with additional parameters or different return types can affect compatibility.

Here , Sum is a function type that takes two parameters, a and b, and returns a number. The extendedSum function, while having the same parameters as Sum, is still compatible because it does not add any new parameters beyond what Sum expects.

type Sum = (a: number, b: number) => number;

const sum: Sum = (a, b) => a + b;

const extendedSum = (a: number, b: number, c: number) => a + b + c;

// Assigning extendedSum to newSum
const newSum: Sum = (a: number, b: number) => extendedSum(a, b, 0);

console.log(newSum(1, 2));


Output

3

Explanation : The output of console.log(newSum(1, 2)); is 3 because newSum calls extendedSum with the extra parameter c set to 0. Thus, extendedSum(1, 2, 0) computes 1 + 2 + 0, resulting in 3.

Example 3: Interface and Class Compatibility

The Circle class is an example of a structurally IShape implementation since it has an area method while not being declared as implementing IShape, it follows that any Circle instance can be assigned to an IShape type variable.

interface IShape {
    area: () => number;
}

class Circle {
    constructor(public radius: number) { }
    area() {
        return Math.PI * this.radius ** 2;
    }
}

class Square {
    constructor(public side: number) { }
    area() {
        return this.side ** 2;
    }
}

// Assigning an instance of Circle to a variable of type IShape
const shape: IShape = new Circle(5);

console.log(shape.area()); 


Output

78.53981633974483

Explanation: The output of console.log(shape.area()); is 78.53981633974483 because the shape variable is assigned an instance of Circle, which calculates the area as π * radius². With a radius of 5, the area is π * 5², resulting in approximately 78.54.

Example 4: Complex Structures and Partial Matching

Structural typing also works for nested structures as long as the required properties are present and compatible, in the given example FullName has one extra property middleName but still it can be assigned to the name property of type FullName in Person because it has two other required properties firstName and lastName.

interface FullName {
    firstName: string;
    lastName: string;
}

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

const fullName = { firstName: "Pankaj", lastName: "Bind", middleName: "K" };

const person: Person = { name: fullName, age: 20 };

console.log(person);

Output

{
name: { firstName: 'Pankaj', lastName: 'Bind', middleName: 'K' },
age: 20
}

This situation occurs because the fullName object includes all the necessary properties (firstName and lastName) required by the FullName interface, even though it also contains an additional middleName property. As a result, the fullName object can be assigned to the name property in the Person interface, making the person object valid.

Advantages of Structural Typing

  1. Flexibility: The ability to assign types flexibly makes working with different types sharing the same structure easier through structural typing.
  2. Compatibility with JavaScript: TypeScript structural typing is well suited for JavaScript dynamic and flexible nature enabling better integration with existing codebases written in JavaScript.
  3. Reduced Boilerplate: There is no need for explicit interfaces or type declarations as compatibility depends on structure only, which cuts down boilerplate code.
  4. Enhanced Code Reusability: Structural typing promotes code reuse by allowing types with compatible structures to be used interchangeably, facilitating more modular and maintainable code.

Considerations and Limitations

  1. Potential for Unintended Compatibility: Under structural typing, types can be considered to be compatible even when they are conceptually different, in this case, if developers depend on the matching based purely on structure, then bugs might occur.
  2. Limited Reflection: TypeScript has no support for reflection it means that a programmer cannot perform runtime type checking by using structures.
  3. Type Safety vs. Flexibility: Structural typing provides flexibility however it may compromise type safety if the developer does not fully understand its implications.
  4. Complexity in Debugging: Debugging can be harder with structural typing due to the implicit nature of type compatibility, making it difficult to trace and fix issues.

Similar Reads

three90RightbarBannerImg