Skip to main content
chriscoppola.me
Stacks of coins growing in size, representing long-term savings milestones.
Photo by Timothy Cuenat on Unsplash
Back to blog

Beyond any: Embracing Type Safety in TypeScript

TypeScript’s any type can lead to harder-to-maintain and error-prone code. A few small habits keep `any` from spreading: prefer `unknown` at boundaries, gate it with type guards, and confine the cases where you genuinely need it.

Understanding the Pitfalls of any

Using any type is risky as it undermines TypeScript type system. Use unknown type instead for safer and explicit coding practices.

Embrace unknown and Type Guards:

Prefer using 'unknown' over 'any' in TypeScript projects for variables with uncertain types. Use type guards for runtime type assertions to ensure safe interaction with variables.

Example of a Type Guard:

This demonstrates a practical application of unknown and type guards, illustrating how to implement type safety in everyday coding practices.

function isString(value: unknown): value is string {
  return typeof value === 'string'}

Usage of the isString Type Guard:

This shows the type guard in action, reinforcing the concept of conditional type narrowing and its impact on ensuring type-safe operations.

const value: unknown = ...; // Assume value is dynamically assigned
if(isString(value)) { // Utilizes the type guard
  console.log(value.toUpperCase()); // Safe to assume 'value' is a string here
}

Strategies for Explicit any Usage:

While avoiding any is ideal, practical scenarios sometimes necessitate its use, particularly when interfacing with JavaScript libraries. Here's how to manage such cases responsibly:

ExplicitAny Type Declaration:

type ExplicitAny = any; // A clear alias for intentional 'any' usage

It makes the deliberate use of any explicit, facilitating easier tracking and future refactoring within the codebase.

Scoped any Usage:

Confine any to specific areas, particularly at the boundaries of TypeScript and JavaScript code. Use functions to narrow any to more specific types through type guards or assertions. It limits the potential issues caused by any, maintaining the integrity of the type system elsewhere in your project.

// Any is confined to a function, not in your main code
function addFunctionToWindow(propertyName: string, fn: () => void){
  (window as ExplicitAny)[propertyName] = fn
}
// We can easily find any
function getPrivateDatumFromObject<D>(value: ExplicitAny): D {
  if(isObject(value) && isObject(object.__datum)){
  return object.__datum as D  }  return undefined
}

You can come back to these functions later and figure out how to get rid of ExplicitAny.

Handling Third-Party Libraries

Navigating untyped third-party libraries is a common reason developers resort to any. Here are strategies from simple to more comprehensive for improving type safety:

Module Declaration In Type Declaration Files:

Create .d.ts files for libraries lacking TypeScript support, initially declaring modules as any to provide a temporary solution.

// In a custom .d.ts file
declare module 'example-library'; // Treated as 'any' when imported
// Add explicit types once you have time
declare module 'example-library' {
  export function add(a: number, b: number): number // Will export add function
}

This is a quick way to integrate untyped libraries into your project while planning for future improvements in type definitions.

BE CAREFUL because whatever you declare will be assumed to exist in the library.

Fork and Type:

Consider forking a library to add your own type annotations, enhancing type safety for your project and potentially benefiting the wider community. It addresses the lack of types at the source, contributing to the ecosystem while ensuring your project’s type safety.


Conclusion: A Call for Type Safety

Switching from `any` to `unknown` is a small change with a long payoff: the code you write now keeps telling you the truth about itself six months from now, when you have forgotten how it was supposed to work.

// keep reading

Get the next one in your inbox.

One email when something new goes up. No digests, no marketing, no tracking pixels.