This TypeScript error occurs when you try to use one function parameter's value to initialize another parameter's default value. TypeScript doesn't allow parameter references in default value expressions because parameters are evaluated in order. The fix involves restructuring your function to use destructuring, optional parameters, or separate initialization logic.
The "Parameter cannot be referenced in another parameter's initializer" error appears when you attempt to use one parameter's value to set the default value of another parameter in a function signature. This violates TypeScript's parameter initialization rules because parameters are evaluated in the order they appear, and you cannot reference a parameter that hasn't been initialized yet. In TypeScript (and JavaScript), function parameters with default values are evaluated from left to right when the function is called. If parameter B tries to reference parameter A in its default value expression, but parameter A might not have been provided by the caller, this creates an ambiguous situation that TypeScript cannot safely resolve. This error commonly occurs when: 1. Trying to create dependent default values between parameters 2. Using object destructuring where one property depends on another 3. Implementing function overloads with complex default logic 4. Migrating JavaScript code that uses runtime checks to TypeScript The error prevents subtle bugs where parameter initialization order could lead to undefined behavior.
Instead of having multiple interdependent parameters, use a single object parameter with destructuring:
// WRONG - parameter b references parameter a
function calculate(a: number = 10, b: number = a * 2) {
return a + b;
}
// Error: Parameter 'a' cannot be referenced in another parameter's initializer
// CORRECT - use object parameter with destructuring
function calculate({ a = 10, b = a * 2 }: { a?: number; b?: number } = {}) {
return a + b;
}
// Usage remains similar
calculate(); // returns 30 (10 + 20)
calculate({ a: 5 }); // returns 15 (5 + 10)
calculate({ a: 5, b: 3 }); // returns 8 (5 + 3)This approach gives you full control over the initialization order since all defaults are evaluated together in the destructuring pattern.
Move the dependent logic inside the function body:
// WRONG - dependent default values
function createUser(
name: string,
displayName: string = name.toUpperCase(),
email: string = `${name.toLowerCase()}@example.com`
) {
return { name, displayName, email };
}
// CORRECT - handle defaults inside function
function createUser(
name: string,
displayName?: string,
email?: string
) {
const finalDisplayName = displayName ?? name.toUpperCase();
const finalEmail = email ?? `${name.toLowerCase()}@example.com`;
return { name, displayName: finalDisplayName, email: finalEmail };
}
// Alternative with destructuring
function createUser(options: {
name: string;
displayName?: string;
email?: string;
}) {
const { name, displayName = name.toUpperCase(), email = `${name.toLowerCase()}@example.com` } = options;
return { name, displayName, email };
}This makes the dependency explicit and easier to understand.
For APIs that need to support multiple calling patterns, use function overloads:
// Function overloads provide type safety for different parameter patterns
function configure(options: { width: number; height?: number }): void;
function configure(width: number, height?: number): void;
function configure(
widthOrOptions: number | { width: number; height?: number },
height?: number
): void {
let actualWidth: number;
let actualHeight: number;
if (typeof widthOrOptions === 'object') {
actualWidth = widthOrOptions.width;
actualHeight = widthOrOptions.height ?? actualWidth; // Now safe
} else {
actualWidth = widthOrOptions;
actualHeight = height ?? actualWidth; // Now safe
}
console.log(`Width: ${actualWidth}, Height: ${actualHeight}`);
}
// All these calls work:
configure({ width: 100 });
configure(100);
configure({ width: 100, height: 200 });
configure(100, 200);Function overloads let you define the public API separately from the implementation.
If parameters are truly independent, give them separate default values:
// WRONG - height depends on width
function createBox(width: number = 100, height: number = width) {
return { width, height };
}
// CORRECT - independent defaults
function createBox(width: number = 100, height: number = 100) {
return { width, height };
}
// OR use a configuration object
function createBox(options: { width?: number; height?: number } = {}) {
const width = options.width ?? 100;
const height = options.height ?? width; // Now safe inside function body
return { width, height };
}
// For square boxes specifically
function createSquare(size: number = 100) {
return createBox({ width: size, height: size });
}This approach makes the function's behavior more predictable.
In rare cases where you're certain about parameter order, you can use non-null assertions (use with caution):
// Advanced technique - use with caution
function processData<T>(
data: T,
// Use non-null assertion to tell TypeScript we know what we're doing
processor: (item: T) => void = (item) => console.log(item!)
) {
processor(data);
}
// Better alternative - factory function
function createProcessor<T>(defaultProcessor?: (item: T) => void) {
return function processData(data: T, processor = defaultProcessor ?? ((item) => console.log(item))) {
processor(data);
};
}
// Usage
const processString = createProcessor<string>();
processString("hello"); // logs "hello"
processString("world", (s) => console.log(s.toUpperCase())); // logs "WORLD"Non-null assertions should be used sparingly and only when you have complete control over the calling context.
For complex initialization with many interdependent values, use a builder pattern:
// Builder pattern for complex object creation
class UserBuilder {
private name: string;
private displayName?: string;
private email?: string;
constructor(name: string) {
this.name = name;
}
withDisplayName(displayName: string): this {
this.displayName = displayName;
return this;
}
withEmail(email: string): this {
this.email = email;
return this;
}
build() {
return {
name: this.name,
displayName: this.displayName ?? this.name.toUpperCase(),
email: this.email ?? `${this.name.toLowerCase()}@example.com`,
};
}
}
// Usage
const user1 = new UserBuilder("john").build();
const user2 = new UserBuilder("jane")
.withDisplayName("Jane Doe")
.withEmail("[email protected]")
.build();
// Configuration object factory
function createUserConfig(name: string, overrides?: Partial<{ displayName: string; email: string }>) {
const config = {
name,
displayName: name.toUpperCase(),
email: `${name.toLowerCase()}@example.com`,
...overrides,
};
return config;
}This pattern provides maximum flexibility and clear initialization order.
### Understanding Parameter Evaluation Order
TypeScript (and JavaScript) evaluates function parameters from left to right:
function example(a = 1, b = 2, c = a + b) {
console.log(a, b, c);
}When called as example():
1. a gets default value 1
2. b gets default value 2
3. c tries to evaluate a + b (which would be 3)
However, TypeScript's static analysis can't guarantee this will work because:
- The caller might provide b but not a: example(undefined, 5)
- The parameters might have different types than expected
- Default values could be complex expressions with side effects
### Destructuring Parameter Patterns
Destructuring in parameters follows different rules:
// This works - destructuring evaluates all defaults together
function drawRect({
x = 0,
y = 0,
width = 100,
height = width // ✅ Allowed in destructuring
}: {
x?: number;
y?: number;
width?: number;
height?: number;
} = {}) {
// ...
}
// This doesn't work - regular parameters
function drawRect(
x = 0,
y = 0,
width = 100,
height = width // ❌ Not allowed
) {
// ...
}Destructuring patterns are evaluated as a single unit, so dependencies between properties are allowed.
### TypeScript Version Considerations
This error has been consistent across TypeScript versions, but the exact error message and error code might vary:
- TypeScript 2.0+: Basic parameter reference checking
- TypeScript 3.0+: Improved destructuring parameter checking
- TypeScript 4.0+: Better error messages with parameter names
### JavaScript Interoperability
JavaScript allows parameter references in default values at runtime, but the behavior can be surprising:
// JavaScript allows this but has edge cases
function jsExample(a = 1, b = a * 2) {
console.log(a, b);
}
jsExample(); // 1, 2
jsExample(5); // 5, 10
jsExample(5, 20); // 5, 20
jsExample(undefined, 20); // 1, 20 - 'a' gets default, 'b' gets 20TypeScript's restriction prevents these edge cases and ensures type safety.
### Alternative: Using Rest Parameters
For functions with variable argument patterns:
function flexibleExample(...args: [number, number?] | [{ a: number; b?: number }]) {
if (args.length === 1 && typeof args[0] === 'object') {
const { a, b = a * 2 } = args[0];
return a + b;
} else {
const [a, b = a * 2] = args;
return a + b;
}
}This pattern gives you runtime flexibility while maintaining type safety.
### Performance Considerations
While the error is about type safety, there are also runtime implications:
1. Default value expressions are evaluated on each call - complex expressions in defaults can impact performance
2. Destructuring has overhead - object destructuring creates temporary variables
3. Builder patterns add abstraction - useful for complex cases but adds code complexity
For performance-critical code, consider:
- Using simple, independent default values
- Moving complex initialization to factory functions
- Using configuration objects passed by reference
### Testing Strategies
When refactoring away from parameter dependencies:
1. Preserve existing behavior: Write tests that verify all existing calling patterns still work
2. Test edge cases: Call with undefined, null, missing parameters
3. Type safety: Verify TypeScript catches invalid calls at compile time
4. Runtime behavior: Ensure the refactored code behaves identically to the original JavaScript
### Migration from JavaScript
When migrating JavaScript code that uses parameter dependencies:
1. Identify all parameter dependencies using search: = [a-zA-Z_$][a-zA-Z0-9_$]* in default values
2. Choose the appropriate refactoring pattern based on usage
3. Update call sites if changing from positional to object parameters
4. Add type annotations to the new parameter structure
5. Test thoroughly with existing test suites and real usage patterns
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript