TypeScript Structural Typing
Last Updated :
07 Aug, 2024
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
- 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.
- 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.
- 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.
JavaScript
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.
JavaScript
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.
JavaScript
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.
JavaScript
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
- Flexibility: The ability to assign types flexibly makes working with different types sharing the same structure easier through structural typing.
- Compatibility with JavaScript: TypeScript structural typing is well suited for JavaScript dynamic and flexible nature enabling better integration with existing codebases written in JavaScript.
- Reduced Boilerplate: There is no need for explicit interfaces or type declarations as compatibility depends on structure only, which cuts down boilerplate code.
- 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
- 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.
- Limited Reflection: TypeScript has no support for reflection it means that a programmer cannot perform runtime type checking by using structures.
- Type Safety vs. Flexibility: Structural typing provides flexibility however it may compromise type safety if the developer does not fully understand its implications.
- 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
TypeScript Duck-Typing
In TypeScript, the duck-typing feature ensures type safety. As a result of the duck-typing rule, the TypeScript compiler verifies that two objects are identical in terms of having matching properties and methods. This technique is used to compare two objects by determining if they have the same type
4 min read
Nominal Typing in TypeScript
TypeScript is a language that has a static type system and enhances JavaScript by adding descriptions of types. One key part of TypeScriptâs type system is its support for structural typing. But there are times when developers need more control over type identity, this is where the idea of nominal t
6 min read
TypeScript Union
The TypeScript union has the ability to combine one or two different types of data (i.e., number, string, float, double, etc). It is the most powerful way to express a variable with multiple types. Use pipe ('|') symbol to combine two or more data types to achieve Union type. Syntax: (type1|type2|ty
3 min read
Introduction to TypeScript
TypeScript is a syntactic superset of JavaScript that adds optional static typing, making it easier to write and maintain large-scale applications.Allows developers to catch errors during development rather than at runtime, improving code reliability.Enhances code readability and maintainability wit
5 min read
What are TypeScript Interfaces?
TypeScript interfaces define the structure of objects by specifying property types and method signatures, ensuring consistent shapes and enhancing code clarity.Allow for optional and read-only properties for flexibility and immutability.Enable interface inheritance to create reusable and extendable
4 min read
Getting Started with TypeScript
TypeScript is an open-source programming language developed by Microsoft that extends JavaScript by adding optional static typing to the language. It aims to make JavaScript development more scalable and maintainable, especially for large-scale projects. TypeScript code is transpiled into JavaScript
4 min read
TypeScript Object
A TypeScript object is a collection of key-value pairs, where keys are strings and values can be any data type. Objects in TypeScript can store various types, including primitives, arrays, and functions, providing a structured way to organize and manipulate data.What Are TypeScript Objects?An object
5 min read
TypeScript Utility Types
TypeScript utility types are predefined constructs that facilitate common type transformations, enhancing code flexibility and maintainability. They allow developers to modify existing types easily, such as by making properties optional or read-only.Utility types help in creating more expressive and
5 min read
TypeScript Operators
TypeScript operators are symbols or keywords that perform operations on one or more operands. Below are the different TypeScript Operators:Table of Content TypeScript Arithmetic operatorsTypeScript Logical operatorsTypeScript Relational operatorsTypeScript Bitwise operatorsTypeScript Assignment oper
6 min read
TypeScript Interfaces Type
TypeScript Interfaces Type offers an alternative method for defining an object's type, allowing for a distinct naming approach. Syntax:interface InterfaceName { property1: type1; property2?: type2; readonly property3: type3; // ... method1(): returnType1; method2(): returnType2; // ...}Parameters:in
2 min read