In TypeScript, an object carrying a property that its declared type does not list is passed to a function. Does the emitted JavaScript still contain that property at run time, and what does that mean if the function serialises the object?
answer
- types are erased
- annotation limits reading, not the value
- no code is emitted to strip keys
- JSON.stringify sees the real object
basics
~20 sYes, the property is still there. TypeScript erases types and never rewrites values, so nothing strips undeclared keys. The excess-property check is a compile-time typo guard only; serialising the object sends the extra key over the wire.
solid answer
~50 sThe property survives untouched. TypeScript's type layer exists only during compilation — the emitted JavaScript is your code with the annotations removed, and the compiler never inserts code to delete, hide or validate a property. So a declared type of `{ id: number }` describes only what you may *read* through that reference; the object at run time still has whatever was put in it, and `JSON.stringify` or a key enumeration will show every one of those keys. That is why the excess-property check on fresh object literals matters at all: it is the only moment the compiler can warn you about a stray key, and it is advisory rather than enforcement. If you actually need the extra fields gone before serialising, you must build a new object explicitly picking the fields you want, or run the value through a runtime validator.
code
typescript · 10 linesinterface User { id: number }
const raw = { id: 1, isAdmin: true };
const user: User = raw; // allowed: raw is not a fresh literal
console.log(JSON.stringify(user)); // {"id":1,"isAdmin":true}
// the only way to actually drop the extra key
const projected: User = { id: raw.id };
console.log(JSON.stringify(projected)); // {"id":1}go deeper
Be ready to state that TypeScript compiles to JavaScript with the types removed, so an annotation never changes the object. Say what JSON.stringify would print — every key that is really there.
Explain the two separate compile-time rules — excess-property checking on fresh literals, permissive assignability everywhere else — and show how the second one lets extra keys reach run time untouched.
Bring the production consequence: over-posting, leaked internal fields in responses, serialisers seeing keys the type never mentioned. Describe where you place explicit projections or runtime validation to close that gap.
Frame it as a trust-boundary policy: the compiler guarantees nothing about data it did not produce, so decide which boundaries in the system get schema validation, who owns those schemas, and how response shaping is enforced rather than reviewed.
## Erasure is the whole answer TypeScript compiles by deleting the type layer. Annotations, interfaces, type aliases and generic parameters produce no output; what remains is JavaScript. Nothing in the type system exists at run time — there is no reflection over declared types, no automatic checking, and crucially no rewriting of values to match a type. So when you write: ```ts interface User { id: number } const raw = { id: 1, isAdmin: true, internalNote: "vip" }; const user: User = raw; // allowed: raw's type is not fresh JSON.stringify(user); // {"id":1,"isAdmin":true,"internalNote":"vip"} ``` the annotation `: User` narrows what you are *permitted to read* through `user`. It does not build a new object, it does not hide keys, and it certainly does not remove them. `user` and `raw` are the same object. ## Where the confusion comes from Candidates who have met the excess-property error often assume the compiler is enforcing shape at run time. It is not. Two separate things are going on: - **The excess-property check** rejects a *fresh object literal* that declares a key the target type does not have. It fires at compile time, and only in that one situation. - **Assignability** governs everything else, and it happily lets a wider object flow into a narrower type — which is what makes the extra keys reach run time in the first place. Neither of them touches the value. ## Why it matters in practice Three recurring production consequences: 1. **Over-posting.** A handler that types its payload as `{ email: string }` and forwards the object to a persistence layer forwards `role: "admin"` too if the caller sent it. The type annotation gives no protection; only explicit field selection or a runtime schema does. 2. **Leaked internals in responses.** Returning a domain object typed as a narrow DTO serialises whatever the domain object actually carries, including fields you never meant to expose. 3. **Surprising key enumeration.** Anything that iterates keys — serialisers, query builders, diffing, deep-equality helpers — sees the real object, not the declared type. ## Getting the object to actually match the type Because the type system cannot do it, you do it in code: ```ts // explicit projection — the only keys that exist are the ones you wrote const dto: User = { id: raw.id }; ``` Or you run the value through a runtime validator or parser at the boundary and use the value it returns. The general principle is that anything crossing a trust boundary — network, disk, user input — needs a run-time check, because the compiler's guarantees stop at the boundary of code it compiled. ## The exceptions worth knowing Almost nothing in the type layer emits code, but a couple of TypeScript constructs are not part of the type layer at all: `enum` declarations emit a real object, and legacy decorators emit calls. Those are language features that happen to live in a `.ts` file, not types. The rule to carry into an interview is precise: *types* are erased, and no annotation ever changes a value. ## How to say it "Types are erased, so the property is still there. The declared type limits what I can read through that reference; it is not a filter. If I need the extra fields gone I have to construct a new object or validate at the boundary — the excess-property check only warns me about a literal I wrote by hand."
- How would you actually drop the unknown keys before sending the object to another system?Construct a new object explicitly listing the fields you want, or pass the value through a runtime validator or parser and use its output. Both produce a value whose keys you control. There is no compiler flag or utility type that does it, because the type layer never touches values.
- If the extra property survives anyway, why does the compiler complain about it in a fresh literal?Because a literal you just wrote by hand is the one place a stray key is almost certainly a mistake — a misspelling, or a field left over from an older shape. Nothing could read it through the target type, so the compiler flags it as a typo guard rather than as enforcement.
saying these in an interview costs you the question
- Believes TypeScript strips undeclared properties during compilation
- Thinks the declared type is checked when the object is used at run time
- Assumes an annotation is enough to prevent over-posting user input
- Says interfaces produce runtime shape validation
- Confuses a compile-time excess-property error with a runtime error