typeIsString<T>=Textends string ?"yes":"no";typeA=IsString<string>;// "yes"typeB=IsString<number>;// "no"typeC=IsString<"hello">;// "yes"// Practical: flatten one level of arraytypeFlatten<T>=TextendsArray<inferItem>?Item:T;typeStr=Flatten<string[]>;// stringtypeNum=Flatten<number>;// number (not an array, returned as-is)
Output
// Conditional types branch on whether T matches a shape
Note Conditional types distribute over naked union type parameters: IsString<string | number> becomes IsString<string> | IsString<number> = "yes" | "no". Wrap in tuple to prevent distribution: [T] extends [string] ? ... : ...
conditional typetype branchingtype-level ifextends conditionaldistribute union
Mapped Types
Syntax
typeResult={[KinkeyofT]:NewType;};
Example
interfaceUserProfile{
name: string;
email: string;
age: number;}// Make every property a getter functiontypeGetters<T>={[KinkeyofTas`get${Capitalize<string &K>}`]:()=>T[K];};typeUserGetters=Getters<UserProfile>;// {// getName: () => string;// getEmail: () => string;// getAge: () => number;// }// Make all properties mutable (remove readonly)typeMutable<T>={-readonly[KinkeyofT]:T[K];};
Output
// Mapped types transform every property of an existing type
Note Key remapping with 'as' (TS 4.1+) enables renaming keys. Filter out keys by mapping to never: [K in keyof T as T[K] extends Function ? never : K]. The +/- modifiers add or remove readonly and optional (?) markers.
mapped typetransform propertieskey remappingmodify all propertiesiterate keys
infer Keyword
Syntax
typeResult=TextendsPattern<inferU>?U:Fallback;
Example
// Extract the resolved type from a PromisetypeUnwrapPromise<T>=TextendsPromise<inferR>?R:T;typeA=UnwrapPromise<Promise<string>>;// stringtypeB=UnwrapPromise<number>;// number// Extract function first argumenttypeFirstArg<T>=Textends(first:inferF,...rest: any[])=> any ?F: never;typeFA=FirstArg<(name: string, age: number)=>void>;// string// Extract array element typetypeArrayItem<T>=Textendsreadonly(inferE)[]?E: never;typeItem=ArrayItem<readonly["a","b","c"]>;// "a" | "b" | "c"
Output
// infer declares a type variable that TypeScript figures out from context
Note infer can only be used inside the extends clause of a conditional type. It captures whatever type fits in that position. Multiple infer clauses in the same conditional are allowed. In union position, infer produces a union; in intersection position (like function params), it produces an intersection.
infer keywordextract typepattern matching typecapture genericunwrap type
// JSON-compatible typetypeJsonValue=| string
| number
| boolean
|null|JsonValue[]|{[key: string]:JsonValue};// Deep ReadonlytypeDeepReadonly<T>=Textends object
?{readonly[KinkeyofT]:DeepReadonly<T[K]>}:T;interfaceNestedConfig{
db:{ host: string; port: number; ssl:{ enabled: boolean }};}typeFrozenConfig=DeepReadonly<NestedConfig>;// All levels are readonly - db.ssl.enabled cannot be reassigned
Output
// Recursive types reference themselves in their definition
Note TypeScript handles direct recursive type aliases since TS 3.7. Earlier versions needed interface workarounds. Be careful with deeply recursive conditional types - the compiler has a recursion depth limit (around 50 levels by default) and will error if exceeded.
recursive typenested typedeep typetree typeJSON typeself-referencing type
Branded / Nominal Types
Syntax
typeBrand<T,B>=T&{readonly __brand:B};
Example
typeUSD= number &{readonly __brand:"USD"};typeEUR= number &{readonly __brand:"EUR"};functionusd(amount: number):USD{return amount asUSD;}functioneur(amount: number):EUR{return amount asEUR;}functionchargeUSD(amount:USD){console.log(`Charging $${amount}`);}const price =usd(29.99);chargeUSD(price);// OK// chargeUSD(eur(25.00)); // Error: EUR is not assignable to USD// chargeUSD(29.99); // Error: number is not assignable to USD
Note TypeScript uses structural typing - two types with the same shape are interchangeable. Branded types add a phantom property to create nominal (name-based) distinction. The __brand property never exists at runtime; it is purely a compile-time discriminator. Use this for user IDs, currency, coordinates, etc.
// Template literals combined with infer enable string parsing at the type level
Note Template literal types combined with conditional types and infer enable powerful string parsing and transformation at compile time. This is the foundation for type-safe routing, ORM query builders, and CSS-in-JS libraries.
typeColorMap=Record<string, string | number[]>;// Without satisfies: loses specific types// const colors: ColorMap = { ... } → all values are string | number[]// With satisfies: validates shape AND keeps specific typesconst colors ={
red:"#ff0000",
green:[0,255,0],
blue:"#0000ff",}satisfiesColorMap;
colors.red.toUpperCase();// OK - TypeScript knows red is string
colors.green.map(c => c);// OK - TypeScript knows green is number[]// colors.red.map(c => c); // Error - string has no .map
Output
// satisfies validates compatibility without widening the inferred type
Note Added in TS 4.9. satisfies checks that a value matches a type without changing the inferred type. This gives you the best of both worlds: validation that the shape is correct, plus precise inference of each property. Use it instead of explicit type annotations when you want both safety and specificity.
satisfiesvalidate typecheck without wideningsatisfies operatortype validation
// keyof extracts property names; typeof extracts the type of a value
Note keyof works on types; typeof works on values. They are often combined: keyof typeof myObject gives you the union of property names of a runtime object. typeof only works with variables and properties, not with arbitrary expressions like function calls.
keyoftypeofproperty names typeextract keysvalue to typeobject keys type