Bird
Raised Fist0
Typescriptprogramming~10 mins

Phantom types in Typescript - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Phantom types
Define generic type with phantom parameter
↓
Use phantom type to tag data
↓
Create values with phantom types
↓
Type system enforces distinctions
↓
Prevent mixing incompatible types
↓
Safe code
Phantom types add extra type info that doesn't exist at runtime but helps TypeScript catch errors by distinguishing similar data.
Execution Sample
Typescript
type Phantom<T, PhantomType> = T & { __phantom?: PhantomType };

// Define two phantom types
type USD = { readonly USD: unique symbol };
type EUR = { readonly EUR: unique symbol };

// Create phantom types for money
const priceUSD: Phantom<number, USD> = 10 as Phantom<number, USD>;
const priceEUR: Phantom<number, EUR> = 15 as Phantom<number, EUR>;

// Function only accepts USD
function addUSD(a: Phantom<number, USD>, b: Phantom<number, USD>): Phantom<number, USD> {
  return (a + b) as Phantom<number, USD>;
}
This code uses phantom types to tag numbers as USD or EUR, so TypeScript prevents mixing them accidentally.
Execution Table
StepActionType CheckedResultNotes
1Define Phantom generic typePhantom<T, PhantomType>Type createdPhantom adds phantom type info
2Define USD and EUR phantom typesUSD, EURTypes createdUnique symbols used for tagging
3Create priceUSD as Phantom<number, USD>priceUSDValue 10 tagged as USDTypeScript treats as number with USD tag
4Create priceEUR as Phantom<number, EUR>priceEURValue 15 tagged as EURDifferent phantom tag from USD
5Call addUSD(priceUSD, priceUSD)ValidReturns Phantom<number, USD>Allowed: same phantom type
6Call addUSD(priceUSD, priceEUR)ErrorType errorCannot mix USD and EUR phantom types
7Return from addUSDPhantom<number, USD>Safe USD sumPhantom type ensures safety
💡 Execution stops because mixing different phantom types causes a compile-time error.
Variable Tracker
VariableStartAfter Step 3After Step 4After Step 5Final
priceUSDundefined10 (tagged USD)10 (tagged USD)10 (tagged USD)10 (tagged USD)
priceEURundefinedundefined15 (tagged EUR)15 (tagged EUR)15 (tagged EUR)
addUSD resultundefinedundefinedundefined20 (tagged USD)20 (tagged USD)
Key Moments - 3 Insights
Why can't I add priceUSD and priceEUR even though both are numbers?
Because priceUSD and priceEUR have different phantom types (USD vs EUR), TypeScript treats them as incompatible to prevent mixing currencies. See execution_table step 6.
Does the phantom type exist at runtime?
No, phantom types are only for TypeScript's type system and do not exist in the compiled JavaScript. They help catch errors during development without affecting runtime.
How does the phantom type prevent mixing incompatible types?
The phantom type adds a unique tag to the type signature, so TypeScript sees them as distinct types even if the underlying data is the same. This is shown in execution_table steps 3-6.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table at step 6. What happens when you try to add priceUSD and priceEUR?
AThe addition works and returns a number
BTypeScript shows a type error preventing the operation
CThe code runs but logs a warning
DThe phantom types are ignored and addition succeeds
💡 Hint
Check execution_table row 6 where mixing USD and EUR phantom types causes an error.
According to variable_tracker, what is the value of addUSD result after step 5?
A20 tagged as USD
B25 tagged as EUR
CUndefined
D10 tagged as USD
💡 Hint
Look at variable_tracker row for addUSD result after step 5.
If you remove the phantom type from priceUSD, what would happen when adding priceUSD and priceEUR?
ARuntime error would occur
BTypeScript would still prevent adding them
CTypeScript would allow adding them without error
DThe phantom type would be inferred automatically
💡 Hint
Phantom types are what make TypeScript distinguish these values; without them, they are just numbers.
Concept Snapshot
Phantom types in TypeScript:
- Use a generic type with an unused type parameter to tag data.
- Phantom types add compile-time info only, no runtime cost.
- They help distinguish similar data (e.g., currencies) to prevent mixing.
- Example: type Phantom<T, P> = T & { __phantom?: P }.
- TypeScript enforces type safety using phantom tags.
Full Transcript
Phantom types are a way to add extra type information in TypeScript that does not exist at runtime but helps the compiler catch mistakes. We define a generic type that combines a real type with a phantom tag. For example, we tag numbers as USD or EUR using phantom types. When we create values with these tags, TypeScript treats them as different types even though they are both numbers. This prevents us from mixing currencies by mistake. The code example shows defining phantom types, creating tagged values, and a function that only accepts USD tagged numbers. The execution table traces each step: defining types, creating values, and calling the function. It shows that adding two USD values works, but mixing USD and EUR causes a type error. The variable tracker shows how values change with phantom tags. Key moments clarify that phantom types exist only at compile time and prevent mixing incompatible types. The quiz tests understanding of type errors, variable values, and the role of phantom types. The snapshot summarizes phantom types as compile-time tags that improve safety without runtime cost.

