This TypeScript error occurs when you try to assign a default value to a rest parameter, which is not allowed because rest parameters collect all remaining arguments into an array. The fix involves removing the initializer or restructuring your function parameters to use regular parameters with defaults instead.
The "Rest parameter cannot have initializer" error (TS1016) appears when you attempt to provide a default value to a rest parameter in a function declaration. Rest parameters (denoted by ...parameterName) are designed to collect all remaining arguments passed to a function into an array, making them inherently variable in length. Since rest parameters gather zero or more arguments, assigning them a default value contradicts their purpose. If no arguments are provided for the rest parameter, it simply becomes an empty array [], which is its natural default state. This error is a TypeScript syntax check that prevents logical inconsistencies in your code. For example, if you could write `function foo(...args: string[] = [])`, it would be unclear what should happen when arguments are actually provided—should they override the default empty array, or should the default only apply when no arguments are given? TypeScript's design avoids this ambiguity by prohibiting initializers on rest parameters altogether.
The simplest fix is to remove the default value assignment from the rest parameter:
// WRONG - rest parameter with initializer
function logMessages(...messages: string[] = []) {
messages.forEach(msg => console.log(msg));
}
// CORRECT - remove the initializer
function logMessages(...messages: string[]) {
messages.forEach(msg => console.log(msg));
}
// Now the function works correctly
logMessages("Hello", "World"); // Logs: Hello, World
logMessages(); // Logs nothing (messages is empty array [])Remember: When no arguments are provided, the rest parameter automatically becomes an empty array. You don't need to specify this default.
If you need default values, place them in regular parameters before the rest parameter:
// WRONG - trying to give rest parameter a default
function processItems(...items: any[] = []) {
// Process items
}
// CORRECT - use a regular parameter with default before rest parameter
function processItems(firstItem: any = null, ...remainingItems: any[]) {
const allItems = firstItem !== null ? [firstItem, ...remainingItems] : remainingItems;
// Process allItems
}
// Alternative: Handle empty case inside function
function processItems(...items: any[]) {
if (items.length === 0) {
// Handle empty case
return;
}
// Process items
}This pattern gives you control over default behavior while keeping the rest parameter flexible.
This error also occurs in plain JavaScript. Verify your code works in both environments:
// This is ALSO invalid in JavaScript!
function invalid(...args = []) {
console.log(args);
}
// JavaScript error: "Rest parameter may not have a default initializer"
// Valid JavaScript/TypeScript:
function valid(...args) {
console.log(args);
}
// Test in Node.js to confirm:
node -e "function test(...args=[]) { console.log(args) }"
// Throws: SyntaxError: Rest parameter may not have a default initializerIf migrating from JavaScript, ensure your code doesn't contain this invalid syntax that some JavaScript engines might have tolerated.
If you need specific default values, consider restructuring your function signature:
// Scenario: You want at least one item with a default
// WRONG approach:
function addItems(...items: string[] = ["default"]) {
// This doesn't make sense - what if user provides items?
}
// CORRECT approach 1: Separate required parameter
function addItems(firstItem: string = "default", ...additionalItems: string[]) {
const allItems = [firstItem, ...additionalItems];
// Process allItems
}
// CORRECT approach 2: Overload the function
function addItems(item: string): void;
function addItems(...items: string[]): void;
function addItems(...items: string[]) {
if (items.length === 0) {
items = ["default"]; // Set default inside function
}
// Process items
}
// CORRECT approach 3: Use optional parameters
function addItems(item1?: string, item2?: string, item3?: string) {
const items = [item1, item2, item3].filter(Boolean) as string[];
if (items.length === 0) {
items.push("default");
}
// Process items
}Choose the approach that best fits your use case.
Ensure your tsconfig.json isn't suppressing syntax errors:
{
"compilerOptions": {
// These settings help catch syntax errors early
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
// Don't use these if you want to catch syntax errors:
// "skipLibCheck": true, // Still checks your code
// "noEmitOnError": false // Would emit despite errors
// Good practice for development
"noUnusedLocals": true,
"noUnusedParameters": true
}
}Run TypeScript with verbose output to see all errors:
npx tsc --noEmit --listFiles
# or for specific file
npx tsc --noEmit your-file.tsThis ensures you catch all syntax issues during development.
For advanced use cases, consider these patterns:
// Pattern 1: Function that accepts array or rest parameters
function processItems(items: string[]): void;
function processItems(...items: string[]): void;
function processItems(...args: [string[]] | string[]) {
const items = Array.isArray(args[0]) ? args[0] : args;
if (items.length === 0) {
// Handle empty case
console.log("No items provided");
return;
}
// Process items
}
// Pattern 2: Builder pattern with defaults
class ItemProcessor {
private items: string[] = [];
addItems(...items: string[]): this {
this.items.push(...items);
return this;
}
process() {
if (this.items.length === 0) {
this.items = ["default"];
}
// Process this.items
}
}
// Pattern 3: Configuration object with rest parameters
interface ProcessOptions {
requiredItem?: string;
additionalItems?: string[];
}
function processWithOptions(options: ProcessOptions) {
const items = [
...(options.requiredItem ? [options.requiredItem] : []),
...(options.additionalItems || [])
];
if (items.length === 0) {
items.push("default");
}
// Process items
}These patterns provide type safety while avoiding rest parameter limitations.
### Understanding Rest Parameters in TypeScript/JavaScript
Rest parameters were introduced in ES2015 (ES6) and have specific rules:
1. Only one rest parameter per function: function foo(...a, ...b) is invalid
2. Must be last parameter: function foo(...args, lastParam) is invalid
3. No default values: function foo(...args = []) is invalid
4. Type is always array: Rest parameters always have array type, even if only one argument is passed
### Historical Context
Before rest parameters, developers used the arguments object:
// Old style (pre-ES6)
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// Modern style (ES6+)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}The arguments object had its own quirks (array-like but not array, doesn't work with arrow functions), which rest parameters solve.
### TypeScript-Specific Considerations
TypeScript enhances rest parameters with type annotations:
// Typed rest parameter
function joinStrings(separator: string, ...strings: string[]): string {
return strings.join(separator);
}
// Generic rest parameters
function mergeArrays<T>(...arrays: T[][]): T[] {
return ([] as T[]).concat(...arrays);
}
// Tuple rest parameters (TypeScript 4.0+)
function foo(...args: [string, number, ...boolean[]]) {
// args[0] is string, args[1] is number, rest are boolean
}### Common Misconceptions
1. "Rest parameters are optional": They are always present as an array (possibly empty), never undefined
2. "I need to check if rest parameter is undefined": No need—it's always an array
3. "Default empty array improves performance": No—JavaScript engines create empty arrays efficiently
4. "This works in some JavaScript engines": It shouldn't—it's invalid syntax per ECMAScript specification
### Edge Cases and Workarounds
For functions that genuinely need different behavior based on argument count:
// Using function overloads
function createArray(): string[];
function createArray(item: string): string[];
function createArray(...items: string[]): string[];
function createArray(...args: string[]): string[] {
if (args.length === 0) {
return ["default"];
}
return args;
}
// Using conditional types
type DefaultIfEmpty<T extends any[]> = T extends [] ? ["default"] : T;
function createArray<T extends string[]>(...items: T): DefaultIfEmpty<T> {
if (items.length === 0) {
return ["default"] as DefaultIfEmpty<T>;
}
return items as DefaultIfEmpty<T>;
}### Tooling Support
Most IDEs and linters catch this error early:
- VS Code: Red squiggles with hover explanation
- ESLint: no-rest-parameter-initializer rule (if using appropriate parser)
- Prettier: May reformat to highlight the issue
- TypeScript Language Server: Provides quick fixes to remove the initializer
### Related Errors
- TS1015: "Parameter cannot have question mark and initializer"
- TS2371: "A parameter initializer is only allowed in a function or constructor implementation"
- TS2300: "Duplicate identifier" (if confusing rest parameters with other syntax)
Remember: Rest parameters are designed to be simple, predictable argument collectors. Their lack of default values is a feature, not a limitation—it ensures consistent behavior across all function calls.
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