# null, but also not null
As a followup to [[technical/a different way to think about typescript|a different way to think about TypeScript]], I wanted to share a TypeScript trick
I've been enjoying (a little too much?) lately.
```tsx
null!;
```
> Note, ! is the [non null assertion operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator)
Which can be read as: the value null, asserted as not being null. Which sounds like nonsense; why wouldn't this produce an error?
Well, here's something even weirder, this compiles even under strict tsc checks
```tsx
type Foo = {
bar: {
baz: number;
};
a: string;
b: number;
c: boolean;
};
const foo: Foo = null!;
```
but actually, this works for any type...
```tsx
const a: string = null!
const b: number = null!
const c: boolean = null!
```
My first reaction when seeing this was it made no sense, and was an escape hatch built into the compiler. But, after thinking about
it for a little longer using the mental model described [in the article](/blog/a-different-way-to-think-about-typescript), it makes sense.
If we were to infer a type from `null!`, what would it be? Another way to ask that is: what type constructs a set that contains the value `null!`.
Well, no value exists that is both null, and not null. So if nothing exists in this set, let's just say it constructs an empty set.
If types are modeled as the sets they construct, which sets contain the empty set? More formally, for which sets does the condition hold that
every item in the empty set is in the set we're checking against.
Well, that's every set, right? If the empty set has nothing, then every set has _at least_ nothing.
![[assets/null-but-not-null/empty-set.png|600]]
That should give a good intuition for why `null!` is assignable to every type in typescript
> Note, the "empty set" type/ the type null! gets inferred as is called `never`. Which you may see frequently as the error branch of
> conditional types.
>
> You may think that this can be problematic because `never` can be assigned to anything. But, the converse
> is not true. Nothing can be assigned to never (other than the empty set itself).
>
> If we tried to assign a value of type number to a value of type never, we would have to check if every value in the set number constructs exists
> in the empty set, which of course, is not the case.
![[assets/null-but-not-null/converse.png|600]]
> Note, since the type of null! is never, you can accomplish the same behavior with asserting any value is never, e.g. `"some string" as never`
## Practical use case
Now this is a dangerous assertion, as the following code blows compiles... and blows up at runtime:
```tsx
type A = { b: number };
const a: A = null!;
a.b.toString();
```
There are 3 cases I find this to be useful:
- placeholder for a future value
- autocomplete debugging (which kinda falls under the previous point)
- `takesComplexArgs(null!) // now I can immediately see the properties on the returned value, without having to pass a real value`
- a hacky way to early return when encountering an [invariant](https://softwareengineering.stackexchange.com/questions/32727/what-are-invariants-how-can-they-be-used-and-have-you-ever-used-it-in-your-pro) error, instead of throwing an error
> Note, in all cases it would not be reasonable to actually ship `null!` to production. It's only intended to be a quick
> way to get around the compiler in some cases
To explain the latter, imagine the following case:
I have a function that accepts an argument, which is an array of reverse sorted ints, the best type I can give it is `Array<number>`.
Should the function operate under the assumption it's working with a `Array<number>` or a reverse sorted array of ints?
If it's a specialized algorithm for operating on a reverse sorted array of ints, assuming its as wide as an array of numbers is out of scope for it (what would it do if it encountered an ascending array of floats?).
Now the function executes with some conditions the compiler does not know about.
In this function, there may be a branch that the compiler has an overly wide type because it can't confirm the condition we only know about.
You may throw an error to avoid wasting computation processing that impossible case.
```tsx
const someComplexFunction = (...args) => {
// ...complex logic
if (!invariant(datastructure)){
throw new Error("This should never happen")
}
}
```
But imagine the following:
The impossible case happens, and the function (maybe the whole program) immediately stops executing because of the error
But for whatever reason (hunting down a bug maybe), you want to very quickly see what happens if the function executes longer.
Perhaps the supposed impossible case only gets hit every 1/1000 cases, and you don't care about the failed case for now.
You're trying to debug something else, and it's getting in the way. You could instead early return, but now the type signature is different.
```tsx
const someComplexFunction = (...args) => {
// ...complex logic
if (!invariant(datastructure)){
console.error("This should never happen")
return
}
}
```
> returns `T | undefined`, where T is the prior return value type.
But, we can use the never type to avoid this type error:
```tsx
const someComplexFunction = (...args) => {
// ...complex logic
if (!invariant(datastructure)){
console.error("This should never happen")
return null!
}
}
```
Our function appears to return `T`, even though there's a branch we early return the value `null`
So now in the case we want our program to execute a little longer, it can, without us having to worry about
messing up the types of the consumers of the function.
But, returning something of type never is dangerous and can make your program extremely difficult to debug if you forget about it.
Use this sparingly, and as a hack to do something fast. Just like you may temporarily use `any` on a variable to do something quickly,
if that `any` is left in the codebase, it would suck.
The main takeaway of this article should be how never can act weird, less so if it's smart to return null! as a hack when debugging.