cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Excess property checks

Learn why TypeScript rejects a typo in an object you write inline but not the same object from a variable, and how to allow extra properties when you mean to.

By the end, you can
  • 01
    Spot a fresh objectTell when an object is written right where a type is expected, so TypeScript checks it for extra properties.
  • 02
    Read the two messagesRead Object literal may only specify known properties and its Did you mean form, and fix the typo it points at.
  • 03
    Know when it's skippedExplain why objects from variables, spreads, and as get through, and choose a safe way to allow an extra property.

A typo caught in a new object

A settings page lets a shopper pick a theme and a text size. The code passes the choices to a function as one object. One property name has a typo: fontSise instead of fontSize. Here is what plain JavaScript does:

Settings with a misspelled namePop out in the code editor (opens in a new tab)JavaScript
function applySettings(settings) {
  const size = settings.fontSize ?? 14;
  console.log(`${settings.theme} theme, ${size}px text`);
}

applySettings({ theme: "dark", fontSise: 18 });

Line 1 starts a function that takes a settings object. Line 2 reads settings.fontSize, and the ?? (nullish coalescing) uses 14 when it's missing. Line 3 prints the theme and the size. Line 6 calls the function with fontSise: 18. The object has no fontSize, so it prints dark theme, 14px text. The shopper asked for 18 and got 14, and nothing warned anyone.

Now the same code in TypeScript, with a type for the settings:

The same settings, with a type (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize?: number }; function applySettings(settings: Settings) {  const size = settings.fontSize ?? 14;  console.log(`${settings.theme} theme, ${size}px text`);} applySettings({ theme: "dark", fontSise: 18 });

Line 1 makes a type called Settings: a theme that is text, and a fontSize that is a number. The ? after fontSize means it can be left out (Object types covers this). Line 3 says applySettings takes a Settings. Line 8 is the call, and TypeScript marks it, on purpose here, because it's the bug:

  • Object literal may only specify known properties, but 'fontSise' does not exist in type 'Settings' means "this new object has a property that Settings doesn't list". An object literal is an object you write out with curly braces, like { theme: "dark" }.
  • Did you mean to write 'fontSize'? is TypeScript's guess at the fix: the listed name closest to the one you wrote.

The code still runs and prints dark theme, 14px text; the error is a warning for you. Fix the name on line 8, and it prints dark theme, 18px text.

Left, the object written in the call on line 8, with theme "dark" and fontSise 18. Right, the type Settings, with theme: string and fontSize?: number. An arrow joins the two theme cards, marked listed. The fontSise card is marked not in Settings.the object on line 8new objecttheme: "dark"fontSise: 18fontSise: not in Settingslistedthe typeSettingstheme: stringfontSize?: numberthe names it lists
TypeScript reads each name in the new object and looks for it in Settings. theme is listed. fontSise isn't, so line 8 gets an error.

This is an excess property check. "Excess" means extra: more than the type asks for. In the Structural typing lesson, extra properties were fine. An object only needed the properties its type lists. That's still true for most objects. But an object you write right where a type is expected gets this one extra check.

Real-life analogyA form at the counter and a parcel from home

At a post office counter, you fill in a delivery form while the clerk watches. If you write a line the form doesn't have, the clerk asks about it straight away: it's probably a slip of the pen. But when you bring a parcel you packed at home, the clerk only checks that the label has an address. What else is inside is your business.

In real life: You fill in a delivery form at the counter
In TypeScript: You write an object right where a type is expected
In real life: The clerk reads every line you just wrote
In TypeScript: TypeScript compares every property with the type
In real life: "Postcdoe? Did you mean Postcode?"
In TypeScript: Did you mean to write 'fontSize'?
In real life: A parcel packed at home: the clerk only checks the label
In TypeScript: An object from a variable: TypeScript checks only what the type needs
The main idea

An object literal written right where a type is expected may only have the properties that type lists. Any other property is an error. Nothing else in your code can ever read that object's extra properties, so TypeScript treats an extra name as a mistake, usually a typo.

Fresh object literals

An object literal is called fresh while it's still the object you just wrote, before it's stored anywhere. A fresh object meets a type in four common places. Each one here has the extra property qty on purpose:

