Declared vs Inferred Types, a Debate of the Century

2023-02-03 by Hisam Fahri

What on earth all of this about?

Well I might be exagerrating on the title. But, I think this little journal will get a little exciting (and a little bit spicy of course). If you've ever written Typescript before, you'll notice that the Typescript compiler is smart enought to inferred types of the code you've been writing. It's surely really helpful, right?

A double edged sword

Let's create a simple function for the sake of demonstration:


// sample.ts
function convertString(str: string) {
    return parseInt(str);
}

const result: number = convertString("1");

Well everything looks fine, right? Is there a problem here? Well, yes. but there's a catch.

Developers are human (at least for now)

Developer can make mistakes. In fact, they can make a lot of mistakes. My personal preferences is that a piece of code should (as best as possible) explain themselves. This often being supported by comments left by the developer itself, name, and the types.


Let's setup an imaginary cases. If your teammates (or even you) try to change the return of a function without checking all of the references of the function, this can lead to a messy problems that might waste a lot of your time. If we take above piece of code and directly messing around with the return type:


// sample.ts 
function convertString(str: string) {
    return parseInt(str).toString();
}

const result: number = convertString("1");
      // ^?: Type 'string' is not assignable to type 'number'.

If the implementation, the function (producer) and the usage (consumer) itself are simple, this problem might be easy to fix. But, often time in a real world implementations, a real world application, the implementation is astronomically more difficult than the example above.


In the code above, you as a developer might think that your consumer is at fault, and spend a lot of time trying to have a lot of weird working around just to "make it work".


// sample.ts 
const result: number = parseint(convertString("1"));

Just "say" it out loud

Some peoples might find this as a hot takes, but declaring your types (for input, and expected output) might be a tedious work at start, but it is surely worth it. Why? Let's take a look at the code again:


// sample.ts 
function convertString(str: string): number {
    return parseInt(str).toString();
      // ^?: Type 'string' is not assignable to type 'number'.
}

const result: number = convertString("1");

As you can see, the error move from the consumer, to the producer itself. Because, it is the producer fault itself at faults here. The example above is surely an over-simplification of what actually could happen in a real world application.

Read it easily

Not just our function become more "secure", it also makes our function itself more readable. And, as I mention earlier, the code speaks for itself. Even though we name our function converString(), since we explicitly declare our refer function as number, it'll intuitively explain that our function is converting a string to a number.

Should I declare everything then?

Well, finally it comes back to your use cases and preferences. Have been working with a lot of low level languages made me really comfortable with declaring types explicitly than relying on the compiler to do that for me.


It surely is an extra piece of code, but I felt amazing using it. In the end it comes down to yourself to decide.