In the bash shell, subshells are implemented by forking a child process, so you won't see a case of a subshell not running in a child process in that shell.

ksh93 is the only shell that I know that skips the forking when possible for subshells (an optimisation that is still quite buggy and that the successive people that have tried to maintain it after AT&T disbanded the team that had written it have considered removing).

Some shells like FreeBSD's `sh` can skip the fork in very specific cases, like in:

```
var=$(printf %04d "$n")
```

(here with a `printf` builtin, and no change to the environment is being done in there).

In a pipeline, all components have to run concurrently, so they have to run in separate processes, even in ksh93.

In `bash`, they all run in child processes. In AT&T ksh or zsh, or with `bash -O lastpipe` (when non-interactive), the rightmost one doesn't (of course, you still need to fork a child process to run external commands such as `ps`).

You don't see an extra `bash` process in `ps >&2 | ps` or `(ps)` because `ps` is executed directly  in that child process (which before executing `ps` was bash interpreting the pipeline component: the subshell).

In `bash` (contrary to `zsh`/`ksh`), you do see an extra `bash` process in `(: anything; ps)`, the optimisation is only done if the subshell has only one external command, you'd need to use `exec` to do that optimisation by hand there: `(: anything; exec ps)`.

Same goes for `{ ps; } | cat`.