Transform each property in a type - iterate over union of keys
Make all properties readonly/optional
Key Remapping
{[K in Keys as NewK]: Type}
Transform property keys during mapping using as clause
Prefix/suffix keys, filter properties
Template Remapping
as `prefix${K}`
Use template literal types to transform key names
Add getters/setters prefixes
Conditional Remapping
as K extends Cond ? T : F
Conditionally include/exclude or transform keys
Filter out specific properties
never for Filtering
as K extends Bad ? never : K
Exclude properties by mapping key to never
Remove private/internal properties
Mapped Modifiers
+readonly, -?
Add (+) or remove (-) readonly/optional modifiers
Create mutable/required versions
Example: Basic mapped types and modifiers
// Make all properties optionaltype Partial<T> = { [P in keyof T]?: T[P];};// Make all properties required (remove optional)type Required<T> = { [P in keyof T]-?: T[P];};// Make all properties readonlytype Readonly<T> = { readonly [P in keyof T]: T[P];};// Remove readonly modifiertype Mutable<T> = { -readonly [P in keyof T]: T[P];};
Example: Key remapping with template literals
// Add getter prefix to all propertiestype Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];};interface User { name: string; age: number;}// Result: { getName: () => string; getAge: () => number; }type UserGetters = Getters<User>;// Add setter prefixtype Setters<T> = { [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;};// Combine getters and setterstype Accessors<T> = Getters<T> & Setters<T>;
Example: Filtering properties with conditional remapping
// Remove properties starting with underscoretype RemovePrivate<T> = { [K in keyof T as K extends `_${string}` ? never : K]: T[K];};interface Data { name: string; _id: number; _internal: boolean; value: number;}// Result: { name: string; value: number; }type PublicData = RemovePrivate<Data>;// Extract only function propertiestype FunctionProps<T> = { [K in keyof T as T[K] extends Function ? K : never]: T[K];};// Extract only non-function propertiestype DataProps<T> = { [K in keyof T as T[K] extends Function ? never : K]: T[K];};
2. Template Literal Types and String Manipulation
Type
Syntax
Description
Example
Template Literal
`${A}${B}`
Combine string literal types - creates union of all combinations
// Deep readonly - make all nested properties readonlytype DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];};interface NestedData { user: { name: string; address: { street: string; city: string; }; };}type ReadonlyData = DeepReadonly<NestedData>;// All nested properties are readonly// Deep partial - make all nested properties optionaltype DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};// Deep required - make all nested properties requiredtype DeepRequired<T> = { [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];};
Example: Path string type for nested objects
// Generate all valid property pathstype Paths<T, Prefix extends string = ''> = { [K in keyof T]: K extends string ? T[K] extends object ? `${Prefix}${K}` | Paths<T[K], `${Prefix}${K}.`> : `${Prefix}${K}` : never;}[keyof T];interface Data { user: { name: string; address: { city: string; }; }; count: number;}// Result: 'user' | 'user.name' | 'user.address' | 'user.address.city' | 'count'type DataPaths = Paths<Data>;// Get value type at pathtype PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;type CityType = PathValue<Data, 'user.address.city'>; // string
4. Higher-Order Types and Type-level Programming
Concept
Description
Pattern
Use Case
Type Constructor
Generic type that takes types as parameters
type Box<T> = { value: T }
Container types, wrappers
Higher-Order Type
Type that takes/returns type constructors
type Apply<F, T>
Generic transformations
Type-level Function
Conditional type that computes new type
type If<C, T, F>
Conditional logic
Type Composition
Combine multiple type transformations
Compose<F, G, T>
Pipeline transformations
Type-level Iteration
Build types through recursion/iteration
Tuple building, counting
Compile-time computation
Example: Type-level utilities
// Identity function at type leveltype Identity<T> = T;// Constant type - always returns same typetype Const<A, B> = A;// Type-level if/elsetype If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;// Type equality checktype Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;type Test1 = Equals<string, string>; // truetype Test2 = Equals<string, number>; // false// Type-level ANDtype And<A extends boolean, B extends boolean> = A extends true ? (B extends true ? true : false) : false;// Type-level ORtype Or<A extends boolean, B extends boolean> = A extends true ? true : (B extends true ? true : false);
Example: Tuple and array manipulation
// Tuple lengthtype Length<T extends any[]> = T['length'];// Prepend element to tupletype Prepend<E, T extends any[]> = [E, ...T];// Append element to tupletype Append<T extends any[], E> = [...T, E];// Concat tuplestype Concat<T extends any[], U extends any[]> = [...T, ...U];// Reverse tupletype Reverse<T extends any[]> = T extends [infer First, ...infer Rest] ? [...Reverse<Rest>, First] : [];type Original = [1, 2, 3];type Reversed = Reverse<Original>; // [3, 2, 1]// Build tuple of length Ntype Tuple<N extends number, T = unknown, Acc extends T[] = []> = Acc['length'] extends N ? Acc : Tuple<N, T, [T, ...Acc]>;type FiveItems = Tuple<5, string>; // [string, string, string, string, string]
Example: Type-level arithmetic (simplified)
// Add 1 to tuple length (increment)type Inc<T extends any[]> = [...T, any];// Subtract 1 from tuple length (decrement)type Dec<T extends any[]> = T extends [any, ...infer Rest] ? Rest : [];// Add two numbers using tuplestype Add<A extends number, B extends number> = [...Tuple<A>, ...Tuple<B>]['length'];type Result = Add<3, 5>; // 8 (limited by recursion depth)// Range type - generate union of numberstype Range< From extends number, To extends number, Acc extends number[] = [], Result = never> = Acc['length'] extends To ? Result | To : Range<From, To, Inc<Acc>, Acc['length'] extends From ? Result | Acc['length'] : Result>;type ZeroToFive = Range<0, 5>; // 0 | 1 | 2 | 3 | 4 | 5
5. Brand Types and Nominal Typing Patterns
Pattern
Technique
Purpose
Advantage
Brand Property
Add unique symbol property
Distinguish structurally identical types
Type safety for primitives
Phantom Type
Generic parameter not used in structure
Tag type with metadata
No runtime overhead
Opaque Type
Hide implementation details
Prevent direct construction
Encapsulation
Newtype Pattern
Wrapper type around primitive
Domain-specific types
Self-documenting code
Example: Basic branding with symbols
// Brand using unique symboldeclare const UserIdBrand: unique symbol;type UserId = string & { [UserIdBrand]: true };declare const ProductIdBrand: unique symbol;type ProductId = string & { [ProductIdBrand]: true };// Constructor functions ensure type safetyfunction createUserId(id: string): UserId { // Validation logic here return id as UserId;}function createProductId(id: string): ProductId { return id as ProductId;}// Type-safe usagefunction getUser(id: UserId) { /* ... */ }function getProduct(id: ProductId) { /* ... */ }const userId = createUserId('user-123');const productId = createProductId('prod-456');getUser(userId); // ✓ OKgetUser(productId); // ✗ Error: ProductId not assignable to UserIdgetUser('user-789'); // ✗ Error: string not assignable to UserId
Example: Branded primitive types
// Email typetype Email = string & { readonly __brand: 'Email' };function isValidEmail(str: string): str is Email { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);}function sendEmail(to: Email, subject: string) { console.log(`Sending to ${to}: ${subject}`);}const input = 'user@example.com';if (isValidEmail(input)) { sendEmail(input, 'Hello'); // ✓ Type guard narrows to Email}sendEmail(input, 'Hello'); // ✗ Error: string not Email// Positive integer typetype PositiveInt = number & { readonly __brand: 'PositiveInt' };function toPositiveInt(n: number): PositiveInt { if (!Number.isInteger(n) || n <= 0) { throw new Error('Not a positive integer'); } return n as PositiveInt;}function setArraySize(size: PositiveInt) { /* ... */ }
Example: Phantom types for state machines
// State machine with phantom typestype State = 'Initial' | 'Loaded' | 'Processed';interface DataContainer<S extends State> { data: unknown; _state?: S; // Phantom - not used at runtime}// State-specific operationsfunction load(): DataContainer<'Loaded'> { return { data: 'loaded data' };}function process( container: DataContainer<'Loaded'>): DataContainer<'Processed'> { return { data: 'processed' };}function save(container: DataContainer<'Processed'>) { console.log('Saving:', container.data);}// Type-safe workflowconst initial: DataContainer<'Initial'> = { data: null };const loaded = load();const processed = process(loaded);save(processed); // ✓ OKsave(loaded); // ✗ Error: 'Loaded' not assignable to 'Processed'process(processed); // ✗ Error: 'Processed' not assignable to 'Loaded'
Note: Brand types enable nominal typing in TypeScript's
structural type system. Use them for domain primitives (IDs, emails, URLs), validated data, and state machine
transitions. The runtime cost is zero since brands are compile-time only.
6. Type-only Imports and Exports
Syntax
Purpose
Behavior
Benefit
import type
Import only for type checking
Erased during compilation - no runtime code
Avoid circular dependencies, smaller bundles
export type
Export only type information
Type-only export, removed at runtime
Clear intent, separate concerns
inline type import
import { type T }
Mix type and value imports
Flexibility, granular control
importsNotUsedAsValues
Compiler option for side effects
Control import preservation
Prevent unwanted side effects
Example: Type-only imports
// types.tsexport interface User { id: string; name: string;}export type UserId = string;export const DEFAULT_USER: User = { id: '0', name: 'Guest'};// user.ts - type-only importimport type { User, UserId } from './types';// DEFAULT_USER not imported - no runtime dependencyfunction processUser(user: User): UserId { return user.id;}// ✗ Error: Cannot use type import as value// const guest = DEFAULT_USER;
Example: Inline type imports (TS 4.5+)
// Mixed imports - types and valuesimport { createUser, type User, type UserId } from './types';// ^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^// value type type// Can use createUser at runtimeconst newUser = createUser();// Can use User and UserId for typingfunction validate(user: User): UserId { return user.id;}// Re-export with inline typeexport { createUser, type User } from './types';
Example: Breaking circular dependencies
// module-a.tsimport type { TypeB } from './module-b';// Only imports type, not runtime codeexport interface TypeA { b: TypeB; name: string;}export function createA(): TypeA { /* ... */ }// module-b.tsimport type { TypeA } from './module-a';// Circular reference OK with type-only importexport interface TypeB { a: TypeA; value: number;}export function createB(): TypeB { /* ... */ }// Without type-only imports, this would cause circular dependency error
Example: Type-only exports
// api-types.ts - pure type definitionsexport type { User, Product, Order } from './models';// Only export types, not any runtime code// Re-export types from multiple sourcesexport type { RequestHandler, Middleware} from './handlers';export type { Config, Environment} from './config';// Can be imported in consuming codeimport type { User, Product, RequestHandler } from './api-types';
Note: Use import type when you only need types for annotations. This is especially
important for:
Breaking circular dependencies between modules
Importing from libraries where you don't want side effects
Reducing bundle size by avoiding unused imports
Making dependencies explicit in type-only contexts
Enable "verbatimModuleSyntax": true in tsconfig for stricter type import checking.
Warning: Type-only imports are erased at runtime. Don't use import type for:
Classes you need to instantiate
Functions you need to call
Values you need to reference
Modules with required side effects (initialization code)
Advanced Type Manipulation Summary
Mapped types transform object properties with key remapping for prefixes,
filters, and transformations
Template literal types enable string manipulation and pattern matching at
the type level
Recursive types handle nested structures like trees, JSON, and deep
transformations (limited by compiler depth)
Higher-order types provide type-level programming with functions,
composition, and computation
Brand types add nominal typing to distinguish structurally identical types
for domain modeling