# Typescript Utility Type - ReturnType

Despite having a reputation of making developers' lives more complicated, Typescript does still provide a huge benefit if you use it well. One of the best ways to use it is to utilize [utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype). They are built-in types that you can use to perform safe type-checking on your codebase. In this article, we'll explore one of the utility types - `ReturnType`.

## What is `ReturnType`?

`ReturnType` allows you to easily create a type that represents whatever a function returns. Let's look at an example:

```typescript
function f() {
  return { a: 1, b: 2, }
}

// F is now { a: 1, b: 2 } type
type F = ReturnType<typeof f>;
```

Pretty simple, right? Well, what's the use case of `ReturnType`? Let's now look at a simple use case for it.

## Reduces Type Maintenance Cost

The power of `ReturnType` comes into play when we're invoking a same function multiple times in various locations and multiple other functions rely on the values returned by that function. Let's look at an example:

```typescript
function getPosts() {
    return [
        {
            title: "Title 1",
            author: "Author 1",
            content: "Content 1",
        },
        {
            title: "Title 2",
            author: "Author 2",
            content: "Content 2",
        }, 
        {
            title: "Title 3",
            author: "Author 3",
            content: "Content 3",
        }
    ]
}

function removeContent(
    posts: {
        title: string;
        author: string;
        content: string;
    }[]
) {
    return posts.map(({content, ...rest}) => rest);
}

function addDisplayOrder(
    posts: {
        title: string;
        author: string;
        content: string;
    }[]
) {
    return posts.map((post, index) => ({ ...post, displayOrder: index }));
}

function combineTitleAuthor(
    posts: {
        title: string;
        author: string;
        content: string;
    }[]
) {
    return posts.map(({title, author, content}) => ({ titleAuthor: `${title} - ${author}`, content }));
}

function main() {
    const posts = getPosts();

    const postsWithoutContent = removeContent(posts);
    const postsWithDisplayOrder = addDisplayOrder(posts);
    const combinedTitleAuthorPosts = combineTitleAuthor(posts);

    // ...
}
```

What's "wrong" with this code? If you have keen eyes, you would've already spotted, but we actually have a repeating code, which is the type of posts used as an argument for `removeContent`, `addDisplayOrder` and `combineTitleAuthor`. So here's an improved code:

```typescript
function getPosts() {
    return [
        {
            title: "Title 1",
            author: "Author 1",
            content: "Content 1",
        },
        {
            title: "Title 2",
            author: "Author 2",
            content: "Content 2",
        }, 
        {
            title: "Title 3",
            author: "Author 3",
            content: "Content 3",
        }
    ]
}

type Posts = {
    title: string;
    author: string;
    content: string;
}[];

function removeContent(posts: Posts) {
    return posts.map(({content, ...rest}) => rest);
}

function addDisplayOrder(posts: Posts) {
    return posts.map((post, index) => ({ ...post, displayOrder: index }));
}

function combineTitleAuthor(posts: Posts) {
    return posts.map(({title, author, content}) => ({ titleAuthor: `${title} - ${author}`, content }));
}

function main() {
    const posts = getPosts();

    const postsWithoutContent = removeContent(posts);
    const postsWithDisplayOrder = addDisplayOrder(posts);
    const combinedTitleAuthorPosts = combineTitleAuthor(posts);

    // ...
}
```

Much better! But what happens when we add number of views for each post? Let's take a look:

```typescript
function getPosts() {
    return [
        {
            title: "Title 1",
            author: "Author 1",
            content: "Content 1",
            views: 1,
        },
        {
            title: "Title 2",
            author: "Author 2",
            content: "Content 2",
            views: 2,
        }, 
        {
            title: "Title 3",
            author: "Author 3",
            content: "Content 3",
            views: 3,
        }
    ]
}

type Posts = {
    title: string;
    author: string;
    content: string;
    // Need to add views field
    views: number;
}[];

function removeContent(posts: Posts) {
    return posts.map(({content, ...rest}) => rest);
}

function addDisplayOrder(posts: Posts) {
    return posts.map((post, index) => ({ ...post, displayOrder: index }));
}

function combineTitleAuthor(posts: Posts) {
    return posts.map(({title, author, content}) => ({ titleAuthor: `${title} - ${author}`, content }));
}

function main() {
    const posts = getPosts();

    const postsWithoutContent = removeContent(posts);
    const postsWithDisplayOrder = addDisplayOrder(posts);
    const combinedTitleAuthorPosts = combineTitleAuthor(posts);

    // ...
}
```

Once we added `views` field to each post, we had to add `views: number;` to `Posts` type. If you're used to construct a type this way, you may think that it's the best we can do. However, you can actually improve this further by utilizing `ReturnType`.

```typescript
function getPosts() {
    return [
        {
            title: "Title 1",
            author: "Author 1",
            content: "Content 1",
            views: 1,
        },
        {
            title: "Title 2",
            author: "Author 2",
            content: "Content 2",
            views: 2,
        }, 
        {
            title: "Title 3",
            author: "Author 3",
            content: "Content 3",
            views: 3,
        }
    ]
}

type Posts = ReturnType<typeof getPosts>

function removeContent(posts: Posts) {
    return posts.map(({content, ...rest}) => rest);
}

function addDisplayOrder(posts: Posts) {
    return posts.map((post, index) => ({ ...post, displayOrder: index }));
}

function combineTitleAuthor(posts: Posts) {
    return posts.map(({title, author, content}) => ({ titleAuthor: `${title} - ${author}`, content }));
}

function main() {
    const posts = getPosts();

    const postsWithoutContent = removeContent(posts);
    const postsWithDisplayOrder = addDisplayOrder(posts);
    const combinedTitleAuthorPosts = combineTitleAuthor(posts);

    // ...
}
```

We've switched from using `{ title: string; author: string; content: string; views: number; }[]` to `ReturnType<typeof getPosts>`. You might wonder why. Before, the `Posts` and the return type of `getPosts` function were not aligned, meaning any changes in `getPosts`' return type had to be manually updated in `Posts`. Now, with the current code, `Posts` and `getPosts` are in sync, ensuring that any changes in the return type of `getPosts` are automatically reflected in `Posts`. This might seem trivial, but it's quite beneficial, especially as your codebase grows. A larger codebase means longer type-checking times by your linter, and this synchronization reduces the need for additional iterations, which can be quite impactful.

## Conclusion

Utilizing TypeScript's `ReturnType` simplifies code management by automatically syncing type definitions, reducing manual updates and maintenance overhead. This not only enhances consistency across the codebase but also streamlines development, particularly in large-scale projects. Happy hacking 👨‍💻!
