TypeScript’s structural typing lets a value with extra properties satisfy a narrower type, which is what makes interfaces compositional. But an object literal assigned directly to that type is treated as “fresh” and gets an extra check: unknown properties are rejected. This is the excess property check, and it is a lint-like guard, not a hole in the type system.
Before you start
You should understand structural typing (compatibility by shape, not name) and the difference between a literal and a variable of an inferred type. This article explains the check and how to opt in or out of it; it does not cover every assignability rule.
Step-by-step walkthrough
Step 1: Distinguish fresh literals from variables
A directly written object literal carries a freshness flag. When it is assigned to Point, every property must belong to Point. A value that first flows through a variable loses that flag: the variable’s inferred type is structurally compatible with Point, so the assignment succeeds even though it has an extra field.
Step 2: See the check as typo and intent protection
The check catches misspelled optional fields and properties that “look” intended but are not part of the contract. Without it, a typo like { x: 1, y: 2, zz: 3 } would silently satisfy a type that never declared z, hiding a bug until runtime.
Step 3: Allow extras on purpose
If extras are genuinely expected, widen the target so the extra properties are known: add an index signature, accept a generic that preserves the argument’s type, or spread the object into a variable first. Each option documents the intent differently, so choose the one that matches the contract rather than silencing the error with a cast.
Worked scenario
The literal is rejected but the variable is accepted, illustrating freshness.
interface Point {
x: number;
y: number;
}
const fromVariable = { x: 1, y: 2, z: 3 };
const p1: Point = fromVariable; // allowed: structural compatibility
// Next line is a compile error: 'z' does not exist in type 'Point'.
// const p2: Point = { x: 1, y: 2, z: 3 };Walk through the example
fromVariable has an inferred type { x: number; y: number; z: number }, which is assignable to Point because it contains the required members. The inline literal in the commented line is fresh, so the compiler reports the extra z immediately. Both objects have the same shape; the difference is only how the compiler treats a literal written at the assignment site.
Common mistake
Believing the check is a bug or that it means extra properties are banned everywhere. It is not: it applies to direct literals. Conversely, relying on it as validation of runtime data is wrong — data from JSON.parse is not a fresh literal, so typos in parsed input will not be caught by this check.
Verify the behavior
Assign a literal with a wrong-type property and confirm the error is about the type; then misspell a property name and confirm the error is about the unknown key. Move the literal through a variable and watch the excess-property error disappear. Finally, add an index signature [key: string]: unknown and confirm extras are then permitted, which proves the check is about declared shape.
Interview exercise
You are building a createUser(input) function and want to reject unknown fields at compile time for callers who pass a literal, but still accept a widened object. What do you do?
Answer and reasoning
Type the parameter as the exact UserInput interface. Callers who pass a literal get the excess property check, while callers who pass a variable are accepted through structural typing. If you must reject unknown keys even for variables, that is a runtime concern — validate the object’s own keys (for example with Object.keys) at the boundary, because the compiler cannot see the provenance of an arbitrary variable.
Follow-up discussion
Does the check apply to arrays and functions? It applies to fresh object and array literals assigned to a target type, including nested literals. Does as const change it? No: it makes properties readonly literal types but keeps the literal fresh, so extras are still flagged unless the target allows them.
Continue learning
Build on structural typing and the satisfies operator. Read the TypeScript object types handbook and test yourself with the TypeScript interview questions.