Practice

(1/5)
1. What is the main purpose of using phantom types in TypeScript?
easy
A. To improve performance by removing types
B. To create new objects at runtime
C. To add extra type information without affecting runtime data
D. To allow dynamic typing in TypeScript

Solution

  1. 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.
  2. 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.
  3. Final Answer:

    To add extra type information without affecting runtime data -> Option C
  4. Quick Check:

    Phantom types = extra compile-time info [OK]
Hint: Phantom types add info only at compile time, no runtime change [OK]
Common Mistakes:
  • Thinking phantom types create new objects
  • Confusing phantom types with runtime data changes
  • Believing phantom types improve runtime performance
2. Which of the following is the correct way to declare a phantom type in TypeScript?
easy
A. type UserId = string & { readonly __brand: unique symbol }
B. type UserId = string | number
C. type UserId = string[]
D. type UserId = number

Solution

  1. 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.
  2. 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.
  3. Final Answer:

    type UserId = string & { readonly __brand: unique symbol } -> Option A
  4. Quick Check:

    Phantom type = base type & unique brand [OK]
Hint: Phantom types use intersection with unique symbol branding [OK]
Common Mistakes:
  • Using union types instead of intersection for phantom types
  • Declaring phantom types as arrays or simple types
  • Missing the unique symbol branding
3. What will be the result of this TypeScript code?
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);
medium
A. NaN
B. 15
C. Type error at addDistance call
D. undefined

Solution

  1. 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.
  2. Step 2: Analyze code output

    dist1 and dist2 are numbers with phantom type. Adding them returns 15, which is logged.
  3. Final Answer:

    15 -> Option B
  4. Quick Check:

    Phantom types don't affect runtime value [OK]
Hint: Phantom types vanish at runtime; values behave normally [OK]
Common Mistakes:
  • Expecting type errors at runtime
  • Thinking phantom types change runtime values
  • Confusing phantom types with runtime wrappers
4. Identify the error in this phantom type usage:
type UserId = string & { readonly __brand: unique symbol };
function getUser(id: UserId) {
  return `User: ${id}`;
}
const id = 'abc123';
getUser(id);
medium
A. Function getUser should return a number, not string
B. No error, code runs fine
C. Unique symbol cannot be used in type intersections
D. Passing a plain string instead of branded UserId causes a type error

Solution

  1. 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.
  2. Step 2: Validate other options

    Return type is correct, unique symbol usage is valid, and code does not run fine due to type mismatch.
  3. Final Answer:

    Passing a plain string instead of branded UserId causes a type error -> Option D
  4. Quick Check:

    Phantom type mismatch = type error [OK]
Hint: Phantom types require branded values, not plain base types [OK]
Common Mistakes:
  • Ignoring phantom brand when passing values
  • Thinking unique symbol is invalid in types
  • Assuming no error when types differ
5. How can phantom types help prevent mixing different ID types in a TypeScript project?
Consider these types:
type UserId = string & { readonly __brand: 'UserId' };
type ProductId = string & { readonly __brand: 'ProductId' };

What is the main benefit of this approach?
hard
A. It prevents accidentally using a ProductId where a UserId is expected at compile time
B. It improves runtime performance by optimizing string storage
C. It automatically converts between UserId and ProductId
D. It allows mixing IDs freely without errors

Solution

  1. Step 1: Understand phantom branding for IDs

    Phantom types add unique brands to similar base types (string) to distinguish them at compile time.
  2. Step 2: Identify benefit in type safety

    This branding prevents passing a ProductId where a UserId is expected, catching errors before running code.
  3. Final Answer:

    It prevents accidentally using a ProductId where a UserId is expected at compile time -> Option A
  4. Quick Check:

    Phantom types enforce distinct types safely [OK]
Hint: Phantom types catch ID mix-ups at compile time [OK]
Common Mistakes:
  • Thinking phantom types affect runtime performance
  • Assuming automatic conversion between branded types
  • Believing phantom types allow mixing without errors
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard