Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Type Compatibility in TypeScript

Tech Nov 2 2

TypeScript employs a structural type system—often described as 'duck typing'—where type compatibility is determined by the shape (i.e., structure) of values rather than their declared names.

For example, two classes with identical member declarations are considered compatible, even if they have different names:

class Position {
  x: number;
  y: number;
}

class Coordinate {
  x: number;
  y: number;
}

const loc: Position = new Coordinate(); // ✅ No error

This works because Coordinate has at least all the members required by Position, satisfying the assignability rule: type A is compatible with type B if A has all members of B, and each corresponding member is of a compatible type.

Extending this idea, a richer type can be assigned to a more constrained one:

class Vector3D {
  x: number;
  y: number;
  z: number;
}

const pos: Position = new Vector3D(); // ✅ Valid: Vector3D satisfies Position's shape

Interfaces follow the same principle. A type with extra properties remains assignable to a narrower interface:

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

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

let p2: Point2D;
let p3: Point3D = { x: 0, y: 0, z: 0 };
p2 = p3; // ✅

Classes and interfaces are also mutually compatible when shapes align:

class Vector4D {
  x: number;
  y: number;
  z: number;
  w: number;
}

let point: Point2D = new Vector4D(); // ✅

Function compatibility involves three aspects: parameter count, parameter types, and return types.

Parameter count: A functon with fewer parameters can be assigned to one expecting more. The exces parameters are ignored at runtime, and TypeScript permits this for callback-style usage:

type UnaryFn = (value: string) => void;
type BinaryFn = (value: string, index: number) => void;

const log: UnaryFn = (v) => console.log(v);
const handler: BinaryFn = log; // ✅

['a', 'b'].forEach(log); // Also valid — index and array args are optional in practice

Parameter types: Corresponding parameters must be compatible. For object-typed parameters, structural subtyping applies:

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

interface ExtendedPoint extends BasicPoint {
  z: number;
}

type AcceptBasic = (p: BasicPoint) => void;
type AcceptExtended = (p: ExtendedPoint) => void;

const handleBasic: AcceptBasic = (p) => console.log(p.x);
const handleExtended: AcceptExtended = handleBasic; // ✅

Note that this may seem counterintuitive at first—AcceptBasic accepts less data but is assignable to AcceptExtended. This reflects contravariance in parameter positions.

Return types: Return types must be covariant. A function returning a more specific (i.e., narrower or richer) type can be assigned where a less specific one is expected:

type GetString = () => string;
type GetNumber = () => number;

// ❌ Not compatible: string ≠ number

interface Person {
  name: string;
}

interface Employee extends Person {
  id: number;
}

type MakePerson = () => Person;
type MakeEmployee = () => Employee;

const makeEmp: MakeEmployee = () => ({ name: 'Alice', id: 1 });
const makeP: MakePerson = makeEmp; // ✅ Employee satisfies Person

In summary, structural compatibility in TypeScript prioritizes member presence and type alignment over nominal identity, enabling flexible yet type-safe composition across classes, interfaces, and functions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.