Lesson 6 · Module 1.1 — Core type vocabulary

When to reach for 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 powers

For 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:

capabilityinterfacetype
describe an object shapeyesyes
union (A | B), tuple, primitive aliasnoyes
declaration merging (re-open & add members)yesno
extend another shapeextends& intersection

So the read-time tell: if you see type X = A | B or type Pair = [a, b], it had to be a typeinterface cannot express a union or a tuple. If you see the same name declared twice and both survive, it is an interface (they merge).

Rule of thumb you can read by

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.

One nuance at scale: 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.

Read-time takeaway

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.

The object / {} / Object trap

These three look related but mean very different things. Read them as a ladder of looseness:

you see…it meansaccepts
objectany non-primitive (the lowercase one)objects, arrays, functions — not number/string
{}“anything except null/undefinedalmost everything, even 42 and "hi"
Objectthe 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.”

Reading it in the wild

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.

Check yourself

Recall, don’t re-read. (Answers reveal on click.)

You need a type that is "a" | "b" | "c". Which keyword can express it?
Why does let c: {} = 42; compile without error?
Seeing the same interface User declared twice (both kept) tells you…

Primary source

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.

Try it yourself

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.