Typescript Union Merging

2 min readOct 19, 2020

Typescript has a concept called ‘Declaration Merging’. The idea is that some kinds of declarations can be specified multiple times, and the ‘final’ or ‘actual’ value is the result of those combined declarations:

interface A { name: string }
interface A { age: number }

Here, we declare using declaration merging to define an interface that is the same as:

interface A { name: string; age: number }

This is really useful for writing code along with contextual, but global type information. Consider this example of defining Action types in the common reducer pattern of event handling / dispatch:

enum ActionKind { }enum ActionKind { AddFile = 0 }
interface ActionAddFile {
type: ActionKind.AddFile
file: File
enum ActionKind { DeleteFile = 1 }
interface ActionDeleteFile {
type: ActionKind.DeleteFile
file: File

We’ve nicely defined two action types: one, AddFile adds a file, while the other DeleteFile deletes a file. These can be uniquely identified by their ActionKind.

Writing the actions this way avoids having to have a big enum ActionKind{ AddFile, DeleteFile, Something, SomethingElse } which would have to be maintained separately to the actions themselves.

Almost always, however, we also want an Action type that contains a union, to be discriminated on Action.type , i.e:

type Action = ActionAddFile | ActionDeleteFile

This tends to become unwieldy after a while, as every time you add an Action interface, you must also add it to the union, and that’s easy to forget.

You can’t merge type aliases (what this construct is called) the same way you can interfaces or enum s, attempting to do so will make typescript complain that you’ve declared the same alias identifier twice:

type Action = ActionAddFile
type Action = ActionDeleteFile

And looking at it, well — it doesn’t look right anyway. Since aliases are not ‘true’ types in that the type system considers them to be equivalent to their right hand side, there’s not really anything to sanely merge.

However! I’ve started to use a pattern that uses interface merging to achieve the same thing.

In this form, we define an interface _Action whose members will be merged to get all the actions as we…