Phantom types help us add extra information to types without changing the actual data. This keeps our code safer and clearer.
Phantom types in Typescript
type Phantom<Type, PhantomType> = Type & { __phantom?: PhantomType }Phantom types use a type parameter that does not affect the runtime value.
The extra property (like __phantom) is optional and never used at runtime.
UserId type that is really a string but marked specially to avoid mixing with other strings.type UserId = string & { __brand: 'UserId' };
function getUser(id: UserId) {
console.log(`User ID is ${id}`);
}type ProductId = string & { __brand: 'ProductId' };
const userId: UserId = 'abc123' as UserId;
const productId: ProductId = 'abc123' as ProductId;
// getUser(productId); // Error: Argument of type 'ProductId' is not assignable to parameter of type 'UserId'.type Phantom<T, PhantomType> = T & { __phantom?: PhantomType };
type Meter = Phantom<number, 'Meter'>;
type Second = Phantom<number, 'Second'>;
const distance = 5 as Meter;
const time = 10 as Second;
// const wrong = distance + time; // Error: Operator '+' cannot be applied to types 'Meter' and 'Second'.This program defines phantom types for user and product IDs to avoid mixing them. It shows how the compiler helps catch mistakes.
type Phantom<T, PhantomType> = T & { __phantom?: PhantomType };
type UserId = Phantom<string, 'UserId'>;
type ProductId = Phantom<string, 'ProductId'>;
function getUser(id: UserId) {
console.log(`User ID is ${id}`);
}
const userId = 'user-123' as UserId;
const productId = 'prod-456' as ProductId;
getUser(userId); // Works fine
// getUser(productId); // Uncommenting this line causes a compile-time errorPhantom types do not exist at runtime, so they add no extra cost.
Use as to tell TypeScript when you want to treat a value as a phantom type.
Phantom types help catch bugs early by making types more specific.
Phantom types add extra meaning to types without changing data.
They help prevent mixing similar but different data types.
They improve code safety and clarity with no runtime cost.
Practice
phantom types in TypeScript?Solution
Step 1: Understand phantom types concept
Phantom types add extra type information that exists only at compile time and does not affect the actual data or runtime behavior.Step 2: Compare options with concept
Only To add extra type information without affecting runtime data correctly states that phantom types add extra type info without changing runtime data. Other options describe unrelated concepts.Final Answer:
To add extra type information without affecting runtime data -> Option CQuick Check:
Phantom types = extra compile-time info [OK]
- Thinking phantom types create new objects
- Confusing phantom types with runtime data changes
- Believing phantom types improve runtime performance
Solution
Step 1: Identify phantom type syntax
Phantom types use intersection with a unique symbol or branded property to add phantom info without changing the base type.Step 2: Check each option
type UserId = string & { readonly __brand: unique symbol } uses intersection with a unique symbol brand, which is the correct phantom type pattern. Others are normal types or unions.Final Answer:
type UserId = string & { readonly __brand: unique symbol } -> Option AQuick Check:
Phantom type = base type & unique brand [OK]
- Using union types instead of intersection for phantom types
- Declaring phantom types as arrays or simple types
- Missing the unique symbol branding
type Meter = number & { readonly __unit: 'meter' };
function addDistance(a: Meter, b: Meter): Meter {
return (a + b) as Meter;
}
const dist1 = 5 as Meter;
const dist2 = 10 as Meter;
const total = addDistance(dist1, dist2);
console.log(total);Solution
Step 1: Understand phantom type behavior at runtime
Phantom types exist only at compile time. At runtime, they are just numbers. So adding two numbers works normally.Step 2: Analyze code output
dist1 and dist2 are numbers with phantom type. Adding them returns 15, which is logged.Final Answer:
15 -> Option BQuick Check:
Phantom types don't affect runtime value [OK]
- Expecting type errors at runtime
- Thinking phantom types change runtime values
- Confusing phantom types with runtime wrappers
type UserId = string & { readonly __brand: unique symbol };
function getUser(id: UserId) {
return `User: ${id}`;
}
const id = 'abc123';
getUser(id);Solution
Step 1: Check argument type for getUser
getUser expects a UserId (string with phantom brand). Passing plain string 'abc123' lacks the brand, causing a type error.Step 2: Validate other options
Return type is correct, unique symbol usage is valid, and code does not run fine due to type mismatch.Final Answer:
Passing a plain string instead of branded UserId causes a type error -> Option DQuick Check:
Phantom type mismatch = type error [OK]
- Ignoring phantom brand when passing values
- Thinking unique symbol is invalid in types
- Assuming no error when types differ
Consider these types:
type UserId = string & { readonly __brand: 'UserId' };
type ProductId = string & { readonly __brand: 'ProductId' };What is the main benefit of this approach?
Solution
Step 1: Understand phantom branding for IDs
Phantom types add unique brands to similar base types (string) to distinguish them at compile time.Step 2: Identify benefit in type safety
This branding prevents passing a ProductId where a UserId is expected, catching errors before running code.Final Answer:
It prevents accidentally using a ProductId where a UserId is expected at compile time -> Option AQuick Check:
Phantom types enforce distinct types safely [OK]
- Thinking phantom types affect runtime performance
- Assuming automatic conversion between branded types
- Believing phantom types allow mixing without errors
Ready to prove it?
Pass the test and this pattern moves unknown
