This post will be shorter than most, mostly because writing this has been on my todo list since I released the newtype
type 11 months ago, and since it came up in the TypeScript discord yesterday.
If you're unfamiliar with branded types in TypeScript, this article is not meant to be an introduction to them -- I recommend you read Josh Goldberg's chapter on Branded Types, and then come back.
tl;dr, about a year ago I released a type called newtype
, that lets you define types like this:
import type { newtype } from "any-ts"
declare namespace integer {
const URI: unique symbol
}
interface integer extends newtype<
number & { [integer.URI]: never }
> {}
declare const myInt: integer
const ex_01 = myInt + 2.3
// 🚫 TypeError ^^:
// Operator '+' cannot be applied to types
// 'integer' and 'number'
// Downcast `myInt` with the unary plus operator:
const ex_02 = +myInt + 2.3
// ^? const ex_02: number
Play with it in the TypeScript Playground
What's happening here?
The newtype
type is part of a library I maintain called any-ts
.
any-ts
is a TypeScript library, but different than the ones you usually see.
Rather than shipping a bunch of fancy TypeScript utility types, it ships a bunch of TypeScript primitives.
One of those primitives is newtype
. If you're familiar with Rust or Haskell, this version of newtype
is like a poor person's newtype
.
Despite that, it's much more powerful than it looks at first glance. But since this article isn't about promoting my library -- we'll skip them for now.
Suffice to say, one of the things newtype
lets you do is wrap up a primitive type (like a string
or number
) in a TypeScript interface.
By doing that, the name you give the type "sticks".
In other words, instead of this:
type Integer = number & { [IntegerSymbol]: never }
declare let myInt: Integer
// ^? let myInt: number { [IntegerSymbol]: never }
...which is harder to read and leaks implementation details, you get this:
interface integer extends newtype<number> {}
declare let myInt: integer
// ^? let myInt: integer
Notably, you can implement every "flavor" (pun intended) of branded type that Josh outlines in his chapter on Branded Types, in terms of the newtype pattern.
If you're curious and would like to learn more, I recommend:
- the TypeScript discord thread from yesterday (June 19, 2025)
- the release notes where I talk about some of the interesting possibilities that a type like
newtype
opens up
And of course if you have any questions for me, I'm totally open to chatting. I'm on GitHub, LinkedIn, and you can always reach my by email at ahrjarrett at gmail dot com.