Four places a new object meets a type (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type CartItem = { name: string; price: number }; const tea: CartItem = { name: "Tea", price: 3, qty: 2 }; function addToCart(item: CartItem) {  return item.name;}addToCart({ name: "Cake", price: 4, qty: 1 }); function freeGift(): CartItem {  return { name: "Mint", price: 0, qty: 1 };} const cart: CartItem[] = [{ name: "Jam", price: 5, qty: 3 }];

Line 1 makes a CartItem type with a name and a price. Then:

  1. Line 3 writes an object into a variable with a type, const tea: CartItem.
  2. Line 8 writes an object as an argument to addToCart, which takes a CartItem.
  3. Line 11 returns an object from freeGift, which says it gives back a CartItem.
  4. Line 14 writes an object inside an array of CartItems.

All four get the same error: 'qty' does not exist in type 'CartItem'. Delete qty from any line with Edit & run, and that line's error disappears.

Four cards on the left, one per place a new object meets CartItem: a typed variable on line 3, a function argument on line 8, a return value on line 11, and an array item on line 14. Four arrows lead to a window for the type CartItem, with name: string and price: number. A note says each one is checked for extra properties.a new object written…in a typed variable (3)as an argument (8)as a return value (11)in an array (14)CartItemname: stringprice: numberqty: not in CartItem
Four places, one check. Wherever an object is written right where CartItem is expected, its extra qty gets an error: lines 3, 8, 11, and 14.

The check also reaches inside. An object written inside another fresh object is fresh too:

A typo inside a nested object (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Order = { id: number; customer: { name: string; email: string } }; const order: Order = {  id: 7,  customer: { name: "Asha", emial: "asha@example.com" },};

Line 1 says an Order has an id and a customer with a name and an email. Line 3 starts an order. Line 5 writes the customer with emial. TypeScript reports line 5 and asks Did you mean to write 'email'? The order on line 3 isn't marked, because its own two properties are right.

Catching typos

The check helps most with names that can be left out. A missing optional property is allowed, so without this check a misspelled one would be silently ignored. Type a name for the second property on line 3, and watch the list under the code:

Playground: type the property name
A settings object with one name to typePop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize?: number }; const settings: Settings = { theme: "dark", fontSise: 18 };console.log(settings.fontSize ?? 14);
Line 3 setsfontSise
What happens
TypeScript1 error: line 3
Line 4 prints14
Try it yourself

fontSise is close to fontSize, so TypeScript reports it and suggests the fix: Object literal may only specify known properties, but 'fontSise' does not exist in type 'Settings'. Did you mean to write 'fontSize'? Run it anyway, and the 18 is lost: it prints 14.

The code is checked by the real TypeScript compiler each time you type. Only the name on line 3 changes. Reset puts fontSise back.

fontSize is the only name that has no error, and then line 4 prints 18. A name close to a real one, like fontsize, gets a Did you mean suggestion. A name that isn't close to anything, like zoom or size, gets the shorter message with no suggestion. With every wrong name, line 4 prints 14, because the 18 sits under a name nothing reads.

The table below shows the two messages. Read each row as: the kind of extra name, what TypeScript says, and its error number.

Two messages for an extra property
When the extra name is…What TypeScript saysError number
The extra name is close to a real one, like fontSiseObject literal may only specify known properties, but 'fontSise' does not exist in type 'Settings'. Did you mean to write 'fontSize'?2561
The extra name isn't close to any real one, like zoomObject literal may only specify known properties, and 'zoom' does not exist in type 'Settings'.2353

What if the misspelled property is one the type needs? Here fontSize has no ?:

A typo in a property the type needs (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number }; const settings: Settings = { theme: "dark", fontSise: 18 };

Line 1 says every Settings needs a fontSize. Line 3 has fontSise instead. You might expect Property 'fontSize' is missing. TypeScript reports the extra name instead, with the same Did you mean to write 'fontSize'?, which points straight at the fix. Once you fix the name, both problems are gone.

When the check doesn't apply

The check only runs on a fresh object. Once the object is stored somewhere first, it's an ordinary value, and the usual rule from Structural typing applies: it must have what the type needs, and extra properties are fine. Press each button to get the same settings to applySettings a different way:

Playground: how the settings get to the function
Settings passed to applySettingsPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize?: number }; function applySettings(settings: Settings) {  const size = settings.fontSize ?? 14;  console.log(`${settings.theme} theme, ${size}px text`);} applySettings({ theme: "dark", fontSise: 18 });
Console outputdark theme, 14px text
What happens
Checked for extrasYes
TypeScript1 error: line 8
Try it yourself

