DEV Community

Cover image for Type Safety with Non-Empty Arrays in TypeScript
Martin Persson
Martin Persson

Posted on

Type Safety with Non-Empty Arrays in TypeScript

The Problem with Empty Arrays

When working with arrays in TypeScript, we can specify the types, for example, an array of numbers number[]. But this can lead to errors because we can create an empty array even if we say it should be an array of numbers.

const numbersArray: number[] = [] 
const getFirstValue = (array: number[]) => array[0];
console.log(getFirstValue(numbersArray)); // undefiend, no type error
Enter fullscreen mode Exit fullscreen mode

Introducing Non-Empty Arrays

TypeScript gives us the ability to create custom generic types and type helpers, and this can be used to create a specific constraint: a "Non-empty array" type.

type NonEmptyArray<T> = [T, ...T[]]
Enter fullscreen mode Exit fullscreen mode

Here we create a generic type helper that ensures that the array contains at least one element of the given type T, but it could also include more. This type is defined using a combination of [T] and ...T[], forming a variadic tuple.

  • [T] signifies that there must be exactly one element of type T at the beginning of the array, guaranteeing that the array is never empty.
  • ...T[] represents the rest of the elements in the array, allowing for zero or more additional elements of type T. This part ensures flexibility in the number of elements while maintaining type safety.

This custom type can be a valuable tool in scenarios where an empty array could lead to unexpected behaviors or errors. By enforcing the presence of at least one element, this type helps improve the robustness of the code and provides clear documentation of the array's expected content.

Going back to our array of numbers, we can improve it using our new type:

const numbersArray1: NonEmptyArray<number> = [] // Type error
const numbersArray2: NonEmptyArray<number> = [1] // Valid

const getFirstValueSafe = (array: NonEmptyArray<number>) => array[0];

const numbersArray3: number[] = []

console.log(getFirstValueSafe(numbersArray2)); // 1
console.log(getFirstValueSafe(numbersArray3)); // Type error
Enter fullscreen mode Exit fullscreen mode

Applying Non-Empty Arrays to Real-World Problems

When working with users in a system, often they need to have specific roles. Let's look at a common example where a user can have a name and an array of different roles.

type Roles = "admin" | "editor" | "user";

interface UserUnsafe {
  name: string;
  roles: Roles[];
}

const createUserWithRoles = (name: string, roles: Roles[]) => ({
  name,
  roles
});

// We can create a user with empty roles that can lead to bugs in our code.
const newUnsafeUser = createUserWithRoles("Bob", []); 
Enter fullscreen mode Exit fullscreen mode

Instead, we should use our Non-Empty Array type:

interface UserSafe {
  name: string;
  roles: NonEmptyArray<Roles>;
}

const createUserWithRolesSafe = (name: string, roles: NonEmptyArray<Roles>) => ({
  name,
  roles
});

const newSafeUser1 = createUserWithRolesSafe("John", []); // Type Error
const newSafeUser2 = createUserWithRolesSafe("John", ["admin"]);
Enter fullscreen mode Exit fullscreen mode

Conclusion

The Non-Empty Array type in TypeScript provides an elegant way to enforce constraints on array content. It allows for better control, reducing the chances of unexpected errors related to empty arrays. By leveraging the power of generics and tuple types, you can create more robust and readable code.

Top comments (0)