keyof & typeof
Turn an object type into a union of its keys with keyof, and a value into a type with typeof, so key names and types come from the code you already have.
- 01List a type's keysUse
keyofto get a union of an object type's property names, and know what it gives for index signatures and unions. - 02Copy a value's typeUse
typeofin a type to reuse the type of a variable, a property, or a function, and tell it apart from JavaScript'stypeof. - 03Type keys from your dataCombine
keyof typeofandas constto write functions likeget(obj, key)that accept only real keys.
Types made from names and from values
JavaScript can already tell you two things about an object while the page runs: what kind of value a property holds, and which property names the object has (the Checking types and Object.keys, values, entries lessons cover these):
const cart = { items: ["Tea"], total: 3 };
console.log(typeof cart.total);
console.log(Object.keys(cart));Line 1 makes a cart with a list of items and a total. Line 2 prints number, the kind of value in cart.total. Line 3 prints the cart's property names, [ 'items', 'total' ].
TypeScript lets you ask the same two questions while you write the code, and get types as answers:
typeofin a type gives the type of a value: "whatever typecarthas".keyofgives the property names of a type, as a union of texts, "this name or that one" (the Union types lesson covers|).
const cart = { items: ["Tea"], total: 3 }; type Cart = typeof cart;// ^?type CartKey = keyof Cart;// ^?const key: CartKey = "total";console.log(key, cart[key]);Line 1 is the same cart. Line 3 makes a type called Cart from the value: typeof cart. The query on line 4 shows what TypeScript worked out: items is a string[] and total is a number. You never wrote that type; it came from the value.
Line 5 makes CartKey from that type with keyof Cart, and the query on line 6 shows "items" | "total": one of the two names. Line 7 uses it for a variable, and line 8 reads cart[key] and prints total 3.
typeof cart reads the type of the value on line 1. keyof Cart reads the names out of that type: "items" | "total".A clinic hands you a filled-in form and asks for more like it. You photocopy it and blank out the answers: now you have the same boxes, ready for the next person. And if someone asks which boxes there are, you read the labels down the side: name, phone, date of birth.
- In real life: A sign-up form someone filled in
- In TypeScript: A value, like
cart - In real life: A blank copy of that form, with the same boxes
- In TypeScript:
typeof cart, its type - In real life: The list of box labels on the form
- In TypeScript:
keyof Cart, its property names - In real life: Writing a label that isn't on the form
- In TypeScript: A key that isn't in the union: an error
typeof turns a value into its type, and keyof turns a type into a union of its property names. Both work only in types, before the code runs, so your types can come from code you already wrote instead of being typed out twice.
keyof: a type's property names
You used keyof in the Generic constraints lesson, inside K extends keyof T. It works on its own too. Put keyof before an object type, and you get the names of its properties. Here is a settings page's type:
type Settings = { theme: string; fontSize: number; showTips: boolean }; type SettingName = keyof Settings;// ^?let field: SettingName = "fontSize";field = "theme";console.log(field);Line 1 describes three settings. Line 3 makes SettingName with keyof Settings. The query on line 4 shows type SettingName = keyof Settings: when a type has a name, TypeScript keeps writing the short form. It means "theme" | "fontSize" | "showTips", one of those three names, as text. Line 5 and line 6 store two of them, and line 7 prints theme.
Any other text is an error, on purpose here:
type Settings = { theme: string; fontSize: number; showTips: boolean }; let field: keyof Settings = "fontSize";field = "colour";Line 4 stores "colour". TypeScript says Type '"colour"' is not assignable to type 'keyof Settings', which means "colour is not one of the names in Settings". The names are exact, so "fontsize" with a small s is an error too, and TypeScript adds Did you mean '"fontSize"'?
keyof works on any object type, not only ones with plain names. Press the buttons to change the type after keyof, and watch the answer on line 2:
type Keys = keyof { id: number; name: string };// ^?Plain object"id" | "name"No errorsEach property name becomes one text type, and they're joined with |: "id" | "name". keyof gives the names, never the values' types.
A few of these are worth remembering. A property marked ? or readonly still has a name. A type that accepts any text as a key (an index signature, which the Index signatures lesson covers) gives string | number. An empty type gives never, the type with no values. And a union of two object types gives only the names both have:
A | B is one of the two, so only a name in both crates is always there. keyof gives just "id".The table below collects what keyof gives. Read each row as: the kind of type, an example, and the names you get.
| The type | Example | What keyof gives |
|---|---|---|
| An object type with named properties | keyof { id: number; name: string } | The names, joined: "id" | "name". |
| Optional or readonly properties | keyof { id?: number; readonly name: string } | The names still count: "id" | "name". |
| Any text as a key | keyof { [word: string]: number } | string | number, because number keys become text. |
| Any number as a key | keyof { [index: number]: string } | number. |
| No properties at all | keyof {} | never: there are no names. |
| One of two object types | keyof (A | B) | Only the names both have. |
typeof: the type of a value
Often the value comes first: a cart object, a coupon code, the shop's name. In a type, typeof followed by a variable's name gives that variable's type, the same type TypeScript shows when you hover over it:
const cart = { items: ["Tea"], total: 3 };let coupon = "SAVE5";const shop = "Tea House"; type Cart = typeof cart;// ^?type Coupon = typeof coupon;// ^?type Shop = typeof shop;// ^?Lines 1 to 3 make three values. The query on line 6 shows the cart's object type. The query on line 8 shows string for the coupon: it's made with let, so it could hold any text later. The query on line 10 shows "Tea House": a const can never change, so its type is that exact text (the Literal types lesson covers this). typeof copies whatever type the value already has.
A function is a value too, so typeof gives you its type. That's handy when another function takes one like it:
function formatPrice(price: number) { return `$${price.toFixed(2)}`;} type Formatter = typeof formatPrice;// ^?function showPrices(prices: number[], format: Formatter) { console.log(prices.map(format).join(", "));}showPrices([3, 4.5], formatPrice);Lines 1 to 3 make formatPrice, which turns a number into a price text. Line 5 copies its type, and the query on line 6 shows (price: number) => string: it takes a number and gives back a string. Line 7 says showPrices takes a list of prices and a function of that type. Line 10 passes formatPrice itself and prints $3.00, $4.50.
typeof only takes a name, or a name followed by dots. Type after typeof on line 6 and watch the answer on line 7:
const cart = { items: ["Tea"], total: 3 };function formatPrice(price: number) { return `$${price.toFixed(2)}`;} type Looked = typeof cart;// ^?cart{ items: string[]; total: number }No errorstypeof cart is the type TypeScript worked out for the cart object: a list of strings called items and a number called total.
Names and properties work. formatPrice(3) doesn't: typeof in a type never runs anything, so it can't call a function. A property that doesn't exist is an error, and so is a value written out, like "Tea".
JavaScript has its own typeof, which you met at the start. The word is the same; where you write it decides which one you get:
const total = 3; const kind = typeof total;// ^?type Total = typeof total;// ^?console.log(kind);Line 3 is normal code: const kind = … runs with the page. So it's JavaScript's typeof, and the query on line 4 shows it gives one of eight texts, like "number" or "string". Line 5 is a type, after type Total =. So it's TypeScript's typeof, and the query on line 6 shows the type 3. Line 7 prints number.
"number". Line 5 is a type, so TypeScript's typeof gives the type 3, and it is gone before the code runs.The rule: wherever a type goes, like after type X = or after the : that gives a variable its type, typeof is TypeScript's. In normal code, it's JavaScript's, and it runs. The JavaScript one is also how you check a value in an if (the Narrowing lesson covers that).
keyof typeof: the names of a value's properties
A list of page routes is a value. To get its names, you might reach for keyof straight away. That's an error, on purpose here:
const routes = { home: "/", cart: "/cart", profile: "/me" }; type RouteName = keyof routes;TypeScript says 'routes' refers to a value, but is being used as a type here. Did you mean 'typeof routes'?. keyof takes a type, and routes is a value that exists while the page runs. The message already says the fix:
const routes = { home: "/", cart: "/cart", profile: "/me" }; type RouteName = keyof typeof routes;// ^?function go(name: RouteName) { console.log(`Going to ${routes[name]}`);}go("cart");Read line 3 from the right. typeof routes is the object's type, and keyof takes its names. The query on line 4 shows "home" | "cart" | "profile". Lines 5 to 7 make go, which accepts only those names, and line 8 prints Going to /cart.
The routes are written once, in the object. Add a checkout route on line 1, and RouteName includes it with no other change.
typeof with as const
typeof copies the type TypeScript gave the value. For an object, each property gets a general type like string, because properties can be changed later. as const after the value says it will never change (the as const & satisfies lesson covers it). Press the buttons to add it and take it away:
const routes = { home: "/", cart: "/cart", profile: "/me" }; type Routes = typeof routes;// ^?routes.home = "/start";console.log(routes.home);/starthome: stringNo errorsEach property could change later, so TypeScript keeps the general type, string. Line 5 changes home, and TypeScript allows it.
Without as const, the query on line 4 shows home: string, and line 5 may change it. With it, every property keeps its exact text and becomes readonly, so line 5 is marked. The keys are the same either way: keyof typeof routes is "home" | "cart" | "profile" in both.
Notice the output: both versions print /start. as const is a type, so it only makes TypeScript complain; it doesn't stop the change while the page runs. To stop that too, freeze the object (the Immutability lesson covers Object.freeze).
as const, typeof gives the general type, because the object could change. With it, each property keeps its exact text and is readonly.Lists work the same way:
const sizes = ["S", "M", "L"];const fixedSizes = ["S", "M", "L"] as const; type Sizes = typeof sizes;// ^?type FixedSizes = typeof fixedSizes;// ^?Line 1 is a plain list, and the query on line 5 shows string[], a list of any texts. Line 2 adds as const, and the query on line 7 shows readonly ["S", "M", "L"]: exactly three items, in that order, that can't change. The next lesson turns that into the type "S" | "M" | "L".
A get function that only takes real keys
A settings page needs a function that reads one setting by its name. In JavaScript, you'd write settings[key] with any text. In TypeScript, that's an error, on purpose here:
const settings = { theme: "light", fontSize: 16, showTips: true }; function getSetting(key: string) { return settings[key];}console.log(getSetting("fontSize"));Line 3 says key is any text. Line 4 reads settings[key], and TypeScript says Element implicitly has an 'any' type because expression of type 'string' can't be used to index type …. In plain words: the settings only have three names, so "any text" might not be one of them, and TypeScript can't tell what you'd get back. The code still runs and prints 16, but a typo would quietly give undefined.
const settings = { theme: "light", fontSize: 16, showTips: true }; function getSetting(key: keyof typeof settings) { return settings[key];}const size = getSetting("fontSize");// ^?console.log(size);Line 3 types key with keyof typeof settings, so the error is gone and a typo gets marked. But the query on line 7 shows string | number | boolean: the function doesn't know which name you passed, so the result could be any setting's type.
The Generic constraints lesson fixed this with two type parameters: T for the object and K extends keyof T for the key, so K is the exact name you pass. The result, obj[key], then has the type of that one property, which TypeScript writes T[K] (the next lesson covers that form). Press the buttons to try different calls on line 6:
function get<T, K extends keyof T>(obj: T, key: K) { return obj[key];} const settings = { theme: "light", fontSize: 16, showTips: true };const value = get(settings, "fontSize");// ^?console.log(value);16numberNo errorsK is "fontSize", so the result is the type of that one property: number. It prints 16.
Each call gets the exact type of its key, a wrong key is marked before the code runs, and the same get works for a user or any other object. keyof decides which keys are allowed; typeof is only needed when you start from a value rather than a type.
Feature flags from one object
Many apps turn features on and off with feature flags: a small object of names and true-or-false values. Write the object once, and let every type come from it:
const flags = { darkMode: true, newCheckout: false, chatHelp: true }; type FlagName = keyof typeof flags;// ^?function isOn(name: FlagName) { return flags[name];} console.log(isOn("newCheckout"));console.log(isOn("darkMode"));Line 1 is the flags object. Line 3 reads its names, and the query on line 4 shows "darkMode" | "newCheckout" | "chatHelp". Lines 5 to 7 make isOn, which only takes those names. Line 9 prints false and line 10 prints true.
When the team adds a flag, they add one line to the object, and isOn accepts the new name. When they remove one, every call that still uses its name is marked. A typo like isOn("darkmode") is marked straight away instead of quietly reading undefined, which would turn the feature off.
type Cart = { total: number }; type K = keyof Cart;const cart = { total: 3 }; type K = keyof cart;const cart = { total: 3 }; type K = keyof typeof cart;type Cart = { total: number }; type T = typeof Cart;const cart = { total: 3 }; type T = typeof cart.total;function total() { return 3; } type R = typeof total();const cart = { total: 3 }; const k: keyof typeof cart = "price";let count = 3; type Count = typeof count;
Sort each line by what TypeScript says about it. Each line is checked on its own.
Common mix-ups
JavaScript's Object.keys gives you an object's names while the page runs. You might expect its type to be the same union as keyof. It isn't, and this loop gets an error, on purpose here:
const flags = { darkMode: true, newCheckout: false }; const names = Object.keys(flags);// ^?for (const name of names) { console.log(name, flags[name]);}The query on line 4 shows const names: string[]. So on line 6, name is any text, and flags[name] gets the same error as the loose getSetting. Why? TypeScript checks shapes, not exact objects: an object with extra properties still fits a type (the Structural typing lesson covers this). So Object.keys might return names the type doesn't list, and TypeScript only promises string[]. The code itself runs fine and prints both flags. Exercise 6 fixes it.
- "
keyof Cartgives the types of the values." No. It gives the property names:"items" | "total". - "
keyof cartworks on a value." No. keyof takes a type. Writekeyof typeof cart. - "typeof in a type gives a text like
"number"." No. That's JavaScript's typeof, in normal code. In a type, typeof gives the type itself. - "
typeof formatPrice(3)gives the type of the result." No. typeof never calls anything; it only takes a name or a name with dots. - "typeof of a
letgives its exact value." No. Aletcan change, so its type is general, likestring. Only aconstkeeps the exact value. - "
as conststops the object changing while the page runs." No. It's a type, so TypeScript only marks the change. UseObject.freezeto stop it. - "
Object.keys(obj)is typed as the object's keys." No. It'sstring[], because an object may have more properties than its type lists.
The table below sets the JavaScript words beside the TypeScript ones. Read each row as: what you write, what it is, and what you get.
| When you write | What it is | What you get |
|---|---|---|
typeof cart.total in normal code | JavaScript's typeof. It runs with the page. | A text that names the kind of value: "number". |
type T = typeof cart.total | TypeScript's typeof, in a type. It's removed before the code runs. | The type of the value: number. |
Object.keys(cart) | A JavaScript function. It runs with the page. | An array of the names the object has right now, typed string[]. |
keyof Cart | TypeScript's keyof, on a type. It's removed before the code runs. | A union of the names: "items" | "total". |
Practice exercises
Each exercise is TypeScript. Press Edit & run on the starter code: the errors and ^? answers update as you type. Try a guess first, then use the hints.
Three lines store an order's property name. Which line will TypeScript mark?
type Order = { id: number; status: string; total: number };
let key: keyof Order = "id";
key = "total";
key = "price";type Order = { id: number; status: string; total: number }; let key: keyof Order = "id";key = "total";key = "status";keyof Order is "id" | "status" | "total". Line 5 stores "price", which isn't one of them, so TypeScript marks it. Storing "status" fixes it.
What type is Country? Guess first. Then press Edit & run and add a // ^? line under it to check.
let city = "Pune";const country = "India"; type City = typeof city;type Country = typeof country;let city = "Pune";const country = "India"; type City = typeof city;// ^?type Country = typeof country;// ^?city is a let, so it could hold any text later: City is string. country is a const, so its type is the exact text: Country is "India".
A shop shows its text in several languages, and t looks up a message by its key. Line 4 gets an error. Change the type of key on line 3 so only real message keys are allowed.
const messages = { greeting: "Hello", cartTitle: "Your cart", checkout: "Pay now" }; function t(key: string) { return messages[key];}console.log(t("cartTitle"));const messages = { greeting: "Hello", cartTitle: "Your cart", checkout: "Pay now" }; function t(key: keyof typeof messages) { return messages[key];}console.log(t("cartTitle"));keyof typeof messages is "greeting" | "cartTitle" | "checkout". Now messages[key] is always a real message, the error on line 4 is gone, and a typo like t("cartTitel") gets marked. It prints Your cart.
Line 6 gets 'format' is of type 'unknown', because TypeScript doesn't know it's a function. Replace unknown on line 5 with the type of formatPrice, without writing it out by hand.
function formatPrice(price: number) { return `$${price.toFixed(2)}`;} function showTotal(total: number, format: unknown) { console.log(format(total));}showTotal(7, formatPrice);function formatPrice(price: number) { return `$${price.toFixed(2)}`;} function showTotal(total: number, format: typeof formatPrice) { console.log(format(total));}showTotal(7, formatPrice);typeof formatPrice is (price: number) => string. Now TypeScript knows format can be called with a number, the error on line 6 is gone, and it prints $7.00.
Line 5 changes a route, and TypeScript allows it. What do you add to line 1 so the routes keep their exact texts and line 5 is marked?
const routes = { home: "/", cart: "/cart" }; type Routes = typeof routes;// ^?routes.cart = "/basket";const routes = { home: "/", cart: "/cart" } as const; type Routes = typeof routes;// ^?routes.cart = "/basket";With as const, typeof routes is { readonly home: "/"; readonly cart: "/cart" }, so line 5 gets Cannot assign to 'cart' because it is a read-only property.
This is the loop from the mix-ups section. Fix the error on line 6 by changing only line 3. What do you add after Object.keys(flags)?
const flags = { darkMode: true, newCheckout: false }; const names = Object.keys(flags);// ^?for (const name of names) { console.log(name, flags[name]);}const flags = { darkMode: true, newCheckout: false }; const names = Object.keys(flags) as (keyof typeof flags)[];// ^?for (const name of names) { console.log(name, flags[name]);}as (keyof typeof flags)[] tells TypeScript the list holds only the flags' names (the Type assertions lesson covers as). The query on line 4 shows ("darkMode" | "newCheckout")[], so flags[name] is fine. It's safe here because the object is written right above and nothing adds to it.
Check your understanding
For each question, ask: is this a type or a value, and is typeof here JavaScript's or TypeScript's? The quiz shows its code without the checker, so the error list doesn't give the answers away.
Question 1 of 7What is
keyof Cart, iftype Cart = { items: string[]; total: number }?Choose an answer to see the explanation.
Question 2 of 7What does line 4 of this price code print?
Read the code, then predictconst price = 4; type Price = typeof price; const kind = typeof price; console.log(kind);Choose an answer to see the explanation.
Question 3 of 7What type is
Sizewhensizeis made withlet?Read the code, then predictlet size = "M"; type Size = typeof size;Choose an answer to see the explanation.
Question 4 of 7Why does TypeScript mark line 3 of this routes code?
Read the code, then predictconst routes = { home: "/", cart: "/cart" }; type RouteName = keyof routes;Choose an answer to see the explanation.
Question 5 of 7What is
keyof { [word: string]: number }?Choose an answer to see the explanation.
Question 6 of 7In
Themehere, what is the type ofmode?Read the code, then predictconst theme = { mode: "dark", size: 16 } as const; type Theme = typeof theme;Choose an answer to see the explanation.
Question 7 of 7Why is
Object.keys(flags)typedstring[], not a list of the flag names?Choose an answer to see the explanation.
Key takeaways
keyof Typeis a union of the type's property names, like"items" | "total". Hovers often show it askeyof Type.- keyof on an index signature gives
string | numberornumber, on{}givesnever, and on a union gives only the shared names. - In a type,
typeof valueis the value's type. It takes a name or a name with dots, and never calls anything. - In normal code,
typeofis JavaScript's and gives a text like"number"while the page runs. keyof typeof objgives the names of a value's properties, so they're written only once.- With
as const, typeof keeps exact values and addsreadonly. It changes the type, not the running object. Object.keysis typedstring[], because objects can have extra properties.
Remember the one-liner.
typeof turns a value into its type, and keyof turns a type into its names.
Coming next: Indexed access types. Once you have a key, they look up the type of that one property, like Cart["total"].