# Remove Assignments to Parameters

> Stop reassigning a function parameter inside its body; copy it into a local variable and work on that instead.

- kind: technique
- category: composing-methods
- canonical: https://aipatterns.dev/refactoring/techniques/remove-assignments-to-parameters
- inspired-by: https://refactoring.guru/remove-assignments-to-parameters

## Problem

A function changes the value of one of its parameters partway through. Below that line the name no longer means what the caller passed in. Readers misread it, and in languages with pass-by-reference parameters (C#'s `ref`, C++ references, PHP's `&`) the caller sees the change too.

## Solution

**Treat parameters as read-only. Introduce a local variable for the changing value and leave the parameter untouched.**

## Before

```ts
function nextRetryDelay(attempt: number, baseMs: number, maxMs: number): number {
  if (attempt > 10) attempt = 10;
  baseMs = baseMs * 2 ** attempt;
  if (baseMs > maxMs) baseMs = maxMs;
  baseMs = baseMs + Math.floor(Math.random() * 250);
  return baseMs;
}
```

## After

```ts
function nextRetryDelay(attempt: number, baseMs: number, maxMs: number): number {
  const cappedAttempt = Math.min(attempt, 10);
  const exponential = baseMs * 2 ** cappedAttempt;
  const bounded = Math.min(exponential, maxMs);
  const jitter = Math.floor(Math.random() * 250);
  return bounded + jitter;
}
```

## Why

- `baseMs` means the base delay everywhere in the function, as its name promises.
- Each intermediate value has a name, which makes logging and testing each step straightforward.
- Code that reads the original input later (a log line, a guard) still gets the original input.

## Steps

1. Declare a local variable initialized from the parameter, named for its new meaning.
2. Replace every assignment to the parameter, and every read after the first assignment, with the local.
3. Compile and run the tests.
4. If the local itself is reassigned for different purposes, apply [Split Temporary Variable](https://aipatterns.dev/refactoring/techniques/split-temporary-variable.md).
5. Mark parameters `readonly` where the language or a linter allows it (for example ESLint `no-param-reassign`).

## Watch out

- Mutating the *contents* of an object parameter (`order.status = "paid"`) is a different issue. This technique is about rebinding the name. Mutating arguments may still be a design problem, but fix it separately.
- If the language passes the parameter by reference on purpose (an `out` or `ref` parameter), the reassignment is the function's contract. Change that contract only together with its callers, typically by returning the value instead.
- Default-value normalization at the very top (`limit = limit ?? 50`) is a common idiom. Converting it is still clearer, but it is low priority.

## Related

- Helps with [Long Method](https://aipatterns.dev/refactoring/smells/long-method.md).
- [Split Temporary Variable](https://aipatterns.dev/refactoring/techniques/split-temporary-variable.md) is the same idea for locals.
- Clears the way for [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md).

## Agent checklist

- [ ] No parameter is reassigned after the change.
- [ ] Every read that used the modified value now reads the local.
- [ ] By-reference contracts (`ref`, `out`, `&`) were preserved or changed together with callers.
- [ ] Tests pass.
