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:

typescript
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:

typescript
// 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:

typescript
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.