Lesson 6 · Module 1.1 — Core type vocabulary
type vs interface (and the object/{}/Object trap)Two questions that come up again and again: should a shape be a
type alias or an interface? And what is the difference between
lowercase object, the empty {}, and capital Object? You
mostly only need to read these, so here is the short, reliable decoder.
interface vs type: same shape, different powersFor an object shape they are interchangeable — both describe the same structure:
interface User { id: number; name: string; }
type User = { id: number; name: string; }; // same shape, different keyword
The differences that matter when you read code:
| capability | interface | type |
|---|---|---|
| describe an object shape | yes | yes |
union (A | B), tuple, primitive alias | no | yes |
| declaration merging (re-open & add members) | yes | no |
| extend another shape | extends | & intersection |
So the read-time tell: if you see type X = A | B or type Pair = [a, b],
it had to be a type — interface cannot express a union or a tuple.
If you see the same name declared twice and both survive, it is an interface (they merge).
Reach for interface when you are describing the shape of an object or a class
contract (and might want it to merge). Reach for type for everything else — unions,
tuples, function types, mapped/conditional types, or aliasing a primitive. When in doubt for a
plain object: either works.
interface extends is cheaper than type + &For a single plain shape, type and interface perform identically —
so the rule above stands. The difference appears only when you compose shapes, and here the
TypeScript
team's own performance guide recommends interface … extends over a type
alias built with &:
// intersection — re-merged and checked piece by piece; the relationship is not cached
type Foo = Bar & Baz & { id: number };
// extends — one flat type; the relationship between the named types is cached
interface Foo extends Bar, Baz { id: number; }
In the team's words, an interface creates “a single flat object type that detects property
conflicts,” and “type relationships between interfaces are also cached, as opposed to intersection types
as a whole.” An intersection (&) instead “recursively merge[s] properties,” and when a
value is checked against it, “every constituent is checked before checking against the … flattened type”
— more work, and none of it remembered between checks.
The effect is real but only bites at scale — large, deeply-composed types in big codebases,
where it is a common cause of slow type-checking. For everyday code, still choose by capability
(the table above). But when you are composing object shapes and either form would work, read
interface extends as the cheaper default and type X = A & B as the form to
flatten if the compiler ever gets slow.
object / {} / Object trapThese three look related but mean very different things. Read them as a ladder of looseness:
| you see… | it means | accepts |
|---|---|---|
| object | any non-primitive (the lowercase one) | objects, arrays, functions — not number/string |
| {} | “anything except null/undefined” | almost everything, even 42 and "hi" |
| Object | the global wrapper interface (capital O) | nearly everything too — avoid; almost never what you want |
let a: object = { x: 1 }; // ok
let b: object = 42; // ERROR — 42 is a primitive, not an object
let c: {} = 42; // ok (!) — {} is "non-null/undefined", and 42 qualifies
let d: {} = null; // ERROR — the one thing {} rejects
The surprise is {}: it does not mean “an empty object you can build up.” It
means “any value that isn’t null or undefined,” so it accepts numbers and
strings too. When code annotates something as {} and then can’t add properties, that is
why — read it as “some non-nullish value,” not “empty object.”
When you meet a parameter typed object, expect “any reference value, but no
primitives.” When you meet {}, expect “practically anything but nullish.” And capital
Object is almost always a mistake or legacy code — treat it as a smell, not a signal.
Recall, don’t re-read. (Answers reveal on click.)
"a" | "b" | "c". Which keyword can express it?let c: {} = 42; compile without error?interface User declared twice (both kept) tells you…The handbook section that contrasts the two is
Everyday Types — Differences Between Type Aliases and Interfaces.
For the loose object types, see
Object Types
and the FAQ on
why {} accepts almost anything.
For the performance point, the authority is the team's
Performance wiki — Preferring Interfaces Over Intersections.
Best way to feel it: paste the four object/{} lines into the
TS Playground and read each error.
Hit a type you can’t classify? Read it closely and ask “interface or type — and could it have been the
other?” Next lesson (1.2): literal types, unions, and why string-literal unions usually beat
enum.