Unions and Narrowing 28 exercises

Should You Provide Function Return Types?

Among TypeScript developers, there's an ongoing debate on whether or not you should provide function return types for your functions. Let's look at an argument for both sides.

Con: Function Return Types Can Be Wider Than What Is Returned

Here we have the function returnsStringOrNumber:

Loading explainer


00:00 Now that we understand a bit more about unions, about discriminated unions, I'd like to come back to a concept that I kind of wanted to touch on in the previous section, but we didn't quite have enough information to do it yet. This is about function return types in TypeScript. Now there's a big debate in the TypeScript community as to whether you should provide function return types

00:19 for your functions. And I'm gonna give you a one pro and one con on kind of like whether you should or shouldn't. A function return type is just a type that we give to the function here. So we have a function called return string or number, and we give a little colon after the parentheses here, and we say this return string or number.

00:38 And this kind of lands us on the first con here, which is function return types can actually be wider than what is returned. You notice here, return string or number, we're saying, sure, this function could return a string or it could return a number. Whereas in fact, inside, we're just returning a number. Okay, that's interesting.

00:58 This means then that when we go, okay, we grab a value out of this, this value could be string or number. Then we test if it's a string here. Technically, this value should be inferred as never inside here, because it's never gonna be a string based on this inference here, based on this actual like typing of our function or based on what the function actually does.

01:18 So if we remove the return type here, then boom, this actually starts working. And value inside here is now inferred as never because it's never going to be a string in this position. So in this scenario, this actually makes our function more robust, makes our application more robust, more,

01:36 I mean, we're never going to actually hit an error here because at the runtime level, this number is only ever going to be a, like it's only ever gonna be a number, right? So this condition is never going to return true, but this just means that actually having the return type here means we end up with a little bit more defensive code than we might need to.

01:56 We remove the return type. We can actually just delete this code because type of value is never going to be string. So that's the first con. Sure, the reason this doesn't throw an error actually inside the function definition is it's never going to cause a runtime error, but it might just lead you with a bit more defensive dead code than you were expecting. So that's a con.

02:15 Actually adding a return type can be actually less accurate than not adding a return type. But a pro here for function return type is it actually means that it can help enforce the type of the function, can actually like tell you when you're doing something stupid. So let's say that we have here a get permissions function

02:33 where we have a user role being passed in, and we have either admin, editor, or viewer being the things that we're passing in there. We do a switch on that role. And then for each role, we return a different type of things. So we return create, read, update, delete for admin, for editor, for viewer.

02:49 And we've added a string array return type to this function. So we're saying, okay, get permissions must always return string array. And if we hover over this error, it's saying function lacks ending return statements and return type does not include undefined. This function doesn't actually handle all of the cases for us.

03:08 We have here case viewer, which is commented out. And if I actually uncomment it, then the error goes away because we're now handling all three cases. If I add another thing here to use a role, so this might be anonymous, for instance, then this now starts erring again because we haven't handled all the cases again. If I had a default, of course,

03:27 this will kind of do the job for me. Let's just say default returns whatever. And now I can add whatever I like. But this means then that actually adding a return type makes our code more robust, not less robust. And I think the difference here is that the function up here, return string or number,

03:44 is a very simple function with a single branch. It just returns one branch. Whereas the function down here has multiple branches in lots of different cases that it's handling. And so this is a judgment call that you're going to need to make. If you feel like, okay, I want my function to tell me

04:03 if I'm doing something wrong, I always want it to return an array of strings, then you should probably use a return type there. And the return type is like a guide to you, the actual writer of the function, that you're doing everything that you expect to. But if you just have a very, very, very simple function here like return string or number, usually it's going to be more accurate

04:22 just to not use the return type there. Now, there is a like config option in TypeScript that forces you to add return types to all of your functions. And I absolutely hate it. Do not use it. It's called no, oh God, I can't even remember it. I think it's no return types. I'll add it to the description below.

04:41 But essentially you should not be forcing yourself to add return types to your functions because often they're not necessary and they can be slightly inaccurate.