Skip to content

Remove Flag Argument

Take a function that uses a boolean argument to choose between two distinct behaviours, and replace it with two separate functions — one per behaviour — each with a name that says what it does. The caller stops passing a cryptic true and starts calling a function whose name is the answer.

A flag argument hides intent at the call site. When you read sendEmail(order, true), the true tells you nothing — you have to open the function to learn that true means “send urgently” while false means “queue for later”. The boolean is doing the job a function name should do. It also tends to grow a forked body: an if (urgent) ... else ... that mashes two unrelated flows into one function, so neither reads cleanly and both must be tested through the same door.

A notification function whose boolean picks between an urgent and a normal path. After, two named functions, each focused.

// Before
function notify(message: string, urgent: boolean): void {
if (urgent) {
console.log(`[URGENT] ${message}`);
} else {
console.log(`[info] ${message}`);
}
}
notify("Server down", true);
notify("Backup finished", false);
// After
function notifyUrgent(message: string): void {
console.log(`[URGENT] ${message}`);
}
function notifyInfo(message: string): void {
console.log(`[info] ${message}`);
}
notifyUrgent("Server down");
notifyInfo("Backup finished");
  1. Confirm the flag truly selects between behaviours, not just a value. If it only toggles a number or a label, Parameterize Function may fit better than splitting.
  2. Create a new function for one branch of the flag. Give it a name that captures that branch’s intent, like notifyUrgent.
  3. Copy the relevant branch’s logic into the new function and drop the dead branch. Run your tests.
  4. Find every caller that passed the flag with that value and redirect it to the new function, removing the boolean argument.
  5. Repeat steps 2 through 4 for the other branch.
  6. Run your tests after each redirection so a failure points at one caller.
  7. When no caller passes the flag any more, delete the original function.

Split out a flag argument whenever a boolean parameter changes what the function does, whenever a literal true or false at a call site is unreadable, or whenever the body has forked into two flows that share little. Two named functions make each call self-explanatory and let each path be tested directly.

The cost is more functions in the namespace, and some genuinely shared setup may now live in two places — extract that shared part into a private helper so you do not duplicate it. Be pragmatic, too: a flag that merely flips configuration data, with no behavioural fork, is fine to keep. The smell is specifically a boolean that decides between behaviours.

Why does a flag argument hide intent at the call site?
When is splitting into two functions the right move rather than parameterizing?
What should you do with setup logic shared by both branches after the split?
After redirecting all callers away from the flag, what is the final step?