Jake Worth

Jake Worth

Just Right: Type Your TypeScript Library Functions

Published: March 01, 2023 2 min read

  • typescript

There’s been a lot of recent discussion in the TypeScript community about typing functions. These arguments tend to take binary positions: always type your functions, or never type them unless the compiler demands it.

Which is the best option? Today I’d like to present a third option you might not have considered, that I think is just right:

  • Use implicit types most of the time
  • Type library functions

Use Implicit Types Most of the Time

Most of the time, we should use implicit types. Here’s a React click handler with implicit typing:

const handleClick = () => {
  alert('clicked');
};

The TypeScript compiler accepts this code without an explicit return type because it can evaluate the function and knows that it can’t return anything.

We call this not-thing void:

const handleClick = (): void => {
  alert('clicked');
};

So, is adding the type an improvement? In my opinion, no. Most click handlers don’t return anything, so adding that doesn’t tell us much.

Use implicit types when you can. Your fingers and fellow developers will thank you.

Type Your Library Functions

An exception to this rule is library functions. These benefit from explicit typing.

Consider this function that accepts a string of markdown and returns a React node with parsed HTML:

import React, { ReactNode } from 'react';
import ReactMarkdown from 'react-markdown';

const renderMarkdown = (markdown: string): ReactNode => {
  return <ReactMarkdown>{markdown}</ReactMarkdown>;
}

My compiler makes me type the input, because ReactMarkdown’s child must be a string, and it can’t be sure that a string will always be passed. But I didn’t have to type the returned ReactNode; TypeScript can infer that from the library because it is itself typed.

If it’s not required, why add it? I think the answer lies in how library functions differ from other kinds of functions. Library functions are meant to be:

  • Extracted to a shared directory
  • Reusable
  • Best when not casually extended. No hasty abstractions!

Typing the function’s inputs and outputs supports each of these goals. It teaches programmers what the inputs and outputs should be, which can be unclear in isolation. It makes the function more reusable because it’s easier to understand. And, typing makes it just a bit harder to casually extend the function because you have to spell out the new types.

Typing your library functions is a good idea.

Counterargument: I Don’t Want To!

Maybe you’re thinking: why bother typing code if we don’t have to?

To that, I’d say code is for humans as well as machines. Just because a compiler doesn’t require something doesn’t mean it doesn’t have value. Consider unit tests, inline tests, or well-written comments— each provides value that isn’t required. We include them because they make the code more pleasant and predictable.

I think library functions in general should be over-communicated. It should be clear through as many channels as possible what they do and don’t do.

Explicit typing provides this kind of value.

Wrapping Up

Use implicit types most of the time, and type your library functions. Do you have a different approach? I’d love to hear about it.

What are your thoughts on this? Let me know!


Join 100+ engineers who subscribe for advice, commentary, and technical deep-dives into the world of software.