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 missingtask 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 shapeThe 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 TitledThe 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.
Target
Titledneeds one thing:titleof typestring.
Drills
Answer fast — trust the rule.
How does TypeScript decide whether a value fits a type?
const t = { id: 1, title: "x", done: true }; Can t be passed where { title: string } is expected?
Why is printTitle({ id: 1, title: "x" }) flagged, when passing a variable with the same content is not?
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.
