Function Length and Parameter Count
How long should a function be? How many parameters should it take?
The unsatisfying answer: "It depends." But I can give you useful guidelines.
Function Length
The Research
Studies in Code Complete found that routines up to 200 lines had the fewest defects per line of code. But "up to 200 lines" doesn't mean "200 lines is good."
The same research found that the sweet spot for comprehension is much shorter. Humans can hold about 7 ± 2 things in working memory. A 200-line function overwhelms that capacity.
Practical Guidelines
| Length | Assessment |
|---|---|
| 1-5 lines | Often ideal. Functions this short are easy to understand at a glance. |
| 5-15 lines | The sweet spot. Complex enough to be worth extracting, short enough to comprehend. |
| 15-30 lines | Review carefully. Can it be split? Is every line necessary? |
| 30-50 lines | Yellow flag. Almost certainly doing too much. |
| 50+ lines | Red flag. Split it. There are rare exceptions (state machines, parsers), but they're rare. |
When Long Functions Are Acceptable
Some patterns genuinely require length:
- State machines with many cases
- Factory functions with many construction paths
- Parsers handling many token types
- Event handlers responding to many event types
Even then, look for extraction opportunities. A 100-line switch statement can often become a lookup table and 10 small functions.
The Ideal Function
The ideal function:
- Fits on one screen without scrolling
- Can be understood without scrolling
- Has a name that describes what it does
- Doesn't surprise you with hidden complexity
Uncle Bob Martin advocates for functions as short as 4-5 lines. That's aspirational—many codebases won't achieve it. But pushing toward shorter is generally right.
Parameter Count
The Zero Parameter Ideal
The best functions take no arguments:
function getCurrentUser() { }
function generateId() { }
function now() { }
Zero parameters means:
- No input combinations to test
- No confusion about what to pass
- Maximum simplicity
One Parameter (Unary)
Single-parameter functions are common and clear:
function parseJson(input) { }
function validateEmail(email) { }
function fileExists(path) { }
The parameter tells you what the function operates on.
Two Parameters (Binary)
Two parameters are manageable but start to require thought:
function copyFile(source, destination) { }
function assertEquals(expected, actual) { }
function pow(base, exponent) { }
With two parameters, order matters. assertEquals(expected, actual) is convention; swap them and you confuse everyone.
Three Parameters (Ternary)
Three parameters are harder to understand and remember:
function createRange(start, end, step) { }
function makeRgb(red, green, blue) { }
function between(value, min, max) { }
At three parameters, consider whether you should use an object.
More Than Three: Use an Object
// Hard to read and easy to get wrong
createUser("Alice", "alice@example.com", "admin", true, false, 30);
// What do those booleans mean? What's the 30?
Better:
createUser({
name: "Alice",
email: "alice@example.com",
role: "admin",
isActive: true,
requiresPasswordChange: false,
sessionTimeoutMinutes: 30
});
Now each value has a name. Order doesn't matter. Readers understand immediately.
The Boolean Flag Anti-Pattern
Boolean parameters are code smells:
renderPage(data, true);
What does true mean? You have to read the function signature to know.
This is called a "flag argument." It means the function does different things based on a boolean, which usually means it does two things and should be two functions.
Before: Boolean Flag
function fetchUser(id, includeProfile) {
const user = db.users.find(id);
if (includeProfile) {
user.profile = db.profiles.find(id);
}
return user;
}
// Caller
const user = fetchUser(123, true); // What does true mean?
After: Separate Functions
function fetchUser(id) {
return db.users.find(id);
}
function fetchUserWithProfile(id) {
const user = fetchUser(id);
user.profile = db.profiles.find(id);
return user;
}
// Caller
const user = fetchUserWithProfile(123); // Clear!
When Boolean Parameters Are Okay
Sometimes a boolean is genuinely the right choice:
setVisible(true);
setEnabled(false);
These read naturally. The parameter name is clear from context.
But even then, consider:
show();
hide();
enable();
disable();
These are even clearer.
Output Parameters Are Evil
Some functions modify their parameters:
function addToList(list, item) {
list.push(item);
}
const myList = [];
addToList(myList, "item"); // myList is modified!
This is confusing. The modification is a side effect hidden in the call.
Better:
function withItem(list, item) {
return [...list, item];
}
const myList = [];
const newList = withItem(myList, "item"); // myList unchanged, newList has item
Or if mutation is necessary, make it obvious:
myList.push("item"); // Clearly mutating myList
The Parameter Object Pattern
When you have related parameters, group them:
Before
function drawRectangle(x, y, width, height, color, borderWidth, borderColor) {
// ...
}
After
function drawRectangle(rect, style) {
// ...
}
drawRectangle(
{ x: 10, y: 20, width: 100, height: 50 },
{ fill: 'blue', borderWidth: 2, borderColor: 'black' }
);
Benefits:
- Parameters have names
- Order doesn't matter
- Easy to add optional parameters
- Related data is grouped
Key insight: Short functions (5-15 lines) are easier to understand, test, and maintain. Fewer parameters (0-3) reduce cognitive load. Boolean flags are usually a sign the function does two things. When you have many parameters, use an object.