Why avoid using 'any' in TypeScript
Tags: typescript, javascript, coding, programming
Published: 2023-11-30 07:45:04

When we create applications in Angular, the `any` type is our "live-saver" when struggle with complex error messages, want to save time by not writing specific type definitions, or think that TypeScript's type checking limits our coding flexibility and freedom.

Using the `any` type might seem like **an easy solution** to common problems, but it's important for developers to think about the **hidden issues** and the real effects of using this simple method.

Using the `any` type a lot can accidentally weaken TypeScript main purpose, which is to make **code safer** and find errors early. Ignoring the advantages of checking types can lead to hidden mistakes, harder-to-maintain code, and more complex code.

Today, I will to show few reasons with examples, why I avoid using `any` and instead embrace `unknown`. I will also discuss how to utilize TypeScript utility types to create flexible new types. Let's get started.

## Type Safety

When you pick `any` type might seem like an easy solution for typing issues. However, this choice has a big drawback: we lose type safety, which is the main feature of TypeScript, to ensuring your code works correctly.

I want to show the dangers of not considering type safety with `any`, and highlight how important TypeScript's type system is.

Consider the following code snippet, we have the `accountBalance` method which expects an `account` and `amount` to update the balance.

```typescript
export class Accounting {

  accountBalance(account: any, amount:any) {
    return account.balance += amount;
  }
}
```

Since the `accountBalance` method expects an '`any`' type for both account and amount, I can pass the following parameters:

```typescript
let accounting = new Accounting()
let balanceA = accounting.accountBalance({accountNumber: '05654613', balance: 15}, 26)    
let balanceB = accounting.accountBalance({accountNumber: '05654613', balance: 15}, '26')
console.log({balanceA, balanceB})
```

Is this the expected result? Why does everything compile and appear to work fine, but fail during runtime?


![rerere](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yiighe8ccetd0u8fs7ex.png)


When working with TypeScript, we expect TypeScript, the IDE, or the compiler to warn us about these kinds of issues 🤦‍♂️.

Why doesn't the compiler warn about the issue? I believe the most effective solution is to introduce the Account type.

```typescript
export type Account = {
  accountNumber: string;
  balance: number;
}
```

Change the signature of the `accountBalance` function to use the `Account` type

```typescript
 accountBalance(account: Account, amount: number): number {
    return (account.total += amount);
  }
```

With appropriate typing, the IDE, compiler, and application all notify us of potential issues.


![rerer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f5p07zc2vhj07ux8dcxz.png)

![rere(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xggy9kjcs6ltrr4ch1y4.png)


![rere](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ay7e9bcn6vibyze3h4je.png)

Perfect! Now we know how to use the function and avoid bugs during runtime. Let's take a look at another scenario.

## The IDE WebStorm & VSCode

We love TypeScript because it offers more than just assistance with code; it enhances Visual Studio Code and WebStorm by providing powerful features such as auto-complete, code navigation, and refactoring, all of which rely on TypeScript's types.

But when we use `any` type safety and these useful tools. We'll see how using `any` too much can make coding worse, and how good typing makes your code safer for refactoring.

For example, we have a method called `updateAccount` that accepts an account of any type, as well as an empty object.

```typescript
account: any = {}
  DEFAULT_BALANCE = 3000

  updateAccount(account: any) {
    account.accountID = Math.random().toString();
    account.balance = this.DEFAULT_BALANCE;
    return account;
  }
```

Everything works fine, but what happens if I want to refactor 'accountNumber' to 'id' and 'balance' to 'total'?

I'm using the refactoring tools in WebStorm to modify the variable names, but when I change `accountID` to `id` and 'balance' to 'total', the IDE fails to make all the necessary adjustments.


![f](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4lbot7hhejhl4vo7vew.png)


We can resolve this issue by changing the type from `any` to `Account`. After making this change, we can confidently proceed with the refactoring.

```typescript
updateAccount(account: Account): Account {
    account.id = Math.random().toString();
    account.total = this.DEFAULT_BALANCE;
    return account;
  }
```

We can refactor once more, and the IDE will catch everything, ensuring a smooth process.


![f](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8pzdbvy4v623rg531dmg.png)


How about the account object? Instead of using '`any`', let's change it to '`Account`'. The IDE will notify us that the object is not initialized and also requires all 'Account' properties.


![f](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1nq3be1cm5m84bpi4g5.png)

![f](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d2lqvi0vx089cxb8q47x.png)



Afterward, the IDE and compiler compel us to initialize all required properties.

```typescript
DEFAULT_BALANCE = 3000;
  account: Account = {
    id: "DEFAULT_ID",
    total : this.DEFAULT_BALANCE
  }
```

As you can see, using '`any`' might save time, but sometimes the price to pay is not worth it.

## **What I can do?**

Maybe you ask, what can you do when you want to have flexibility? Then, when that happens, the unknown type is your best friend. But before introducing the `unknown` and utility types comes to help you.

Before to introduce unknown, let show the the differences between `any` and `unknown`.

* `any` permits any operation, which can lead to runtime errors.
    
* `unknown` require explicit type validation before its use.
    

### **Any Type**

The `any` type lets you do anything without checking types, making TypeScript more flexible. This can be helpful, but also risky, as it may cause hard-to-find errors during program execution.

#### Example:

```typescript
let riskyData: any = getDataFromAPI();
console.log(riskyData.name); // Compiles fine, even if riskyData doesn’t have a name property.
riskyData(); // Compiles fine, even if riskyData is not a function.
```

In the example, TypeScript doesn't give any warnings about riskyData because it has the "any" type. This can cause errors if riskyData doesn't have the features we think it has.

### **Unknown Type**

The `unknown` type is safer than any. It stands for any value but limits random actions on those values. To use an unknown value, you need to do specific checks to find out its type.

#### Example:

```typescript
let safeData: unknown = getDataFromAPI();
console.log(safeData.name); // Error: Object is of type 'unknown'.
if (typeof safeData === 'string') {
   // we can perform because now TypeScript knows it’s a string.
    console.log(safeData.toUpperCase()); 
} else if (typeof safeData === 'object' && safeData !== null && 'name' in safeData) {
   // Safe, as we checked that 'name' is a property on safeData.
    console.log(safeData.name); 
}
```

In this example, TypeScript makes sure you can only use the name property or change safeData to uppercase after checking its type. This helps avoid errors by making sure the actions on the data are safe.

## I'm lazy to create types

I know maybe we want to create a type with just a few properties, or miss one from Account; then, for these situations, the [TypeScript utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html) can help you.

These utility types help you create types from others, but the ones I use most are Pick and Omit, which are utility types that help eliminate the need for any:

1. **Pick:** Creates a type with selected properties from another type. Example: `type AccountID = Pick<Account, 'id'>;`
    
2. **Omit:** Generates a type excluding specific properties. Example: `type AccountWithoutID = Omit<Account, 'id'>;`
    

## Recap

I know using `any` can be tempting, but it should be approached with caution. Now that we understand the implications and the price to pay when using it, I believe that employing alternatives such as `unknown` and utility types will make your life easier and result in a better code base.

![re](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hvw5kmec7za5dgii4b08.png)

If you want to learn more about types feel free to checkout these other articles:

* [https://www.danywalls.com/3-ways-of-type-transformation-in-typescript](https://www.danywalls.com/3-ways-of-type-transformation-in-typescript)
    
* [https://www.danywalls.com/how-to-use-record-type-in-typescript](https://www.danywalls.com/how-to-use-record-type-in-typescript)