TypeScript provides operators that work on types the way JavaScript operators work on values. These let you extract, query, and transform types programmatically.
keyof: the keys of a type
keyof produces a union of all property names of a type:
interface User {
id: number;
name: string;
email: string;
}
type UserKeys = keyof User;
// UserKeys is "id" | "name" | "email"
keyof is most useful with generics:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Colin", email: "c@example.com" };
getProperty(user, "name"); // OK — returns string
getProperty(user, "age"); // Error — "age" is not a key of User
The constraint K extends keyof T ensures the key actually exists on the object.
typeof in type positions
typeof in a type context extracts the type of a value:
const config = {
retries: 3,
timeout: 5000,
verbose: false,
};
type Config = typeof config;
// Config is { retries: number; timeout: number; verbose: boolean }
This is useful for capturing the type of a value without duplicating its shape:
const defaultHeaders = {
"Content-Type": "application/json",
"Authorization": "",
};
type Headers = typeof defaultHeaders;
function mergeHeaders(overrides: Partial<Headers>): Headers {
return { ...defaultHeaders, ...overrides };
}
Do not confuse typeof in type positions (which extracts types) with typeof at runtime (which checks primitive types). They are the same keyword used in different contexts.
Indexed access types
Indexed access types look up the type of a property using bracket notation:
interface User {
id: number;
name: string;
settings: { theme: string; lang: string };
}
type NameType = User["name"];
// NameType is string
type SettingsType = User["settings"];
// SettingsType is { theme: string; lang: string }
Indexed access works with unions of keys:
type NameOrId = User["name" | "id"];
// NameOrId is string | number
And with keyof:
type AnyUserValue = User[keyof User];
// AnyUserValue is number | string | { theme: string; lang: string }
Combining the operators
These operators compose naturally:
interface Config {
database: { host: string; port: number };
cache: { ttl: number; enabled: boolean };
}
type DatabaseConfig = Config["database"];
// { host: string; port: number }
type DatabaseKeys = keyof Config["database"];
// "host" | "port"
type AnyConfigValue = Config[keyof Config];
// { host: string; port: number } | { ttl: number; enabled: boolean }
What to carry forward
keyof Tproduces a union of all property names ofTtypeof valuein a type position extracts the type of a valueT[K](indexed access) looks up the type of propertyKon typeT- these operators compose:
keyof T["property"],T[keyof T] - they enable type-safe property access and dynamic type extraction
The next lesson covers mapped types — how to transform every property of a type.