Don't use return types, unless...

Your default attitude towards return types should be 'don't use them', unless one of three things are true:

  • You're writing a function with multiple branches
  • You're building a library
  • You have a specific TS perf concern and you're banging your head against the wall

Transcript

0:00 What's up, wizards? We are going to be looking at return types today. Return types in TypeScript give you the ability to specify what you think a function should return. Here, we have a function called makeId. This returns a string of ID with a random number of length 16 appended to the end of it.

0:16 If we want to, we can guarantee that this function returns a string by adding string here. Now we can see that result is result string. We could force makeId to return a number instead by changing this to number. Now we get an error here saying, "Type string is not assignable to type number."

0:30 The result down here changes its type to match the return type. This means you can use a return type to force a function to return a certain thing. If we remove this number, you can see that result still gets inferred properly. That's because TypeScript is smart enough to understand that this returns a string.

0:47 The question is, should we add the return type or should we not? Most of the time, you should not be using return types. In simple functions like makeId, TypeScript knows what you're returning. You don't need to tell it something that it already knows. This return type ends up just being extra code that you need to maintain.

1:04 The rule of thumb should be not to use return types and let TypeScript infer the result on its own. This changes, though, when you have multiple branches inside your function. What do I mean by a branch? Let's imagine we have a function called handleNewState where we're taking in two different types of events. We have a window focused event and a window blurred event.

1:22 This handleNewState function is going to return a new state based on the event that's been passed. In window blurred, we return one state. In window focused, we return another state. Let's take a look at the new state that we get when we handle the window blurred event.

1:35 This new state is pretty messy. We've got what looks like latestUpdate with an uppercase U and latestupdate with a lowercase u defined here. This looks really, really fishy to me. The only things that we intended to return were isFocused Boolean and latestUpdate number. Let's create a new type that describes that.

1:52 Now we'll mark handleNewState return type as state. You can see that in one of the branches here, latestupdate is with a lowercase. We can fix that by changing it to uppercase. Because of this return type, we're now sure that handleNewState will return what we want it to.

2:04 If we add a new event called window closed, then we can be sure that this function still returns what we want it to, making this code easier to extend. If you have more than one branch in your function, then you should be using a return type. That means an if statement. That means a switch statement. That probably also means a ternary as well.

2:22 By default, don't require your devs to mark every function with a return type. There's an ESLint rule for that, and it's my least favorite one. I hate it. Some devs in some projects have it turned on. There is an exception to this, though. If you're writing library code, you should always declare return types.

2:38 Let's say that we wanted to bundle our handleNewState function into a library. We should add an export to every type that that function consumes. Let's say we're inside an application that wants to consume from this library. We can create a function called wrapHandleNewState, which just takes handleNewState and passes an event to it.

2:54 The issue is, how do we type this event? We can just use the type that we get from the library. If we want to handle our own logic inside here, then we probably also want to add a state return type here, too. This means that consumers of your library can use the types that you've used to type your functions.

3:10 There's also some stuff in the TypeScript Performance Wiki about libraries using return types. If, as a library, you use return types, you're going to see a faster bundle speed, because these types are easier to emit from TypeScript than anonymous ones.

3:22 For the folks saying, "I use TypeScript return types because of performance," that's really only for when you're printing declaration files in a library setting, which is a legit use case, but it doesn't really map up to application code.

3:34 I have been proved wrong on that point before. I've had people bring to me TypeScript performance problems where just a return type was the solution for some magical reason. If you're getting some really, really slow TypeScript compilation in your application, you may want to consider adding return types if the return type is really, really complicated.

3:53 To sum up, in application code, don't by default use return types. Use them if your function has multiple branches. Use them if you're writing a library, definitely. Use them if you have any weird niche performance concerns to do with really big objects being passed and inferred through your code. You can turn that ESLint rule off that forces you to have return types, please.

When should you use return types? The use cases are narrower than you might think...

Discuss on Twitter

More Tips

Play Type Predicates

Type Predicates

1 min

Play TypeScript 5.1 Beta is OUT!

TypeScript 5.1 Beta is OUT!

2 mins

Play How to Name your Types

How to Name your Types

4 mins

Play TypeScript 5.0 Beta Deep Dive

TypeScript 5.0 Beta Deep Dive

6 mins

Play Conform a Derived Type Without Losing Its Literal Values

Conform a Derived Type Without Losing Its Literal Values

1 min

Play Avoid unexpected behavior of React’s useState

Avoid unexpected behavior of React’s useState

1 min

Play Understand assignability in TypeScript

Understand assignability in TypeScript

2 mins

Play Compare function overloads and generics

Compare function overloads and generics

1 min

Play Use infer in combination with string literals to manipulate keys of objects

Use infer in combination with string literals to manipulate keys of objects

1 min

Play Access deeper parts of objects and arrays

Access deeper parts of objects and arrays

1 min

Play Ensure that all call sites must be given value

Ensure that all call sites must be given value

1 min

Play Understand how TypeScript infers literal types

Understand how TypeScript infers literal types

1 min

Play Get a TypeScript package ready for release to NPM in under 2 minutes

Get a TypeScript package ready for release to NPM in under 2 minutes

1 min

Play Use assertion functions inside classes

Use assertion functions inside classes

1 min

Play Assign local variables to default generic slots to dry up your code and improve performance

Assign local variables to default generic slots to dry up your code and improve performance

2 mins

Play Know when to use generics

Know when to use generics

2 mins

Play Map over a union type

Map over a union type

1 min

Play Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

1 min

Play Use generics to dynamically specify the number, and type, of arguments to functions

Use generics to dynamically specify the number, and type, of arguments to functions

1 min

Play Use 'declare global' to allow types to cross module boundaries

Use 'declare global' to allow types to cross module boundaries

2 mins

Play Turn a module into a type

Turn a module into a type

2 mins

Play Create autocomplete helper which allows for arbitrary values

Create autocomplete helper which allows for arbitrary values

2 mins

Play Use deep partials to help with mocking an entity

Use deep partials to help with mocking an entity

1 min

Play Throw detailed error messages for type checks

Throw detailed error messages for type checks

1 min

Play Create a 'key remover' function which can process any generic object

Create a 'key remover' function which can process any generic object

1 min

Play Use generics in React to make dynamic and flexible components

Use generics in React to make dynamic and flexible components

1 min

Play Create your own 'objectKeys' function using generics and the 'keyof' operator

Create your own 'objectKeys' function using generics and the 'keyof' operator

1 min

Play Write your own 'PropsFrom' helper to extract props from any React component

Write your own 'PropsFrom' helper to extract props from any React component

1 min

Play Use 'extends' keyword to narrow the value of a generic

Use 'extends' keyword to narrow the value of a generic

1 min

Play Use function overloads and generics to type a compose function

Use function overloads and generics to type a compose function

2 mins

Play Decode URL search params at the type level with ts-toolbelt

Decode URL search params at the type level with ts-toolbelt

2 mins

Play Use 'in' operator to transform a union to another union

Use 'in' operator to transform a union to another union

2 mins

Play Derive a union type from an object

Derive a union type from an object

2 mins