When a function takes more than three or four parameters, it becomes difficult to use correctly. Callers struggle to remember the order, the meaning of each argument, and which are optional. This is a code smell with several well-known cures.

Why Long Parameter Lists Happen

  1. Organic growth: Parameters added one at a time over months
  2. Missing abstractions: Related parameters that should be grouped
  3. God Functions: The function does too much, needing data for all concerns
  4. Avoided refactoring: Quicker to add a param than restructure

The Cures

1. Introduce Parameter Object

// ❌ Before
function createUser(
  name: string, email: string, age: number, 
  street: string, city: string, zip: string, country: string,
  role: string, department: string,
) { }

// ✅ After — group related parameters
interface Address { street: string; city: string; zip: string; country: string; }
interface CreateUserRequest { name: string; email: string; age: number; address: Address; role: string; department: string; }

function createUser(request: CreateUserRequest) { }

2. Extract Method

If the function needs many parameters because it does too much, split it:

// ❌ One function handling multiple concerns
function processOrder(items: Item[], userId: string, paymentMethod: string,
  cardNumber: string, shippingSpeed: string, giftWrap: boolean,
  couponCode: string, notes: string) { }

// ✅ Split into focused functions
function calculateTotal(items: Item[], couponCode?: string): OrderTotal { }
function processPayment(total: number, payment: PaymentDetails): PaymentResult { }
function createShipment(items: Item[], speed: ShippingSpeed, giftWrap: boolean): Shipment { }

3. Builder Pattern

For optional parameters in complex object construction:

// ✅ Builder lets callers set only what they need
const query = new QueryBuilder('users')
  .where('active', true)
  .where('age', '>', 18)
  .orderBy('name')
  .limit(50)
  .build();

4. Use Sensible Defaults

interface PaginationOptions {
  page?: number;      // default: 1
  pageSize?: number;  // default: 20
  sortBy?: string;    // default: 'createdAt'
  order?: 'asc' | 'desc'; // default: 'desc'
}

function listUsers(options: PaginationOptions = {}): Promise<User[]> {
  const { page = 1, pageSize = 20, sortBy = 'createdAt', order = 'desc' } = options;
  // ...
}

// Caller specifies only what differs from defaults
listUsers({ page: 3 });
listUsers({ sortBy: 'name', order: 'asc' });

The Boolean Trap

Boolean parameters are especially problematic — they’re meaningless at the call site:

// ❌ What do these booleans mean?
render(template, data, true, false, true);

// ✅ Use an options object
render(template, data, { 
  escapeHtml: true, 
  minify: false, 
  cache: true 
});

Rule of Thumb

Parameter CountAction
0-2Fine as-is
3Consider grouping if related
4+Refactor — introduce parameter object
6+The function likely does too much — split it

“Functions should have a small number of arguments. No argument is best, followed by one, two, and three.” — Robert C. Martin