I dug into portage a bit and I think I see what you're saying. But the only way Portage can automatically load a dependency is if the ebuild specifies it. For example an ebuild can install dependencies in DEPEND or RDEPEND by checking what USE flags were passed ([1]). You can also have CFLAGs which modify compilation without installing any dependencies. So for example, in portage if I install vim with USE=python, and gnucash with USE=python, then they will both pull in python dependencies based on what was specified in the respective ebuilds (see [2] and [3]), which will probably install the same version of dev-python libs. But that's only because someone packaged vim and gnucash with a USE=python option and specified the required packages for that flag.

You can do the same thing in Nix (e.g. you can package software with a bunch of options and pull in required dependencies based on the options selected by the user), but you're right that Nix doesn't have a standardized API for doing this like Portage does. I think it would be a nice thing to have. Maybe it takes a lot of work for maintainers to pre-package every possible build option like they do in Gentoo. Instead, Nix focuses more on providing a unified interface (the Nix language) for end users to modify anything about any package. nixpkgs is also pretty flexible, allowing maintainers to use packaging approaches that aren't "standard practice", and those approaches are all exposed to the end user if they need to make changes. This usually means reading the Nix package source to see what's going on if you want to make changes to a given package. For example some packages like vim do provide a nice interface for users to choose pre-packaged build options [4], similar to USE in Portage. Since Nix is lazy-evaluated, you just have to reference the dependency in the code somewhere (search for ${python3} in the linked example), and then that dependency will only get installed if that bit of code gets executed (e.g. if the pythonSupport option gets passed).

I think that's a big part of what makes Nix a pain, the fact that end users can make almost any change they can think of to any attribute on the entire system. But that's also what makes it very powerful. Right now most people use overlays, as people described above. So you can modify the "buildInputs" attribute (the set of build-time dependencies), or the "buildPhase" to pass some different CMake flags, or any install phase you like, for any package in nixpkgs (see [5] for all bajillion phases). Nix flakes might make it easier to compose custom set of packages, but I'm not sure how yet.

Oh and one more random tangent - this whole time I've been talking about build dependencies in Nix as opposed to runtime dependencies. That's because runtime dependencies are loaded automatically based on whether they get referenced in the final output of the build. Since every package has a unique hash, Nix literally greps the final build for package hashes, and if it finds any it loads them in as runtime dependencies. This is different from something like Portage where runtime dependencies are explicitly specified in RDEPEND.

[1] https://devmanual.gentoo.org/general-concepts/dependencies/#...
[2] https://gitweb.gentoo.org/repo/gentoo.git/tree/app-office/gn...
[3] https://gitweb.gentoo.org/repo/gentoo.git/tree/app-editors/v...
[4] https://github.com/NixOS/nixpkgs/blob/master/pkgs/applicatio...
[5] https://nixos.org/manual/nixpkgs/stable/#ssec-controlling-ph...