Long Parameter Lists: Causes and Cures
Functions with too many parameters are hard to call, hard to test, and hard to maintain. Learn systematic approaches to taming them.
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
- Organic growth: Parameters added one at a time over months
- Missing abstractions: Related parameters that should be grouped
- God Functions: The function does too much, needing data for all concerns
- 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 Count | Action |
|---|---|
| 0-2 | Fine as-is |
| 3 | Consider 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