Back to Blog
code-quality

The Base-Variant-Union Pattern

A structural pattern for managing complex conditionals and varying modes in your code. Learn how to turn messy conditional logic into clean, type-safe abstractions.

27 Jan 2025
6 min read
Share:
The Base-Variant-Union Pattern

Have you ever found yourself drowning in a sea of conditional statements? You know the feeling—your component or function starts simple, but as requirements evolve, it becomes a tangled mess of if statements and type assertions. 😵‍💫

I've been there. Whether it's handling different user roles, supporting multiple brands, or creating flexible data types, complex conditionals can quickly urn into spaghetti 🍝. But there's a pattern I've been using that's changed how I approach these problems: the Base-Variant-Union pattern. 🎯

The Problem

Picture this: You're building a feature that needs to behave differently based on some "mode" or condition. Maybe it's:

  • A UI component that changes based on the current brand or theme
  • A data type that has different shapes depending on its state
  • A function with behavior that varies based on user permissions

You start with a few if statements. Then more requirements come in. Soon, your code looks like a choose-your-own-adventure book, and you're not even sure which paths are still relevant. 📚🤷‍♂️

The Solution: Base-Variant-Union

The Base-Variant-Union (BVU) pattern offers a structured approach to handling these scenarios. Here's how it works:

  1. Base: Create a foundation that encapsulates all possible variations, with parameters for everything that changes
  2. Variants: Build specific implementations for each mode or archetype
  3. Union: Combine the variants into a single interface that chooses the right one based on conditions

Think of it as creating a family of related implementations that share a common structure but express different behaviors. It's like the structural cousin of Martin Fowler's "refactor conditional to polymorphism"—but using composition and discriminated unions instead of inheritance. 🧬

Example 1: Building a Better Option Type

Let's start with something familiar to TypeScript developers. You might have written an option type like this:

type Option<T> = {
  kind: 'some' | 'none';
  value?: T;
}

This works, but it's not type-safe. When kind is 'none', value could still exist (just undefined). Let's apply the BVU pattern to fix this.

First, our base type captures the full structure:

type OptionBase<T> = {
  kind: 'some' | 'none';
  value?: T;
}

Next, we create variants for each specific case:

type Some<T> = {
  kind: 'some';
  value: T;
}

type None<T> = {
  kind: 'none';
  value: undefined;
}

Finally, the union brings them together:

type Option<T> = Some<T> | None<T>;

Now TypeScript understands our intent perfectly:

function add1(val: Option<number>): number {
  if (val.kind === 'some') {
    // TypeScript knows val.value is a number here!
    return val.value + 1;
  }
  return 1;
}

No type assertions needed. No possibility of accessing value when it doesn't exist. Beautiful. ✨

Example 2: Taming Multi-Brand React Components

At Blazesoft, we build gaming platforms that need to adapt to different brands and regulatory requirements. Each brand might need different legal links, disclaimers, or UI elements. Without a good pattern, this quickly becomes a nightmare of conditionals.

Here's what our footer component used to look like, albeit in demonstration-form:

interface AppFooterProps {
  linkStructure: LinkGroup[];
}

const AppFooter = ({ linkStructure }: AppFooterProps) => {
  return (
    <footer className="app-footer">
      <div className="app-footer__fixed-width-section">
        {/* render linkStructure */}
      </div>
      <div className="app-footer__full-width">
        &copy; 2025 {config.brandCompanyName}
        {config.brand === BRAND_ABC && <LegalIconLink1 />}
        <LegalIconLink2 />
        <LegalIconLink3 />
        {config.brand === BRAND_DEF && <LegalIconLink4 />}
      </div>
    </footer>
  );
};

As we added more brands, this became increasingly unwieldy. Time for BVU! 💪

First, we create a base component that accepts all varying elements as props:

interface AppFooterBaseProps extends AppFooterProps {
  legalIconLinks: ReactNode[];
}

