Annotated solution
Published August 23, 2026The solution
type Chunk< T extends readonly unknown[], N extends number, Acc extends unknown[] = [], > = Acc['length'] extends N ? [Acc, ...Chunk<T, N>] : T extends readonly [infer H, ...infer R] ? Chunk<R, N, [...Acc, H]> : Acc extends [] ? [] : [Acc]
The common wrong answer
type Chunk< T extends readonly unknown[], N extends number, Acc extends unknown[] = [], > = Acc['length'] extends N ? [Acc, ...Chunk<T, N>] : T extends readonly [infer H, ...infer R] ? Chunk<R, N, [...Acc, H]> : [Acc]
Almost right, and wrong only where it is hardest to notice. Without the `Acc extends []` guard the empty input produces `[[]]` — a tuple containing one empty group — instead of `[]`. A perfectly divisible input hits the same path and gains a trailing empty group.
Line by line
Acc['length'] extends NThe full-group test comes first, before the input is examined. Reaching `N` emits the group and restarts with the default empty accumulator, because `Chunk<T, N>` omits the third argument.
Acc extends [] ? [] : [Acc]The input ran out. Either the last group is empty — meaning the previous one closed exactly on the boundary, and there is nothing left to emit — or it holds a short final group that belongs in the result.
Takeaway
Recursions with two exits need both exits tested. The boundary case where the input divides evenly is the one that slips through, and it is exactly the case a hand-written example usually avoids.
