Q11 of 38 · Test design

How do you test a function with several boolean inputs?

Test designMidboolean-inputscombinatorialproperty-basedmid

Short answer

Short answer: For ≤ 4 booleans (16 combinations), test all combinations. For 5+, use pairwise to cover every pair, augment with property-based tests for invariants, and add explicit cases for the combinations the business cares most about.

Detail

Boolean inputs are tempting to under-test because each looks simple. The trap is the combinatorial explosion: n booleans gives 2^n combinations, and bugs often hide in unexpected combinations.

The decision tree:

  • ≤ 3 booleans (≤ 8 combos) — test exhaustively. There's no excuse not to.
  • 4 booleans (16 combos) — still exhaustive in most contexts; a few seconds of test time.
  • 5–7 booleans (32–128 combos) — pairwise with an exhaustive subset for known-important combinations.
  • 8+ booleans (256+ combos) — pairwise as baseline + explicit cases + look for whether some inputs are actually independent (split into separate functions if so).

Two senior moves worth mentioning:

  1. Property-based testing complements combinatorial. With Hypothesis (Python) or fast-check (JS), you state invariants ("if is_admin is true, the result must be visible regardless of other flags") and the framework explores combinations automatically, including edge cases you didn't think to enumerate.

  2. Decision tables for categorical-and-boolean mixes. If alongside the booleans there's a 3-value enum (role: free | pro | enterprise), the table format scales cleaner than nested loops and reads better in code review.

The signal: you understand that boolean inputs scale combinatorially and have multiple tools for different scales, not "I'd test all combinations" or "I'd test the obvious ones."

// EXAMPLE

test_feature_visibility.py

import pytest
from itertools import product
from feature import is_feature_visible

# 4 booleans = 16 cases; cheap to enumerate.
@pytest.mark.parametrize(
    "is_admin,in_beta,has_flag_x,is_paid",
    list(product([True, False], repeat=4)),
)
def test_feature_visibility_is_correct(is_admin, in_beta, has_flag_x, is_paid):
    expected = (is_admin or has_flag_x) and is_paid
    assert is_feature_visible(is_admin, in_beta, has_flag_x, is_paid) == expected

# Property-based for an invariant — admin always sees, regardless of in_beta.
from hypothesis import given, strategies as st

@given(st.booleans(), st.booleans(), st.booleans())
def test_admin_always_sees_when_paid(in_beta, has_flag_x, is_paid):
    if is_paid:
        assert is_feature_visible(True, in_beta, has_flag_x, is_paid) is True

// WHAT INTERVIEWERS LOOK FOR

Awareness of the 2^n explosion, scaled approach (exhaustive at small n, pairwise at large n), bonus for property-based.

// COMMON PITFALL

'I'd test all combinations' without saying it works for small *n* but fails at 10 booleans.