In many situations, a Foo*& is a replacement for a Foo**. In both cases, you have a pointer whose address can be modified.
Suppose you have an abstract non-value type and you need to return it, but the return value is taken up by the error code:
error_code get_foo( Foo** ppfoo )
or
error_code get_foo( Foo*& pfoo_out )
Now a function argument being mutable is rarely useful, so the ability to change where the outermost pointer ppFoo points at is rarely useful. However, a pointer is nullable -- so if get_foo's argument is optional, a pointer acts like an optional reference.
In this case, the return value is a raw pointer. If it returns an owned resource, it should usually be instead a std::unique_ptr<Foo>* -- a smart pointer at that level of indirection.
If instead, it is returning a pointer to something it does not share ownership of, then a raw pointer makes more sense.
There are other uses for Foo** besides these "crude out parameters". If you have a polymorphic non-value type, non-owning handles are Foo*, and the same reason why you'd want to have an int* you would want to have a Foo**.
Which then leads you to ask "why do you want an int*?" In modern C++ int* is a non-owning nullable mutable reference to an int. It behaves better when stored in a struct than a reference does (references in structs generate confusing semantics around assignment and copy, especially if mixed with non-references).
You could sometimes replace int* with std::reference_wrapper<int>, well std::optional<std::reference_wrapper<int>>, but note that is going to be 2x as large as a simple int*.
So there are legitimate reasons to use int*. Once you have that, you can legitimately use Foo** when you want a pointer to a non-value type. You can even get to int** by having a contiguous array of int*s you want to operate on.
Legitimately getting to three-star programmer gets harder. Now you need a legitimate reason to (say) want to pass a Foo** by indirection. Usually long before you reach that point, you should have considered abstracting and/or simplifying your code structure.
All of this ignores the most common reason; interacting with C APIs. C doesn't have unique_ptr, it doesn't have span. It tends to use primitive types instead of structs because structs require awkward function based access (no operator overloading).
So when C++ interacts with C, you sometimes get 0-3 more *s than the equivalent C++ code would.