# Introduce Parameter Object

> Replace a group of parameters that always travel together with one object type, then move behavior on that group into it.

- kind: technique
- category: simplifying-method-calls
- canonical: https://aipatterns.dev/refactoring/techniques/introduce-parameter-object
- inspired-by: https://refactoring.guru/introduce-parameter-object

## Problem

Several methods take the same cluster of parameters, such as `start` and `end`, or `amount` and `currency`. The values only make sense together. Callers can pass them in the wrong order, and the rules that link them (start before end) are rechecked or forgotten in each method.

## Solution

**Create a type that holds the group, replace the parameters with one parameter of that type, and move the logic that belongs to the group into it.**

## Before

```ts
function countBookings(roomId: string, from: Date, to: Date): Promise<number> {
  if (from >= to) throw new RangeError("from must be before to");
  return db.bookings.count({ roomId, startsAt: { gte: from, lt: to } });
}

function revenue(roomId: string, from: Date, to: Date, currency: string): Promise<number> {
  if (from >= to) throw new RangeError("from must be before to");
  return db.payments.sum({ roomId, currency, paidAt: { gte: from, lt: to } });
}

await revenue("r-12", new Date("2026-01-01"), new Date("2026-02-01"), "EUR");
```

## After

```ts
class DateRange {
  constructor(readonly from: Date, readonly to: Date) {
    if (from >= to) throw new RangeError("from must be before to");
  }
  toFilter(): { gte: Date; lt: Date } {
    return { gte: this.from, lt: this.to };
  }
}

function countBookings(roomId: string, period: DateRange): Promise<number> {
  return db.bookings.count({ roomId, startsAt: period.toFilter() });
}

function revenue(roomId: string, period: DateRange, currency: string): Promise<number> {
  return db.payments.sum({ roomId, currency, paidAt: period.toFilter() });
}

await revenue("r-12", new DateRange(new Date("2026-01-01"), new Date("2026-02-01")), "EUR");
```

## Why

- Signatures are shorter and say what they work with.
- The invariant is enforced once, at construction, so an invalid range cannot exist.
- The new type attracts behavior (`contains`, `overlaps`, `lengthInDays`) that was scattered.

## Steps

1. Create an immutable type with the grouped fields. Run the tests.
2. Add a parameter of the new type to one method, keeping the old parameters. Update its callers to pass both. Run the tests.
3. Inside the method, read from the new object instead of each old parameter, then remove the old parameters. Run the tests.
4. Repeat for every method that takes the same group.
5. Move duplicated logic about the group (validation, conversion) into the new type. Run the tests.

## Watch out

- Do not group parameters that only coincidentally appear together in one method. The group should show up in several signatures or have rules linking its members.
- Make the object immutable. A mutable parameter object shared between callers leads to surprising action at a distance.
- If an existing object already holds the values, use [Preserve Whole Object](https://aipatterns.dev/refactoring/techniques/preserve-whole-object.md) instead of creating a new type.
- In JavaScript and TypeScript, a plain options object (`{ from, to }`) is a lighter first step when you only need named arguments, not invariants.
- Unwrapping a parameter object back into separate parameters has no named technique; do it only when the type stops pulling its weight.

## Related

- Fixes [Long Parameter List](https://aipatterns.dev/refactoring/smells/long-parameter-list.md), [Data Clumps](https://aipatterns.dev/refactoring/smells/data-clumps.md) and [Primitive Obsession](https://aipatterns.dev/refactoring/smells/primitive-obsession.md).
- Alternative: [Preserve Whole Object](https://aipatterns.dev/refactoring/techniques/preserve-whole-object.md).
- Next step: [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md) into the new type, as with [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md).

## Agent checklist

- [ ] The same group of parameters appears in more than one signature, or its members share a rule.
- [ ] The new type is immutable and validates its invariants on construction.
- [ ] Each method was migrated separately with tests green in between.
- [ ] Duplicated validation for the group was moved into the type and removed from methods.
