Structural Typing
Structural typing means TypeScript decides whether two types are compatible by comparing their shape — the properties and methods they have — rather than their declared name, so any object with the required members counts as that type.
Learn Structural Typing in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
A power socket doesn't ask which company made your charger — it only checks that the plug has the right prongs. Any device with a matching plug "fits," regardless of brand or name. Structural typing is the same: TypeScript checks the prongs (the properties), not the label on the box. If the shape fits, it plugs in.
1. Shape Over Name
A function that needs a Point with x and y will accept any object that has those properties — even one you never declared as a Point . There's no implements , no inheritance, no conversion: having the right structure is being the type. This is why TypeScript is described as "structurally typed" and nicknamed "duck typed."
2. Excess Property Checks
Structural typing normally allows extra properties — a wider object fits a narrower requirement. But TypeScript makes one strict exception: when you assign a fresh object literal directly to a typed target, an unexpected property is flagged. That's because a literal with a surprise property is almost always a typo. Route the literal through a variable first and the special check disappears, leaving plain assignability.
3. Assignability: Has at Least What's Required
The core rule is simple: a value is assignable to a type when it has at least every member that type requires. A wider object can stand in for a narrower one, and the extra fields are simply ignored. A direct consequence is that two named types you never related to each other — a Person and a Product — can both satisfy a function that needs only their common shape.
🎯 Your Turn
A robot and a pet share nothing but a name . A function needing only { name: string } accepts both. Fill in the blank marked ___ with the shared property, then run it.
Common Errors (and the fix)
- ❌ "Object literal may only specify known properties, and 'z' does not exist..." — an excess property on a fresh literal. ✅ Fix the typo, or widen the type/add an index signature if the field is intended.
- ❌ Expecting nominal behaviour — assuming an object must be "declared a Point" to count. ✅ In TypeScript, the right shape is enough; no declaration needed.
- ❌ Silencing excess errors with as — hides a real typo. ✅ Prefer fixing the property name; reserve assertions for genuine cases.
- ❌ Assuming extra props are forbidden everywhere — they're only checked on fresh literals. ✅ Through a variable, supersets are accepted by assignability.
Pro Tips
- 💡 Type parameters by the shape you need , not the biggest type available — accept { id: string } if that's all you use, and callers get more flexibility.
- 💡 Excess-property errors are friends — they catch real typos at the literal site. Read the property name it complains about before reaching for a workaround.
- 💡 If you need true nominal typing (so two same-shaped types can't mix), use branded types — covered later in this course.
- 💡 Functions are compared structurally too — parameter and return types must be compatible, which enables safe callback substitution.
📚 Keep learning
- TypeScript Handbook — Type Compatibility
- TypeScript Handbook — Excess Property Checks
- TypeScript Handbook — Structural relationships between classes
Frequently Asked Questions
Mini-Challenge: Sum by Shared Field
No blanks this time — just a brief and a starting outline. Build the sum yourself, run it, and check your output against the example in the comments.
🎉 Lesson Complete
- ✅ TypeScript compares types by shape , not by name (structural typing)
- ✅ Any object with the required members satisfies a type — "duck typing"
- ✅ Excess property checks fire only on fresh object literals
- ✅ Routing a literal through a variable skips the excess check
- ✅ Assignability = "has at least the required members"; extras are ignored
- ✅ Next lesson: type vs interface — when to use which
Practice quiz
What does structural typing compare to decide compatibility?
- The declared type name
- The file the type lives in
- The shape - the set of properties and methods
- The order types were declared
Answer: The shape - the set of properties and methods. TypeScript matches types by structure, not by their declared name.
Why is structural typing nicknamed 'duck typing'?
- If it has the right members, it counts as the type - walks/quacks like a duck
- It involves real ducks
- It only works on animals
- It quacks at runtime
Answer: If it has the right members, it counts as the type - walks/quacks like a duck. TypeScript asks what a value can do, not what it is called.
Does an object need to be declared 'a Point' to be used as one?
- Yes, with implements
- Yes, with a class
- Only with as Point
- No - having x and y is enough
Answer: No - having x and y is enough. Any object with the required members satisfies the type; no declaration needed.
When does an excess property check fire?
- On every object
- On a fresh object literal assigned directly to a typed target
- Only inside functions
- Never in strict mode
Answer: On a fresh object literal assigned directly to a typed target. TypeScript flags extra properties only on fresh literals - a deliberate typo catcher.
How can you make an excess-property error go away (legitimately)?
- Assign the literal to a variable first, then assign that variable
- Use any
- Delete the type
- Add @ts-ignore
Answer: Assign the literal to a variable first, then assign that variable. Routing through a variable skips the literal-only check; plain assignability takes over.
What is the core rule of assignability?
- Types must share a name
- Both types need the same number of properties
- A value is assignable if it has at least every member the type requires
- Objects must be frozen
Answer: A value is assignable if it has at least every member the type requires. Has at least the required members = assignable; extras are ignored.
Can a wider object stand in for a narrower required shape?
- No
- Yes - the extra properties are simply ignored
- Only with a cast
- Only if frozen
Answer: Yes - the extra properties are simply ignored. A superset object satisfies a smaller required shape; extra fields don't matter.
Can two unrelated named types be compatible?
- No, names must match
- Only if one extends the other
- Only for classes
- Yes, if their shapes line up (e.g. both have name: string)
Answer: Yes, if their shapes line up (e.g. both have name: string). Structural compatibility means coincidental matching shapes are interchangeable.
How does TypeScript's typing differ from Java/C# nominal typing?
- It is identical
- Nominal typing needs explicit declaration by name; structural only needs the shape
- Structural typing is slower
- Nominal typing ignores names
Answer: Nominal typing needs explicit declaration by name; structural only needs the shape. Nominal systems require declaring a value to be a type by name; TypeScript only checks structure.
If you need two same-shaped types to be incompatible, you should use:
- as const
- Object.freeze
- branded (nominal) types
- an index signature
Answer: branded (nominal) types. Branded types add a unique marker so structurally identical types can't mix.
Continue this course
- Previous: void, null & undefined
- Next: type vs interface