Reducing Input Variables in TypeScript Functions
I looked at a function signature: createUser(name, email, age, role, department, managerId, startDate, isActive). Eight parameters. I had to read the impl...
19 Apr 2024

I looked at a function signature: createUser(name, email, age, role, department, managerId, startDate, isActive). Eight parameters. I had to read the implementation to figure out the order. I got two of them swapped. The TypeScript compiler didn't complain because both were strings.
Too many parameters is a code smell. It signals low cohesion — the function is trying to do too much, or its inputs aren't structured properly.
Why fewer parameters matter
Every parameter increases the cognitive load on the caller. They have to know the order, the types, and which ones are optional. Three parameters are manageable. Five is pushing it. Eight is a bug waiting to happen.
Functions with fewer parameters are easier to read, easier to test, and harder to misuse.
Strategy 1: Group related data into objects
If multiple parameters describe the same concept, bundle them.
// BEFORE: scattered parameters
function calculateOrderTotal(price: number, quantity: number, discount: number): number {
return (price * quantity) - discount;
}
// AFTER: grouped into a meaningful type
type OrderDetails = {
price: number;
quantity: number;
discount: number;
};
function calculateOrderTotal(order: OrderDetails): number {
return (order.price * order.quantity) - order.discount;
}
Now the caller passes a single, self-documenting object. Property names act as labels. Order doesn't matter.
Strategy 2: Use default parameters
If a parameter has a sensible default, don't force the caller to provide it.
// BEFORE: both required
function greet(name: string, greeting: string): void {
console.log(`${greeting}, ${name}!`);
}
// AFTER: greeting has a default
function greet(name: string, greeting: string = 'Hello'): void {
console.log(`${greeting}, ${name}!`);
}
greet('Ehsan'); // "Hello, Ehsan!"
Default parameters simplify the common case. Callers only specify what's unusual.
Strategy 3: Use configuration objects
For functions with many settings, pass a single config object. This is especially common in library APIs.
// BEFORE: too many positional arguments
function fetchData(url: string, method: string, headers: Record<string, string>, timeout: number): Promise<any> {
// ...
}
// AFTER: options object
type FetchOptions = {
url: string;
method: string;
headers: Record<string, string>;
timeout: number;
};
function fetchData(options: FetchOptions): Promise<any> {
// ...
}
Bonus: config objects are easy to extend. Adding a new option doesn't change the function signature or break existing callers.
The ideal number
Zero, one, or two parameters. Three when necessary. Beyond that, you should reach for an options object.
This isn't dogma. Some functions genuinely need multiple distinct inputs. But if you find yourself counting past three, pause and ask: can I group these? Should this function do less?
The trade-off
Object parameters add indirection. Instead of reading the arguments inline, the caller builds an object first. For simple functions called in one place, this can feel like unnecessary ceremony.
Use judgment. A sum(a, b) function doesn't need an options object. A createUser(...) function with eight fields absolutely does. The goal is readability at the call site — not blind adherence to a rule.