Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Assertions and Type Compatibility  ›  Capstone Project

Type Compatibility: Shape Is What Counts

Type Compatibility 12 minTypeScript decides whether one type fits another by comparing shapes: a value fits if it has at least the properties the target needs, with compatible types.

Type Compatibility: Shape Is What Counts

You write printTitle(t: { title: string }) and pass it a full task that was never declared as that type. It compiles. Coming from languages like Java or C#, that looks wrong. In TypeScript it is the core rule: types are matched by shape, not by name.

The rule

A value fits a type if it has at least what the type requires.

This is called structural typing. When you assign one type to another, TypeScript checks each property the target needs, and ignores where the value came from or what it was called.

type Titled = { title: string };

function printTitle(item: Titled) {
  console.log(item.title);
}

const task = { id: 1, title: "Buy milk", done: false };
printTitle(task);   // fine: task has a string title

const note = { text: "Remember" };
printTitle(note);   // error: property 'title' is missing

task has more than Titled asks for, and that is fine. Extra properties never hurt, because printTitle only reads title.

The same idea applies to two separately declared types:

interface Task { id: number; title: string }
class Draft { id = 0; title = "" }

const t: Task = new Draft();  // fine: same shape

The one exception you will hit: fresh object literals. If you write the object directly where the type is expected, TypeScript runs an extra excess property check:

printTitle({ id: 1, title: "Buy milk" }); // error: 'id' is not in Titled

The reasoning: nobody else can ever read id from a literal passed straight into a Titled slot, so it is probably a typo. Assign the literal to a variable first and the check does not apply.

For functions, the rule reads naturally too: a function that takes fewer parameters can be used where more are supplied. That is why tasks.forEach(t => log(t)) works even though forEach passes three arguments.

The shortcut

Read every assignment right to left and ask "Does the value have everything the target needs, with matching types?" Ignore names. Ignore extras — unless the value is a fresh literal.

Does it fit?Step 1 of 4
  1. Target

    Titled needs one thing: title of type string.

Drills

Answer fast — trust the rule.

quick drill

How does TypeScript decide whether a value fits a type?

quick drill

const t = { id: 1, title: "x", done: true }; Can t be passed where { title: string } is expected?

quick drill

Why is printTitle({ id: 1, title: "x" }) flagged, when passing a variable with the same content is not?

quick drill

interface A { x: number } and class B { x = 5 }. Is const a: A = new B(); allowed?

Remember

  • A value fits a type if it has at least what the type requires.
  • Read assignments right to left: does the value have everything the target needs?
  • Trap: fresh object literals are the exception — extra properties there are flagged.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainWhy does this compile?

type Titled = { title: string }. A variable task holds { id: 1, title: "Buy milk", done: false } and printTitle(t: Titled) is called as printTitle(task). Explain why this compiles even though task was never declared as Titled, and why printTitle({ id: 1, title: "x" }) is flagged.