TypeScript uses STRUCTURAL typing: compatibility is based on a type's SHAPE (its members), not its name or declared inheritance. If it has the required members, it fits — 'if it walks like a duck'.
Two independently declared types with the same structure are interchangeable, and a value with EXTRA properties still satisfies a type needing a subset (with a special exception for object literals, which get excess-property checks). This differs from nominal typing (Java/C#) where names/inheritance decide compatibility. Structural typing makes TypeScript flexible — a function needing { id: number } accepts anything with a numeric id — but can surprise people expecting nominal behavior. To force distinctness (a 'branded' type), you add a private/unique marker. Understanding this explains why unrelated objects are assignable when shapes match.
A logging helper typed to accept { message: string } will accept an Error, an API response, or any object that has a message field — no shared base class required, thanks to structural typing.
In Java, two classes with identical fields are NOT interchangeable. Why ARE they in TypeScript, and when can that bite you?