The object is written right in the call, so it's fresh. TypeScript checks it for extra properties and reports fontSise on line 8.

TypeScript checks the code for real, and the output is what the code prints. Each button changes only the lines from line 8 on. Reset goes back to Written in the call.

Written in the call and A variable with a type both write the object right where Settings is expected, so both get the error. From a variable stores it in saved with no type first: no error, and the code prints dark theme, 14px text. With as Settings uses a type assertion: as Settings after a value tells TypeScript "treat this as a Settings, I know what I'm doing", and the check is skipped. Only Typo fixed prints dark theme, 18px text.

Left, written in the call: applySettings with the object inside it goes straight into Settings, and it is checked for extra properties. Right, stored first: the object goes into a variable saved, then applySettings(saved) passes it on to Settings, and it is not checked for extra properties.Written in the callapplySettings({ …, fontSise })Settingschecked for extrasStored firstconst saved = { …, fontSise }applySettings(saved)Settingsnot checked for extras
Left: an object written in the call can only ever be a Settings, so an extra name is a mistake. Right: once it's stored in saved, other code could still read saved.fontSise, so TypeScript only checks what Settings needs.

Why the difference? A fresh object written in the call can only ever be read as a Settings, so nothing could use fontSise. A stored object is different: other code could read saved.fontSise, so its extra properties might be on purpose. TypeScript only flags extras where they can't be anything but a mistake.

Properties copied in with a spread, ..., aren't checked either:

A typo that arrives through a spreadPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize?: number }; const saved = { fontSise: 18 };//    ^?const settings: Settings = { theme: "dark", ...saved };console.log(settings.fontSize ?? 14);

Line 3 stores { fontSise: 18 } in saved. The query on line 4 shows its type, { fontSise: number; }, typo and all. Line 5 writes a fresh object with a theme and copies in everything from saved. TypeScript only checks the properties written by hand, so there's no error, and line 6 prints 14.

One more check works on variables too. When every property of a type is optional, TypeScript calls it a weak type, and an object must share at least one property with it:

