A chave aqui é que abrir um FIFO é uma operação de bloqueio. O open só retorna quando as duas extremidades estiverem conectadas, ou seja, quando o fifo estiver aberto para leitura e escrita.
Normally, opening the FIFO blocks until the other end is opened also.
No primeiro caso, o shell se bifurca para executar o pipeline, portanto, abrir o fifo para leitura ( cat fifo ) e abrir o fifo para gravação ( > fifo ) ocorre em processos separados, portanto, independentemente.
No segundo caso, o aberto para leitura e aberto para escrita ( 3<>fifo ) acontece em uma única etapa.
No terceiro caso, <(cat fifo) se expande para um nome de arquivo, por exemplo %código%. Então, é como se você estivesse executando /dev/fd/42 . Você precisa de um nc -l localhost 8888 /dev/fd/42 > fifo extra para que seja equivalente, por exemplo, < .
No quarto caso, o shell está tentando abrir o fifo para leitura ( nc -l localhost 8888 < <(cat fifo) > fifo ) e abri-lo para gravação ( < fifo ) como parte do mesmo processo. O shell faz um de cada vez, da esquerda para a direita. Então, ele tenta abrir > fifo para leitura e bloqueia para sempre, esperando que algo abra fifo para gravação. Eu acho que você vai achar que, neste caso, fifo nunca sequer começou, e a porta nunca foi aberta para ouvir.