Is your code really typesafe? Evolving any might ruin it
Tags: typescript, cleancode, tips, beginners
Published: 2023-11-06 07:01:12

## Summary
- When you declare a variable like the following, the variable is "evolving any"
  - `let` variable without type annotation and initial value
  - `const` variable with mutable initial values such as an array but no type annotation
- "Evolving any" is a variable that can be assigned to any value at any time though it has a value assigned to it already

## Overview
If you have experience working on a project using TypeScript, you might have seen a pattern like below.
```typescript
function foo(arg: "string" | "number") {
  let dynamicVariable;
  
  // depends on the given arg, change the value of "dynamicVariable"
  if(arg === "string") {
    dynamicVariable = "Yes I'm string";
  } else {
    dynamicVariable = 100;
  }
 
  return dynamicVariable;
}
```
It seems to be just a regular function that returns either `string` or `number`. However, there's a little pitfall on `dynamicVariable` which has something to do with its implicit behavior.

Let's say there's another developer happened to work on this function and they assigned a new value to it. Then the code looks as follows;
```typescript
function foo(arg: "string" | "number") {
  let dynamicVariable;
  
  // depends on the given arg, change the value of "dynamicVariable"
  if(arg === "string") {
    dynamicVariable = "Yes I'm string";
  } 
  
  if(arg === "number") {
    dynamicVariable = 100;
  }

  // adding an arbitrary value - you can assign it without a problem
  dynamicVariable = true;

  return dynamicVariable;
}
```
Yes, there's nothing wrong with it. We know that because TypeScript doesn't cry on this code. But **this `dynamicVariable` being able to be assigned to anything** is quite concerning as someone who doesn't have a context that this is expected to return `string | number` could totally ruin this function's intention by assigning some unintended value as I did in the example above.

## "Evolving any"
The behavior in the previous example is called **Evolving any**. You have two ways to declare an evolving any variable;

1. When you declare a variable with `let` or `var` that has neither type nor initial value
2. `const` variable without type annotation with mutable initial value such as array

```typescript
//You can make this variable to any type at any point within the scope
let evolvingAnyLet;

//You can put any element in this array at any point within the scope. However, you cannot directly mutate this variable
const evolvingAnyConst = [];
``` 

A variable with evolving any acts like a variable with type `any`. You can assign any value to it at any point and TypeScript updates the inference of the variable as you assign a new value to it. So it's something a bit more type-safe than `any` but less than typed variables.

## Why is Evolving any bad?
Though TypeScript allows us to have some freedom with the dynamic types with evolving any, this can confuse your team or future-yourself. As I mentioned in the example, it's quite easy for a developer without the original context to misinterpret the intention of the variable or the scope which results in assigning some arbitrary value to it which eventually destroys the initial intention of the block of the code. There are several points why I think it's an anti-pattern.

### 1. It's hard to spot when there's a bug
A bug introduced by evolving any tends to be hard to debug since TypeScript, our beloved friend doesn't give us any clue of the error when it occurs as it's "type-safe". Imagine you have long chains of functions that contain multiple evolving any values...

### 2. Evolving any is an implicit behavior
Since this behavior is implicit, we cannot get help from IDE (e.g. VSCode)or TypeScript. Any implicit behavior like this can bring an unspoken nuance to your codebase which can be missed quite easily. The "original intention" is one of the examples.

### 3. Less readability
It's simply hard to read. You cannot trust the value until you hit the bottom line of the scope and you have to read from bottom to top to understand what it actually does. Any code that requires you to read back and forth typically has room for improvement and evolving any indeed is one of them.

## What to do to avoid evolving any
Simple. Just assign either type or initial value to the variable when it's declared. If you do so, you won't need to deal with any of these surprising behaviors so it's such a huge win for a small effort. It's important to be clear on the intention when you declare a variable as every single line of code you write is a potential legacy of your project. The less clue you leave the higher chance you'd be cursed by the developers who look into the project later.

## Conclusion
Once you understand what it is, it's not hard to avoid but I've seen quite a few developers (even seniors too!) making this mistake and ending up leaving cryptic-nuanced code. I'd strongly recommend [cleaning up any evolving any when you see one](https://twitter.com/unclebobmartin/status/1591443936836747264?lang=en). This little check would truly increase type-safety of your codebase. Making TypeScript silent isn't our goal, writing a code that prevents surprises is.