I came up with something that "works", but it's kinda crazy:
type Alternating<T extends readonly any[], A, B> =
T extends readonly [] ? T
: T extends readonly [A] ? T
: T extends readonly [A, B, ...infer T2]
? T2 extends Alternating<T2, A, B> ? T : never
: never
This requires TypeScript 4.1+ because of the recursive conditional type.
Naive usage requires duplicating the value as a literal type for the T parameter, which is not ideal:
const x: Alternating<[1, 'a', 2], number, string> = [1, 'a', 2]
That seems strictly worse than just writing out [number, string, number] as the type. However with the help of a dummy function it's possible to avoid repetition:
function mustAlternate<T extends readonly any[], A, B>(
_: Alternating<T, A, B>
): void {}
const x = [1, 'a', 2] as const
mustAlternate<typeof x, number, string>(x)
Here's a live demo with some test cases.
I wouldn't actually recommend relying on this in typical codebases (it's awkward to use and the error messages are terrible). I mostly just worked through it to see how far the type system could be stretched.
If anyone has suggestions for how to make it less wonky, I'm all ears!
Update
If you're using TypeScript 4.9+ you can ditch the mustAlternate function in favor of the satisfies operator. Here's an example.
[A, B][], an array of tuples?