Conditional types in TypeScript

To avoid creating multiple interfaces which only get used to create a third, you can also alternate directly, with a type instead:

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};

One way to model this kind of logic is to use a union type, something like this

interface Valid {
  isValid: true
}

interface Invalid {
  isValid: false
  errorText: string
}

type ValidationResult = Valid | Invalid

const validate = (n: number): ValidationResult => {
  return n === 4 ? { isValid: true } : { isValid: false, errorText: "num is not 4" }
}

The compiler is then able to narrow the type down based on the boolean flag

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}