Typescript perfect sync - 3 tricks to keep it tight
Mini essays,  Code

Typescript perfect sync – 3 tricks to keep it tight

Typescript perfect sync can be kept using couple of strategies. Most of us start with simple union types, like this:

const ScenarioStatus = 'success' | 'fail' | 'error' | 'other';

So far so good. That works great… until you need to iterate over those values for rendering, validation, or other logic. And now You are stuck with two separate definitions: one array for logic, and one string union for the type. Sooner or later, they’ll fall out of sync and provide headaches to people. You could go for en enum….

Below behold the three TypeScript tricks to make your code tight, hard to break, cleaner, safer, DRYer and resistant to any unwanted mutations.

Trick 1: Use an Array and typeof for a Single Source of Truth

Instead of union type and value list kept separately, define the array only once and let TypeScript build the type automatically:

const scenarioTypes = ['success', 'fail', 'error', 'other'] as const;
type ScenarioStatus = typeof scenarioTypes[number];

Now ScenarioStatus becomes 'success' | 'fail' | 'error' | 'other' automagically !

Why it’s great:

  • You have one source of truth for both – the values and the type.
  • You can iterate over the array in templates, functions or stores without duplicating data.
  • Your type and array are in Typescript perfect sync.

Trick 2: Don’t Forget as const

When using

const scenarioTypes = ['success', 'fail', 'error', 'other'] as const;

Without as const, TypeScript treats your array values as plain strings:

textconst scenarioTypes = ['success', 'fail', 'error', 'other'];
type ScenarioStatus = typeof scenarioTypes[number];
// ❌ ScenarioStatus is just string

By adding as const, you lock in the literal values:

textconst scenarioTypes = ['success', 'fail', 'error', 'other'] as const;
type ScenarioStatus = typeof scenarioTypes[number];
// ✅ ScenarioStatus is 'success' | 'fail' | 'error' | 'other'

This ensures strong typing, autocomplete support, prevents the compiler from using string as type.

Trick 3: Understand the Magic of [number]

It is kinda confusing, kinda doozy.

type ScenarioStatus = (typeof scenarioTypes)[number];

Here’s what’s happening:

  • typeof scenarioTypes gives the array type:
    readonly ['success', 'fail', 'error', 'other']
  • [number] extracts a union of all possible items in that tuple or readonly array.

Then Typescript perfect sync uses it as „Give me any type that is defined in the array”.

So now we have 'success' | 'fail' | 'error' | 'other' as hard coupled types !

When to Use Each Pattern

Use CaseBest Practice
Only need type validation (no iteration)Simple union types without need for iteration
Need both the type and the list for iteration or validation, most of the cases in the long run 🙂 Array + as const + typeof pattern

That is one of my favorite TypeScript best practice for TYpescript perfect sync. It`s hard to break anything if You know what will break and other things will support it. Keeps You safe, and sane !

What’s Next?

Want to dig deeper into advanced TypeScript typing patterns? I must say it is starting to be my favorite language. God knows how dissapointed i am with scala… or maybes it is just the work…

TypeScript Handbook: Advanced Types

Piotr Kowalski