DEV Community

Cover image for The 3-Argument Rule: How to Stop "Argument Bankruptcy"
Doogal Simpson
Doogal Simpson

Posted on • Originally published at doogal.dev

The 3-Argument Rule: How to Stop "Argument Bankruptcy"

You are reading a file, trying to debug a critical issue. You stumble upon this line:

createUser("John", "Doe", "j@d.com", true, false, 5);

Your brain freezes.

What is true? What is false? What does the number 5 represent? Is that a retry count? A timeout? A user ID?

To understand this single line of code, you have to navigate to the function definition, memorize the order of six different parameters, and then mentally map them back to the function call.

You have just paid a heavy tax.

In "The Professional Junior," I talk about the concept of Cognitive Budget. As developers, we can only hold so much context in our heads at once. Every time we force a caller to memorize an argument order, we deplete that budget.

This is called Argument Bankruptcy. Here is how to fix it.

The Concept: Arguments are Taxes

Think of arguments as the taxes we pay to use a function.

  • 0 Arguments: Tax-free. The function just does its job.
  • 1-2 Arguments: A reasonable sales tax. We can easily remember getUser(id) or login(username, password).
  • 3+ Arguments: Bankruptcy.

When a function requires three or more arguments, we introduce two major risks:

  1. The "Mystery Boolean": Passing true or false without context makes the calling code unreadable.
  2. The Order Trap: Was it (email, name) or (name, email)? In loosely typed languages, getting this wrong leads to silent data corruption. In typed languages, it leads to frustration.

The Junior Trap: "Just One More Param"

We’ve all been there. You have a function that takes three arguments. A new requirement comes in: we need to track the user's last login date.

The easy path (The Junior Trap) is to just tack it onto the end.

function save(name, email, isAdmin, lastLogin) { ... }

Six months later, another requirement comes in. Now we need a retry count. Tack it on the end.

Suddenly, you are playing "Guess the Argument" every time you call the function.

The Code: The Object Pattern

The solution is to stop passing loose data. If logic requires 3+ things, those things belong together. We wrap them in a single object (or Interface).

// Before: The Bankruptcy

Here, the caller is forced to memorize the specific order of five different variables. If they swap the booleans, an admin might get downgraded to a standard user silently.

// ❌ The Function Definition
function saveUser(
  firstName, 
  lastName, 
  email, 
  isAdmin, 
  sendWelcomeEmail
) {
  // ... implementation
}

// ❌ The Usage (The Horror)
// What does 'true' mean? What does 'false' mean?
// If I swap them, I break the system.
saveUser("John", "Doe", "j@d.com", true, false); 
Enter fullscreen mode Exit fullscreen mode

// After: The Virtue of Economy

By using a single object argument (often called "named parameters" or the "RObject pattern"), we eliminate the tax.

  1. Order doesn't matter.
  2. Self-Documenting: We know exactly what isAdmin is because the key says so.
  3. Extensible: We can add a new property to the interface later without breaking every single function call in the codebase.
// ✅ The Function Definition
// We expect ONE object.
function saveUser(userContext) {
  // We can destructure immediately for clarity
  const { firstName, email, isAdmin } = userContext;
  // ... implementation
}

// ✅ The Usage (Clear & Robust)
saveUser({
  firstName: "John",
  lastName: "Doe",
  email: "j@d.com",
  isAdmin: true,
  sendWelcomeEmail: false
});
Enter fullscreen mode Exit fullscreen mode

The Pro Move: Extensibility

The greatest benefit of the After approach is how it handles change.

If you need to add a middleName parameter to the Before example, you have to find every instance of saveUser in your application and update the arguments, or carefully place it at the very end and hope no one messed up the order.

In the After example, you add middleName to the function logic. Old calls that don't pass middleName simply treat it as undefined (or you provide a default), and the code keeps running.

Stop making your teammates memorize your implementation details. Pay the tax for them.


Stop writing code just to please the compiler.

This article was an excerpt from my handbook, "The Professional Junior: Writing Code that Matters."

It’s not a 400-page textbook. It’s a tactical field guide to unwritten engineering rules.

👉 Get the Full Handbook Here

Top comments (0)