You don't need the isOk()/isErr() methods if you use discriminating readonly properties instead
Author: calculuswhizCreated Aug 5, 2026Updated Aug 5, 2026
Instead of:
interface IResult<T, E> {
isOk(): this is Ok<T, E>
isErr(): this is Err<T, E>
...
}
export class Ok<T, E> implements IResult<T, E> {
constructor(readonly value: T) {}
isOk(): this is Ok<T, E> {
return true
}
isErr(): this is Err<T, E> {
return !this.isOk()
}
...
}
export class Err<T, E> implements IResult<T, E> {
constructor(readonly error: E) {}
isOk(): this is Ok<T, E> {
return false
}
isErr(): this is Err<T, E> {
return !this.isOk()
}
...
}If you discriminate them with readonly properties:
// Keep IResult, minus these two properties, and define a new type for them:
export type ResultProps<TOk extends boolean> = {
readonly isOk: TOk;
readonly isErr: TOk extends true ? false : true;
};
export class Ok<T, E> implements IResult<T, E>, ResultProps<true> {
readonly isOk = true;
readonly isErr = false;
...
}
export class Err<T, E> implements IResult<T, E>, ResultProps<false> {
readonly isOk = false;
readonly isErr = true;
...
}Then instead of calling isOk()/isErr() a method, you can just use a property to discriminate the type:
const result: Result<A, B> = basicResult();
if (result.isOk) {
// result is Ok<A> here
} else {
// result is Err<B> here
}Since Result is a union of those two types, TypeScript can figure out the type from this.
Source: supermacro/neverthrow