ts-reset icon indicating copy to clipboard operation
ts-reset copied to clipboard

Tuple return types for `Map.groupBy` and `Object.groupBy`

Open controversial opened this issue 2 months ago • 1 comments

Object.groupBy and Map.groupBy always generate non-empty arrays in values, so accessing someValue[0] is always safe:

Consider the following example (under noUncheckedIndexedAccess)

const myItems = ['a', 'b', 'c', 'd'];
// 1 => ['a', 'c'], 0 => ['b', 'd']
const grouped = Map.groupBy(myItems, (letter) => letter.charCodeAt(0) % 2);
grouped.values().forEach((group) => {
  //                           ^? (parameter) group: string[]
  const myElement = group[0];
  //    ^? const myElement: string | undefined
});

However, a still-safe and more useful type for group would be [string, ...string[]] (indicating that the array is always non-empty), because Map.groupBy / Object.groupBy never generate empty array values. This would let myElement be safely typed as string even under noUncheckedIndexedAccess.

Something like:

interface MapConstructor {
    /**
     * Groups members of an iterable according to the return value of the passed callback.
     * @param items An iterable.
     * @param keySelector A callback which will be invoked for each item in items.
     */
    groupBy<K, T>(
        items: Iterable<T>,
        keySelector: (item: T, index: number) => K,
    ): Map<K, [T, ...T[]]>;
}

controversial avatar Oct 07 '25 20:10 controversial

Yeah this is nice and makes sense.

What I like about it is that [T, ...T] is assignable to T[], so we're actually being more permissive, not less permissive.

mattpocock avatar Oct 09 '25 08:10 mattpocock