Software Development

Two features Typescript will never include

Let's see why this is the case

Two features Typescript will never include

TypeScript is an amazing tool, but no language is perfect. Some features never make it to TypeScript no matter how useful they might be.

Today we explore two of them: negated types and nominal/branded types. We’ll look at why they’re not supported and show you some clever workarounds.


Negated Types

Typescript is closely aligned with Set Theory from mathematics. We know this because Typescript supports set operations with types, such as Union, Intersection, Concatenation…

But there is no support for Type subtraction, or in other words Negated types.

For example, you can’t say something like string - "click". But there is a cool trick you can use as a workaround.

This trick works best in generic function arguments. Take a look at this handler function that accepts all strings except click:

type AnyStringExceptClick = string;

// Constrain the generic input type to 'string'
const addGlobalHandler = <T extends string>(
  // If the input is 'click' this is the same as `never`
  event: T extends "click" ? never : T
) => {
  // event logic
};

// The type becomes 'never', so this is not allowed
addGlobalHandler("click")

// This works because it's not "click"
addGlobalHandler("hover");

It’s not perfect, and you can see this workaround only works in specific contexts, but given that there is rarely a need for type subtraction, it does make sense why Typescript wouldn’t support it natively.


Nominal Types

TypeScript uses a structural type system. That means it only cares about the structure of data rather than their names. This is why nominal/branded types don’t exist in TypeScript.

But let's say you want to distinguish between absolute and relative file path strings. Treating them as interchangeable could lead to bugs later down the line.

Conveniently, you can simulate nominal types by creating “branded types”:

declare const _brand: unique symbol;

// Use this to "tag" strings with additional metadata in the type
type Brand<T, TBrand> = T & { [_brand]: TBrand };

// Creating two branded types with different tags
type AbsolutePath = Brand<string, "AbsolutePath">;
type RelativePath = Brand<string, "RelativePath">;

// This cast is important to distinguish the type names
// through the structure
const absPath = "/path/to/file" as AbsolutePath;
const relPath = "../../file" as RelativePath;

const acceptsAbsPath = (path: AbsolutePath) => null;

acceptsAbsPath(absPath); // ✅ Valid
acceptsAbsPath(relPath); // ❌ Error

This is a neat trick that works for all use cases. I would love to hear situations where you would use it in the comments.


Conclusion

TypeScript will never officially support negated types or nominal types, but their workarounds still promise good results.

Do you know other features Typescript is avoiding to add? Please share them in the comments below.


Need a cofounder for your SaaS project? Check out DevMarket today!
1