Options with nothing in common (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type ButtonOptions = { color?: string; size?: number }; function buttonClass(options: ButtonOptions) {  return `btn btn-${options.color ?? "grey"}`;} const options = { colour: "red" };console.log(buttonClass(options));

Line 1 makes ButtonOptions, where both properties have a ?. Line 7 stores { colour: "red" }, spelled the British way. Line 8 passes it in. options comes from a variable, but TypeScript still reports Type '{ colour: string; }' has no properties in common with type 'ButtonOptions': an object with none of the listed names can't be what you meant. Run it anyway and it prints btn btn-grey. If the object also had a size, it would share one property, and the colour typo would get through.

The table below sums up the section. Read each row as: how the object gets to the type, whether it's checked for extras, and why.

When TypeScript checks for extra properties
How the object gets thereChecked for extras?Why
Written right in the call: applySettings({ … })Yes.Nothing else can ever read the object, so an extra property is almost surely a mistake.
Written in a variable with a type: const saved: Settings = { … }Yes.The object meets the type Settings the moment it's written.
Stored in a variable first, then passed: applySettings(saved)No.Other code can still read saved, so its extra properties may be useful. The usual shape check runs.
Copied in with a spread: { theme: "dark", ...saved }No.Only the properties you write by hand are checked. The ones a spread copies are not.
Marked with as SettingsNo.as tells TypeScript to treat the object as a Settings, so it skips the check.

Allowing extra properties on purpose

Sometimes the extra property is not a typo. A shop sends analytics events (small records of what shoppers do) to a tracking service, and one event needs a product ID the type doesn't list:

An event with a detail the type doesn't list (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type TrackedEvent = { name: string }; function track(event: TrackedEvent) {  console.log(JSON.stringify(event));} track({ name: "add_to_cart", productId: "p-7" });

Line 1 says a TrackedEvent has a name. Line 3 starts track, and line 4 prints the event as text with JSON.stringify (JSON covers it). Line 7 sends productId, and TypeScript reports 'productId' does not exist in type 'TrackedEvent'. This time the property is wanted. The best fix is to say so in the type:

The type lists the new property (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type TrackedEvent = { name: string; productId?: string }; function track(event: TrackedEvent) {  console.log(JSON.stringify(event));} track({ name: "add_to_cart", productId: "p-7" });track({ name: "add_to_cart", prodcutId: "p-8" });

Line 1 adds productId?: string. Line 7 now has no error, and line 4 prints {"name":"add_to_cart","productId":"p-7"}. The check still protects you: line 8 misspells it as prodcutId, and TypeScript asks Did you mean to write 'productId'?

When the extra names really can't be known ahead of time, the type can say "any other name is fine, as long as its value is text". That's an index signature, and the next lesson, Index signatures & Record, is about them:

Any extra detail, as long as it's textPop out in the code editor (opens in a new tab)TypeScript
type TrackedEvent = { name: string; [detail: string]: string }; function track(event: TrackedEvent) {  console.log(JSON.stringify(event));} track({ name: "add_to_cart", productId: "p-7" });track({ name: "search", query: "green tea" });

Line 1 adds [detail: string]: string: any property name, with a string value. Lines 7 and 8 send different details, productId and query, and neither is an error. The price of this freedom: a typo like prodcutId would be allowed too.

The table below compares the ways to make the error go away. Read each row as: what you do, what TypeScript still checks, and when to use it.

Four ways to allow an extra property
What you doWhat TypeScript still checksWhen to use it
Add the property to the type: productId?: stringEvery other property, and the new one's type. A typo like prodcutId still gets an error.Best when you know the name. Do this first.
Add an index signature: [detail: string]: stringOnly that every extra value is a string. Any name is allowed, typos too.When the names really can't be known ahead of time.
Store the object in a variable firstOnly the properties the type needs.Rarely. It hides the check without saying why.
Write as TrackedEvent after the objectAlmost nothing about the extras.Avoid it for this. It switches the check off and hides typos.

Options for a product search

Option objects are where this check helps most. A search box builds a link from a search word and a few optional settings. Every setting can be left out, so a misspelled one would quietly do nothing:

A search with a misspelled option (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type SearchOptions = { query: string; sortBy?: string; limit?: number }; function searchUrl(options: SearchOptions) {  const limit = options.limit ?? 20;  return `/search?q=${options.query}&limit=${limit}`;} console.log(searchUrl({ query: "tea", limt: 5 }));

Line 1 says a search has a query, and may have a sortBy and a limit. Line 4 uses 20 results when no limit is given. Line 5 builds the link. Line 8 asks for 5 results but writes limt. TypeScript asks Did you mean to write 'limit'? Run it anyway and it prints /search?q=tea&limit=20: the shopper gets 20 results instead of 5.

The option spelled rightPop out in the code editor (opens in a new tab)TypeScript
type SearchOptions = { query: string; sortBy?: string; limit?: number }; function searchUrl(options: SearchOptions) {  const limit = options.limit ?? 20;  return `/search?q=${options.query}&limit=${limit}`;} console.log(searchUrl({ query: "tea", limit: 5 }));

Only line 8 changed: limit: 5. The error is gone, and it prints /search?q=tea&limit=5.

Error or fine?
  • const s: { theme: string } = { theme: "dark", color: "red" };
  • const saved = { theme: "dark", color: "red" }; const s: { theme: string } = saved;
  • const s: { fontSize?: number } = { fontSise: 16 };
  • const s = { theme: "dark", color: "red" } as { theme: string };
  • const opts = { colour: "red" }; const o: { color?: string } = opts;
  • const opts = { colour: "red", size: 2 }; const o: { color?: string; size?: number } = opts;
  • const list: { name: string }[] = [{ name: "Tea", price: 3 }];
  • const extra = { price: 3 }; const item: { name: string } = { name: "Tea", ...extra };
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "Objects can never have extra properties in TypeScript." No. Only a fresh object literal is checked for extras. An object from a variable can have as many extras as it likes, as long as it has what the type needs.
  • "Storing the object in a variable first is the way to allow an extra property." No. It hides the check without saying why. Add the property to the type, so typos in it are still caught.
  • "as Settings checks the object more strictly." No. A type assertion tells TypeScript to trust you, and it skips the excess property check.
  • "TypeScript lists every extra property at once." No. It reports the first extra property in an object. Fix that one, and it shows the next.
  • "The error removes the extra property." No. The types are gone when the code runs, and the object keeps every property. That's why the overview still prints 14px: the 18 is there, under the wrong name.
  • "Spread objects are checked too." No. Only properties you write by hand in the literal are checked. Ones copied in with ... get through.

Practice exercises

Each exercise is TypeScript. When you fix code, press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upSpot the misspelled name

A user profile with a typo. Which name should line 3 use?

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; avatarUrl?: string };

const profile: Profile = { name: "Asha", avatarURL: "/avatars/asha.png" };
console.log(profile.avatarUrl ?? "/avatars/default.png");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upWhich call gets the error?

    Two addresses, both with a floor. Which line does TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type Address = { street: string; city: string };
    
    function shipTo(address: Address) {
      return `${address.street}, ${address.city}`;
    }
    
    const home = { street: "12 Park Road", city: "Pune", floor: 2 };
    console.log(shipTo(home));
    console.log(shipTo({ street: "4 Lake View", city: "Goa", floor: 1 }));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a toast option

      A toast is a small message that pops up for a few seconds. Fix line 8. What does the fixed code print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type ToastOptions = { message: string; durationMs?: number }; function toastText(options: ToastOptions) {  const seconds = (options.durationMs ?? 3000) / 1000;  return `${options.message} (${seconds}s)`;} console.log(toastText({ message: "Saved", duration: 5000 }));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeAllow a product ID

        The event really needs its productId. Make line 7 pass without as, and print the name followed by the ID. What does your code print?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type TrackedEvent = { name: string }; function describe(event: TrackedEvent) {  return event.name;} console.log(describe({ name: "add_to_cart", productId: "p-7" }));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeA typo that gets through

          TypeScript reports nothing here. What does it print? Then change line 3 so a typo there would get an error, and fix the typo.

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type Settings = { theme: string; fontSize?: number }; const fromStorage = { fontSise: 18 };const settings: Settings = { theme: "dark", ...fromStorage };console.log(settings.fontSize ?? 14);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeOptions with nothing in common

            Why does TypeScript mark line 8 even though options is a variable? What does the code print if you run it anyway? Then fix it.

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type ButtonOptions = { color?: string; size?: number }; function buttonClass(options: ButtonOptions) {  return `btn btn-${options.color ?? "grey"}`;} const options = { colour: "red" };console.log(buttonClass(options));

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask where the object was written: right where the type is expected, or somewhere else first. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The excess property checks quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Which objects does TypeScript check for extra properties?

                Choose an answer to see the explanation.

              2. Question 2 of 7Which line does TypeScript flag in this coupon code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Coupon = { code: string; percent: number };
                
                const saved = { code: "SAVE10", percent: 10, expires: "2025-12-31" };
                const a: Coupon = saved;
                const b: Coupon = { code: "TEA5", percent: 5, expires: "2025-06-30" };

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this settings code print, and does TypeScript complain?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Settings = { theme: string; fontSize?: number };
                
                const settings = { theme: "light", fontSise: 20 } as Settings;
                console.log(settings.fontSize ?? 14);

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this profile code print when the city comes in through a spread?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Profile = { name: string; city?: string };
                
                const fromForm = { name: "Asha", citty: "Pune" };
                const profile: Profile = { ...fromForm };
                console.log(profile.city ?? "unknown");

                Choose an answer to see the explanation.

              5. Question 5 of 7Where does TypeScript report an error in this filters code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Filters = { inStock?: boolean; maxPrice?: number };
                
                function countFilters(filters: Filters) {
                  return Object.keys(filters).length;
                }
                
                const picked = { instock: true };
                console.log(countFilters(picked));

                Choose an answer to see the explanation.

              6. Question 6 of 7How many errors does TypeScript report for line 3 of this settings object?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Settings = { theme: string };
                
                const settings: Settings = { theme: "dark", color: "red", zoom: 2 };

                Choose an answer to see the explanation.

              7. Question 7 of 7An analytics event really needs a productId. What's the safest fix for the extra-property error?

                Choose an answer to see the explanation.

              Key takeaways

              • An object literal written right where a type is expected is fresh, and may only have the properties that type lists.
              • The check runs for typed variables, arguments, return values, array items, and objects nested inside them.
              • A name close to a listed one gets Did you mean to write …? (2561); any other extra name gets 2353.
              • Objects from variables, properties from a spread, and objects marked with as skip the check.
              • A type whose properties are all optional also needs at least one shared property, even from a variable.
              • To allow an extra property, add it to the type. Use an index signature only when the names can't be known.

              Remember the one-liner.
              A new object written where a type is expected may only say what the type lists, so a typo there is caught.

              Coming next: Index signatures & Record. It types objects whose property names you don't know ahead of time, like lookup tables and dictionaries.

              CompleteFrontend Clear concepts. Working examples.