The reasons why one may not want to use `which` have already been explained, but here are a few examples on a few systems where `which` actually fails.

On Bourne-like shells, we're comparing the output of `which` with the output of `type` (`type` being a shell builtin, it's meant to be the ground truth, as it's the shell telling us how it would invoke a command).

Many cases are _corner_ cases, but bear in mind that `which`/`type` are often used in corner cases (to find the answer to an unexpected behaviour like: _why on earth is that command behaving like that, which one am I calling?_).

## Most systems, most Bourne-like shells: functions

The most obvious case is for functions:

 $ type ls
 ls is a function
 ls ()
 {
	[ -t 1 ] && set -- -F "$@";
	command ls "$@"
 }
 $ which ls
 /bin/ls

The reason being that `which` only reports about executables, and sometimes about aliases (though not always the ones of _your_ shell), not functions.

The GNU which man page has a broken (as they forgot to quote `$@`) example on how to use it to report functions as well, but just like for aliases, because it doesn't implement a shell syntax parser, it's easily fooled:

 $ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
 $ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
 $ type f
 f is a function
 f ()
 {
	echo '
 }
 g ()
 { echo bar;
 }
 ' >> ~/foo
 }
 $ type g
 bash: type: g: not found
 $ which f
 f ()
 {
	echo '
 }
 $ which g
 g ()
 { echo bar;
 }



## Most systems, most Bourne-like shells: builtins

Another obvious case is builtins or keywords, as `which` being an external command has no way to know which builtins your shell have (and some shells like `zsh`, `bash` or `ksh` can load builtins dynamically):

 $ type echo . time
 echo is a shell builtin
 . is a shell builtin
 time is a shell keyword
 $ which echo . time
 /bin/echo
 which: no . in (/bin:/usr/bin)
 /usr/bin/time

(that doesn't apply to `zsh` where `which` is builtin)

## Solaris 10, AIX 7.1, HP/UX 11i, Tru64 5.1 and many others:

 $ csh
 % which ls
 ls: aliased to ls -F
 % unalias ls
 % which ls
 ls: aliased to ls -F
 % ksh
 $ which ls
 ls: aliased to ls -F
 $ type ls
 ls is a tracked alias for /usr/bin/ls
 
That is because on most commercial Unices, `which` (like in the original implementation on 3BSD) is a `csh` script that reads `~/.cshrc`.
The aliases it will report are the ones defined there regardless of the aliases you currently have defined and regardless of the shell you're actually using.

In HP/UX or Tru64:

 % echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
 % setenv PATH ~/bin:/bin:/usr/bin
 % ln -s /bin/ls ~/bin/
 % which ls
 /bin/ls
 
(the Solaris and AIX versions have fixed that issue by saving `$path` before reading the `~/.cshrc` and restoring it before looking up the command(s))

 $ type 'a b'
 a b is /home/stephane/bin/a b
 $ which 'a b'
 no a in /usr/sbin /usr/bin
 no b in /usr/sbin /usr/bin

Or:

 $ d="$HOME/my bin"
 $ mkdir "$d"; PATH=$PATH:$d
 $ ln -s /bin/ls "$d/myls"
 $ type myls
 myls is /home/stephane/my bin/myls
 $ which myls
 no myls in /usr/sbin /usr/bin /home/stephane/my bin

(of course, being a `csh` script you can't expect it to work with arguments containing spaces...)

## CentOS 6.4, bash

 $ type which
 which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
 $ alias foo=': "|test|"'
 $ which foo
 alias foo=': "|test|"'
 /usr/bin/test
 $ alias $'foo=\nalias bar='
 $ unalias bar
 -bash: unalias: bar: not found
 $ which bar
 alias bar='

On that system, there's an alias defined system-wide that wraps the GNU `which` command.

The bogus output is because `which` reads the output of `bash`'s `alias` but doesn't know how to parse it properly and uses heuristics (one alias per line, looks for the first found command after a `|`, `;`, `&`...)

The worst thing on CentOS is that `zsh` has a perfectly fine `which` builtin command but CentOS managed to break it by replacing it with a non-working alias to GNU `which`.


## Debian 7.0, ksh93:

(though applies to most systems with many shells)

 $ unset PATH
 $ which which
 /usr/local/bin/which
 $ type which
 which is a tracked alias for /bin/which

On Debian, `/bin/which` is a `/bin/sh` script. In my case, `sh` being `dash` but it's the same when it's `bash`.

An unset `PATH` is not to disable `PATH` lookup, but means using the system's _default PATH_ which unfortunately on Debian, nobody agrees on (`dash` and `bash` have `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, `zsh` has `/bin:/usr/bin:/usr/ucb:/usr/local/bin`, `ksh93` has `/bin:/usr/bin`, `mksh` has `/usr/bin:/bin`, `execvp()` (like in `env`) has `:/bin:/usr/bin` (yes, looks in the current directory first!)).

Which is why `which` gets it wrong above since it's using `dash`'s default `PATH` which is different from `ksh93`'s

It's not better with GNU `which` which reports:

 which: no which in ((null))

(interestingly, there is indeed a `/usr/local/bin/which` on my system which is actually an `akanga` script that came with `akanga` (an `rc` shell derivative where the default `PATH` is `/usr/ucb:/usr/bin:/bin:.`))

## bash, any system:

The one [Chris is referring to in his answer](http://unix.stackexchange.com/a/85280/22565):
 
 $ PATH=$HOME/bin:/bin
 $ ls /dev/null
 /dev/null
 $ cp /bin/ls bin
 $ type ls
 ls is hashed (/bin/ls)
 $ command -v ls
 /bin/ls
 $ which ls
 /home/chazelas/bin/ls

Also after calling `hash` manually:

 $ type -a which
 which is /usr/local/bin/which
 which is /usr/bin/which
 which is /bin/which
 $ hash -p /bin/which which
 $ which which
 /usr/local/bin/which
 $ type which
 which is hashed (/bin/which)

## Now a case where `which` and sometimes `type` fail:

 $ mkdir a b
 $ echo '#!/bin/echo' > a/foo
 $ echo '#!/' > b/foo
 $ chmod +x a/foo b/foo
 $ PATH=b:a:$PATH
 $ which foo
 b/foo
 $ type foo
 foo is b/foo

Now, with some shells:

 $ foo
 bash: ./b/foo: /: bad interpreter: Permission denied

With others:

 $ foo
 a/foo

Neither `which` nor `type` can know in advance that `b/foo` cannot be executed. Some shells like `bash`, `ksh` or `yash`, when invoking `foo` will indeed try to run `b/foo` and report an error, while others (like `zsh`, `ash`, `csh`, `Bourne`, `tcsh`) will run `a/foo` upon the failure of the `execve()` system call on `b/foo`.