const AppFooterBase = ({ linkStructure, legalIconLinks }: AppFooterBaseProps) => {
  return (
    <footer className="app-footer">
      <div className="app-footer__fixed-width-section">
        {/* render linkStructure */}
      </div>
      <div className="app-footer__full-width">
        &copy; 2025 {config.brandCompanyName}
        {legalIconLinks}
      </div>
    </footer>
  );
};

Then we create variant components for each brand:

const AppFooterAbc = (props: AppFooterProps) => {
  return (
    <AppFooterBase
      {...props}
      legalIconLinks={[
        <LegalIconLink1 key="1" />,
        <LegalIconLink2 key="2" />,
        <LegalIconLink3 key="3" />,
      ]}
    />
  );
};

const AppFooterDef = (props: AppFooterProps) => {
  return (
    <AppFooterBase
      {...props}
      legalIconLinks={[
        <LegalIconLink2 key="2" />,
        <LegalIconLink3 key="3" />,
        <LegalIconLink4 key="4" />,
      ]}
    />
  );
};

const AppFooterGeneric = (props: AppFooterProps) => {
  return (
    <AppFooterBase
      {...props}
      legalIconLinks={[
        <LegalIconLink2 key="2" />,
        <LegalIconLink3 key="3" />,
      ]}
    />
  );
};

Finally, our union component selects the right variant:

const AppFooter = (props: AppFooterProps) => {
  switch (config.brand) {
    case BRAND_ABC:
      return <AppFooterAbc {...props} />;
    case BRAND_DEF:
      return <AppFooterDef {...props} />;
    default:
      return <AppFooterGeneric {...props} />;
  }
};

The benefits are immediate:

  • Each brand's requirements are clearly isolated
  • Adding a new brand is straightforward—just create a new variant
  • The base component ensures consistency across brands
  • Testing becomes easier—you can test each variant in isolation

When to Use BVU

The Base-Variant-Union pattern shines when you have:

  • Multiple "modes" or archetypes of the same concept
  • Complex conditionals that are growing out of control
  • A need for type safety across different variations
  • Requirements that change frequently by category

It's particularly powerful in:

  • Multi-tenant applications where behavior varies by client
  • Feature flags that significantly alter functionality
  • Data structures with state-dependent shapes
  • UI components with theme or brand variations

The Pattern in Practice

Here's the recipe:

  1. Identify what varies: Look for the conditionals in your code. What changes? What stays the same?

  2. Extract the base: Create a structure that accepts all variations as parameters. This becomes your single source of truth for the shape.

  3. Create variants: Build specific implementations for each condition. Keep them focused and single-purpose.

  4. Unite with a discriminator: Use a switch statement, pattern matching, or conditional logic to select the right variant.

The beauty is that this pattern scales. Start with two variants, add ten more—the structure remains clean and maintainable.

A Word of Caution

Like any pattern, BVU isn't a silver bullet. Don't use it for:

  • Simple boolean conditions (just use an if statement)
  • Variations that share no common structure
  • Cases where inheritance or simple composition would be clearer

The goal is to make your code more maintainable, not to add unnecessary abstraction layers. 🎯

Conclusion

The Base-Variant-Union pattern has become one of my go-to tools for managing complexity. It takes the mess of conditional logic and transforms it into a clear, type-safe structure that's easy to understand and modify.

Next time you find yourself writing yet another if statement in that already-complex function, consider whether BVU might help. Start small—refactor one feature, one component, one type. See how it feels.

The best patterns are the ones that make your code easier to work with, not harder. BVU does exactly that by turning conditional chaos into structured clarity. Your future self (and your teammates) will thank you. 🙏

Have you used similar patterns in your code? How do you handle complex conditionals? I'd love to hear your thoughts—drop me an email at tzvi@tzvipm.dev. Happy coding! 🚀

Tzvi Melamed

Tzvi Melamed

Software Engineer & Writer

Sharing insights and experiences from the world of software development.

Learn more about me →

Yoo-hoo

I'm always looking for interesting stories, different perspectives, and opportunities to learn from fellow developers. If you like what you read, reach out!

Contact me →

Enjoyed this article?

Follow me on social media for more insights like this. Newsletter coming soon!