Lesson 4 · Module 0.4 — Reading TS syntax
Coming from class-based OO, you expect types to match by name. TypeScript matches by shape — duck typing. One consequence often trips people up: pass an object literal inline with an extra field and TS errors; assign it to a variable first and the same extra field is fine. Same object, opposite outcome. Here's the rule that explains it.
TypeScript types are structural: a value fits a type if it has the required shape, extras allowed. But an object literal passed directly gets a one-time bonus check — excess-property checking — that rejects unknown props as likely typos. Put the literal in a variable first and that bonus check is skipped; plain structural subtyping takes over.
Start with the duck line: TypeScript cares about the shape of your object, not
its name. A value with all the required properties (and more) is a valid subtype — exactly
like passing a Dog where an Animal is wanted. Extra properties don't break
that; the receiver simply ignores them.
interface FetchOptions { url: string; method: string; }
const myFetch = (opts: FetchOptions) => {};
Anything with a string url and a string method structurally is a
FetchOptions — even if it carries more. So why does the compiler ever complain about
extras? Because of where the object is written.
// INLINE literal passed straight to the call → EXCESS-PROPERTY CHECK runs
myFetch({
url: "/",
method: "GET",
search: new URLSearchParams({ limit: "10" }), // ERROR: 'search' not in FetchOptions
});
// Assign to a VARIABLE first, then pass → excess check SKIPPED, structural subtyping allows it
const options = {
url: "/",
method: "GET",
search: new URLSearchParams({ limit: "10" }), // extra prop — totally fine here
};
myFetch(options); // OK — options has url+method (+extra); it's a valid subtype
Identical object, two outcomes. Inline, TypeScript runs excess-property checking:
a fresh literal handed straight to a slot is probably hand-written for that slot, so an unknown key
like search is most likely a typo — error. Via a variable, the literal's type is first
inferred on its own line ({ url; method; search }), and that type is then checked
structurally: it has the two required props, so it's an acceptable subtype, extras and all.
A sharper framing: the inline case behaves like a strict assignment (“create an
object that IS exactly FetchOptions”), while the variable case behaves like
subtyping (“create something that extends FetchOptions and substitute
it in”). That's the same substitution rule as class inheritance you already know:
class Animal { name = ""; }
class Dog extends Animal { breed = ""; }
const pet = (a: Animal) => {};
pet(new Dog()); // OK — Dog IS-A Animal: a richer subtype substitutes for the base type
A type with more properties stands in for one with fewer, as long as every required property is present. Excess-property checking is the single exception, bolted on top of that rule purely to catch typos in fresh literals — and it fires only when the literal is brand new and written right at the call site.
A @ts-expect-error placed above myFetch(options) is itself an error —
there is no error on the variable form, so TS flags the unused directive. The lesson: don't
expect the excess-property error where structural subtyping applies. The error lives at the
inline literal, not at the variable. Annotating the variable with : FetchOptions
would bring the strict check back, because then the literal is assigned to a typed slot.
Recall, don’t re-read. (Answers reveal on click.)
search prop get rejected?The official handbook page that names and explains this behavior — structural object types plus the excess-property check on fresh literals — is TypeScript Handbook — Object Types: Excess Property Checks. Best way to feel it: paste both the inline call and the variable version into the TS Playground and watch the red squiggle appear on the literal but vanish on the variable.
Hit a “property does not exist in type” error and unsure if it's the excess check or a real shape mismatch? Read it closely and ask “inline literal, or true subtype error?” This closes Module 0 — you can now read a TS line and name where each type comes from and why it is or isn't accepted.