Execute Program

Advanced TypeScript: Function Return Type Compatibility

Welcome to the Function Return Type Compatibility lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • Generally, narrower types are assignable to wider types. For example, we can assign the literal string type 'lastLoginDate' to a string variable.

  • However, as we saw in an earlier lesson, this rule is reversed for function parameter types. Fortunately, functions' return types don't have that problem; they use the normal TypeScript type compatibility rule. For example, () => 'lastLoginDate' is assignable to () => string.

  • >
    function returnsLiteralString(): 'lastLoginDate' {
    return 'lastLoginDate';
    }

    type ReturnsString = () => string;

    const testFunction: ReturnsString = returnsLiteralString;
    testFunction();
    Result:
    'lastLoginDate'Pass Icon
  • Now let's flip the types around. In the next example, we define a function that returns string, then try to assign it to a function type that returns 'lastLoginDate'. That's a type error.

  • >
    function returnsString(): string {
    return 'lastLoginDate';
    }

    type ReturnsLiteralString = () => 'lastLoginDate';

    const testFunction: ReturnsLiteralString = returnsString;
    testFunction();
    Result:
    type error: Type '() => string' is not assignable to type 'ReturnsLiteralString'.
      Type 'string' is not assignable to type '"lastLoginDate"'.Pass Icon
  • Reasoning about this is much easier than reasoning about parameter types. If a function returns a 'lastLoginDate', then it's perfectly safe to use it as a function that returns string. The literal string 'lastLoginDate' is definitely a string!

  • But if a function returns a string, then it's not safe to treat that value as a 'lastLoginDate'. For example, we might say const s: 'lastLoginDate' = testFunction(). If the testFunction returns string, that string might be 'Amir' or 'some other string'. None of those are compatible with the variable's 'lastLoginDate' type! The type error prevents that mistake.

  • The same reasoning applies to other return types, like tuples vs. arrays.

  • >
    function returnsTuple(): [boolean, boolean, boolean] {
    return [true, false, true];
    }

    type ReturnsArray = () => Array<boolean>;

    const testFunction: ReturnsArray = returnsTuple;
    testFunction();
    Result:
    [true, false, true]Pass Icon
  • >
    function returnsArray(): Array<boolean> {
    return [true, false, true];
    }

    type ReturnsTuple = () => [boolean, boolean, boolean];

    const testFunction: ReturnsTuple = returnsArray;
    testFunction();
    Result:
    type error: Type '() => boolean[]' is not assignable to type 'ReturnsTuple'.
      Type 'boolean[]' is not assignable to type '[boolean, boolean, boolean]'.
        Target requires 3 element(s) but source may have fewer.Pass Icon
  • Finally, we need another brief note about terminology. The "normal" rule for assignability, where narrow types are assignable to wider types, is called "covariance". The types vary ("-variance") together ("co-").

  • That's what happens with 'lastLoginDate' vs. string and with [boolean, boolean, boolean] vs. Array<boolean>. It's also what we saw in this lesson: function types are "covariant on the return type". That is, they follow the normal TypeScript compatibility rule.

  • In a previous lesson, we saw that type compatibility is "reversed" for function parameter types. That's called "contravariance". The types vary ("-variance") in the opposite of the normal way ("contra-").

  • In most powerful static languages, the full description of function types is: "function types are covariant on return type and contravariant on parameter type". That short sentence hides a lot of complexity and subtlety, but you may see it show up in real-world discussions.

  • As usual, it's not important to fixate on the technical terminology, and there's no reason to feel bad about forgetting it. You can wipe "covariance" and "contravariance" from your memory right now, and still be an excellent TypeScript programmer. An easier way to remember it is: function types work like other types, except that assignability is reversed for the parameter types.