(01) - Engineeringstop-building-mappings-start-writing-rules

Stop Building Mappings, Start Writing Rules

How I stopped maintaining hundreds of lines of mapping objects and started writing functions that derive the output instead.

10 April 20265 min readEngineering

Stop Building Mappings, Start Writing Rules

You've probably seen code like this everywhere:

export const SECTION_MAPPING: Record<Section, string> = {
    community: "Community",
    engineering: "Engineering",
    customers: "Customers",
    projects: "Projects",
    "open-source": "Open Source"
};

At first glance, nothing wrong with it.

But in real codebases this ends up turning into hundreds of lines of hand-written mappings.

And the weird part is: most of them follow the same pattern.


The pattern hiding in plain sight

Look at the transformation:

  • projects -> Projects
  • open-source -> Open Source
  • user_profile -> User Profile

It's always the same idea:

  1. split words (- or _)
  2. capitalize each word

So why are we writing the output instead of the rule?


Step 1: stop storing results, compute them

Instead of maintaining a mapping:

SECTION_MAPPING["open-source"];

Just derive it:

export function formatLabel(input: string): string {
    return input
        .replace(/[-_]/g, " ").split(" ")
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join(" ");
}

Now the mapping disappears completely:

formatLabel("open-source"); // Open Source

No data file. No duplication. Just logic.


The thing that always breaks this simplicity

Real systems always introduce "special cases":

  • ai -> AI
  • sdk -> SDK
  • mcp -> MCP
  • waf -> WAF

And this is where people usually go back to object mappings:

const EXCEPTIONS: Record<string, string> = {
    ai: "AI",
    sdk: "SDK",
    mcp: "MCP",
    waf: "WAF"
};

But that's still just encoding a simple rule in a verbose way.


Step 2: reduce exceptions to a rule

These aren't really exceptions. They're just "always uppercase words".

So instead of mapping key -> value, just store intent:

const UPPERCASE_WORDS = ["ai", "sdk", "mcp", "waf"];

Now the function becomes:

export function formatLabel(input: string): string {
    return input.replace(/[-_]/g, " ").split(" ")
        .map(word => {
            const lower = word.toLowerCase();
            if (UPPERCASE_WORDS.includes(lower)) return lower.toUpperCase();
            return word.charAt(0).toUpperCase() + word.slice(1);
        }).join(" ");
};

What actually changed?

Not much in code size.

A lot in design.

Before:

  • you stored every output manually
  • mappings grew linearly with the product
  • every new label meant a new entry

After:

  • you encode one rule
  • plus a tiny set of overrides
  • everything else is derived

The real mental shift

Here's the thing I keep coming back to:

Most mapping objects aren't data. They're failed functions.

I mean that in the most practical sense possible. You already know how to transform open-source into Open Source. You've done it a thousand times. The only reason you're storing it in an object is because at some point someone decided "this is data, not logic."

But it doesn't have to be either/or.

Once this clicked for me, I started seeing it everywhere. The sidebar labels that don't match their routes. The permission names that get transformed into readable text. The feature flags that are just camelCase in, Title Case out.

The best part? You don't even need to refactor everything. Just next time you reach for a mapping object, ask yourself: what's the rule here?

Chances are, you already know it.