Clean Code: Taming Long Parameter Lists with Objects

Imagine ordering a coffee and having to say: "I'd like a large, with skim milk, no sugar, one shot of espresso, with whipped cream, no cinnamon, in a ceramic mug, for here." It's a mouthful, and it's easy to get the order wrong. It would be much simpler to hand over a note with your preferences written down.

Functions with a long list of parameters are just like that complicated coffee order. They're hard to read, easy to call incorrectly, and a pain to modify. The solution is simple: bundle those parameters into a single "parameter object."

From a Long List to a Tidy Object

Let's look at a function for searching for flights. As more search criteria are added, the parameter list grows longer and longer.

// 👎 Bad: Too many parameters are hard to read and error-prone.
function searchFlights(
  from: string,
  to: string,
  departureDate: Date,
  returnDate: Date | null,
  passengers: number,
  cabinClass: 'economy' | 'business' | 'first',
  isFlexible: boolean
) {
  // ... searching logic
}

// Calling it is a nightmare. What was the 5th parameter again?
searchFlights('JFK', 'LAX', new Date(), null, 1, 'economy', false);

This is not ideal. It's hard to remember the order of the arguments, and adding a new one (like maxStops) would be a breaking change for every place this function is called.

Now, let's group these into a parameter object.

// 👍 Good: A single object makes the function signature clean and the call site readable.
interface FlightSearchOptions {
  from: string;
  to: string;
  departureDate: Date;
  returnDate?: Date; // Optional parameters are now easy to handle
  passengers: number;
  cabinClass: 'economy' | 'business' | 'first';
  isFlexible?: boolean;
}

function searchFlights(options: FlightSearchOptions) {
  const {
    from,
    to,
    departureDate,
    returnDate,
    passengers,
    cabinClass,
    isFlexible,
  } = options;
  // ... searching logic
}

// The call is now self-documenting!
searchFlights({
  from: 'JFK',
  to: 'LAX',
  departureDate: new Date(),
  passengers: 1,
  cabinClass: 'economy',
});

The Benefits of Parameter Objects

  1. Readability: The code at the call site becomes self-documenting. The property names (from, to, etc.) make it clear what each value represents.
  2. Flexibility: You can easily add new optional parameters to the FlightSearchOptions interface without having to update every single function call.
  3. Simpler Function Signatures: The function signature is clean and simple: it just takes options.
  4. Reusability: The FlightSearchOptions type can be reused in other parts of your application that deal with flight searches.

Great for Configuration

This pattern is especially useful for functions that take a lot of configuration options, like a function that sends an email.

// 👍 Good: A configuration object makes complex options manageable.
interface EmailOptions {
  to: string;
  subject: string;
  body: string;
  from?: string;
  cc?: string[];
  attachments?: File[];
}

function sendEmail(options: EmailOptions) {
  // Set default values for optional properties
  const fromAddress = options.from ?? 'noreply@example.com';
  // ... logic to send email
}

Next time you find yourself adding a third or fourth parameter to a function, take a moment to consider if it's time to create a parameter object. It's a small change that can make your code significantly cleaner and easier to work with.