Structural Typing & Duck Typing in TypeScript

TypeScript‘s type system is based on Structural typing. In a structurally typed system, a type is considered to be compatible with another type if the type has all the properties and methods of that type. But JavaScript is a duck-typed language. Since Typescript Compiles to JavaScript, you can take the benefit of duck typing also.

Type Compatibility

Type compatibility refers to the similarity of two types to each other. One of the essential tasks of a type system is to determine if the two given types are compatible with each other or if a type is a subtype of another type.

Type Compatibility is important because if a type is compatible with another type, then you can convert it and substitute it in operations involving another type.

Consider the primitive data types integer and decimal. We can convert an integer value to a decimal value without losing precision. We can easily substitute integer values everywhere a program it expects a decimal type. Hence, we can say that the integer is compatible with the decimal data type.

Type T1 compatibility with T2 does not mean that T2 is also compatible with T1. An integer is compatible with a decimal, but a decimal type is definitely not compatible with an integer. Converting a decimal value into an integer definitely results in a loss of precision. Hence, they are not compatible.

It is easier to compare primitive types like integers, strings, decimals, etc. They have a simple structure. But objects, classes, etc., have complex structures. There are two ways in which type systems compare the types to each other for compatibility. One is nominal typing and the other one is structural typing.

Nominal typing uses the name to compare types. The compatibility of the types is determined by explicit declarations and/or the names of their types. Each type is unique in the nominal system. Even if they have the same data and shape, we cannot assign them across types.

Structural typing is where two types are considered compatible if they have the same shape. The name of the type or how we create the object is immaterial

Duck typing is usage-based structural equivalence, which we usually find in dynamic languages like JavaScript which do not have a strong typing

Structural Typing in TypeScript

In this example, the interface Dog & Cat has the same property name. But the Person interface has the property firstName & lastName.

We create two objects dog & cat using the Dog & Cat interface. Typescript does not complain when we assign dog to cat and vice versa. This is because both have the same shape.

But we cannot assign person object to dog or to cat. This is because the structure of person is different from the structure of cat or dog

In this example, we have added breedName property to the Dog interface.

Now we can assign dog to cat because a cat expects only a name property, which dog has. Additional properties of dog does not make any difference.

But we cannot assign cat to a dog because dog also expects a breedName property and cat does not have it.

You can make the breedName property optional in the Dog interface for the above code to work.

Classes and Structural Typing

This example shows the structural typing when using the classes. We can assign a cat to a dog and vice versa irrespective of the different classes from which we create them.

But if we make age property private in both classes then they become incompatible. The following code throws an error.

Duck Typing in TypeScript

Duck typing neither cares about the name nor the structure of the type. It must have the given method or properties required by the operation.

TypeScript does not allow Duck Typing. But JavaScript does.

The duck type originates from the phrase “If it walks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck“. It means that if an entity behaves like a duck then you can safely assume it is probably a duck. In the type system, if you are expecting a certain behavior and the object has those behaviors then you can use that object. Its shape and type are not important.

This is JavaScript code. We have two different objects person and bankAccount. Both are unrelated objects but have a one common function someFn. The invokeSomeFn invokes the someFn of whatever object passed as an argument to it. We can pass any object to invokeSomeFn and it will happily execute the someFn from that object. The existence or nonexistence of any other property in the passed object is irrelevant to the invokeFn. We call this behavior Duck typing. Duck typing does not check for compatibility.

TypeScript does not support duck typing. The following code is exactly the same as above but with types added. We annotate the argument of invokeSomeFn as obj:Person. Now invokeSomeFn will not accept the BankAccount as it has a different structure.

But we can get around it by creating a separate type consisting of someFn only. In this example, we define an interface for the parameter inline. Now invokeSomeFn will accept any object that has someFn method in it. This trick, allow us to implement the Duck typing in TypeScript.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top