So, with bash's alphabet expansion thing, this works:
set {a..z}
for a do printf "./$a/$a%s\n" "$@"
done | xargs mkdir -p
And if you just type out the alphabet once in the first line the same concept should be portable to any shell. There are other ways to arrive at the set line if you don't want to type it like:
seq -sP32P 97 123|dc
a b c d e f g h i j k l m n o p q r s t u v w x y z
...for instance works in an ASCII locale. So you could do set $(seq -sP32P 97 123|dc) or any other command that would get you an $IFS separated list of the arguments you need, but, I mean, it's probably better just to use the bash thing or to type it.
Anyway, I think that's the way I would do it if only because it only invokes mkdir as often as necessary.
And just to demonstrate how it works, here's a little debug output of a smaller set:
sh -cx 'for n do printf "./$n/$n%s\n" "$@"; done|cat' -- arg1 arg2 arg3
+ for n in '"$@"'
+ printf './arg1/arg1%s\n' arg1 arg2 arg3
+ cat
+ for n in '"$@"'
+ printf './arg2/arg2%s\n' arg1 arg2 arg3
+ for n in '"$@"'
+ printf './arg3/arg3%s\n' arg1 arg2 arg3
./arg1/arg1arg1
./arg1/arg1arg2
./arg1/arg1arg3
./arg2/arg2arg1
./arg2/arg2arg2
./arg2/arg2arg3
./arg3/arg3arg1
./arg3/arg3arg2
./arg3/arg3arg3
As you can see, the for only loops once per positional parameter array index, which I here set by simply handing sh the parameters at invocation, and above with set ${positionals}. But printf receives the same array in its argument list for each iteration and applies its format string to each of its arguments, so you get the semblance of recursion without any unnecessary recursion.
And adding the done|command will stream all of a for loop's output over the pipe in the same way done >file would stream it all into a file - only opening and closing the output file once for the entire for...done construct.