Long Parameter List: group related arguments into objects
Nine positional arguments are easy to mix up. Learn to group related values into objects and make options keyword-only, with tested before and after code.

A Long Parameter List is a function that takes four, five, nine arguments. The call is hard to read (create_user("a", "b", 24, "c", ...)), easy to get wrong (two booleans swapped), and hard to extend. The code and output below were run with Python 3 before publishing.
How to recognise it
- You count arguments on your fingers.
- Calls have several literals in a row, and you cannot tell what each one means without opening the function.
- Two parameters of the same type sit next to each other (two booleans, two strings), so swapping them still runs.
- Adding one more option means changing every caller.
- Several parameters always travel together (street, city, zip, country).
The fixes
- Introduce a Parameter Object. Group values that belong together (an
Address, aContact) into one object. - Preserve Whole Object. If the function takes three fields of an object, pass the object.
- Make options keyword-only with defaults. A
*in the signature forces callers to writenewsletter=True, which documents itself.
from dataclasses import dataclass, field
# ---- BEFORE: nine positional parameters, easy to mix up ----
def create_user_before(name, email, age, street, city, zip_code, country, newsletter, active):
return {"name": name, "email": email, "age": age, "address": f"{street}, {city} {zip_code}, {country}", "newsletter": newsletter, "active": active}
# ---- AFTER: related values grouped, options keyword-only with defaults ----
@dataclass(frozen=True)
class Address:
street: str
city: str
zip_code: str
country: str
def __str__(self): return f"{self.street}, {self.city} {self.zip_code}, {self.country}"
@dataclass(frozen=True)
class Contact:
name: str
email: str
age: int
def create_user(contact: Contact, address: Address, *, newsletter=False, active=True):
return {"name": contact.name, "email": contact.email, "age": contact.age, "address": str(address), "newsletter": newsletter, "active": active}
The new create_user takes two objects and two named options. A call reads like a sentence, the address validates in one place, and new options can be added with a default without breaking old callers.
Prove the result is the same, and see the old trap
old = create_user_before("Amina", "a@example.com", 24, "12 Road", "Dhaka", "1207", "BD", True, True)
new = create_user(Contact("Amina", "a@example.com", 24), Address("12 Road", "Dhaka", "1207", "BD"), newsletter=True)
print(new)
assert old == new
# The old call, with the last two flags swapped by mistake, still runs and is silently wrong:
print(create_user_before("Amina", "a@example.com", 24, "12 Road", "Dhaka", "1207", "BD", False, True)["newsletter"])
try: create_user(Contact("A", "a@x.com", 1), Address("s", "c", "z", "k"), True)
except TypeError as e: print("error:", e)
{'name': 'Amina', 'email': 'a@example.com', 'age': 24, 'address': '12 Road, Dhaka 1207, BD', 'newsletter': True, 'active': True}
False
error: create_user() takes 2 positional arguments but 3 were given
The new call gives exactly the same dictionary as the old one. The old function with newsletter and active swapped by mistake still runs and gives a wrong answer (False for the newsletter flag) because both are booleans. The new version refuses a positional True, since options must be named, so the mistake becomes an immediate TypeError.
How to refactor safely
- Write a test for the current function.
- Create the parameter objects and a new function that takes them, then make the old function call the new one.
- Move callers over one at a time and remove the old function.
- Use
frozen=Truedataclasses so the objects cannot change while in use. - Put the
*before options so they must be named.
When to leave it alone
- Two or three clear parameters are fine.
- Do not bundle unrelated values into a bag called
ParamsorOptionsjust to cut the count. A group needs a real meaning. - Public library functions with many options may use keyword-only arguments instead of one object.
Common mistakes
- A "god" parameter object with every possible field.
- Using a dictionary instead of a typed object, so typos in keys fail late.
- Mutable parameter objects shared between calls.
- Keeping positional booleans, which hide meaning in the call.
Main idea: group values that belong together into an object, pass whole objects instead of their pieces, and make options keyword-only. Calls become readable and mix-ups turn into errors.
Code smells and refactoring series, Bloaters: 4 of 5. Previous: Primitive Obsession: replace bare strings and floats with value objects.


