Simplified:
In *nix environments primarily, many programs have similar building blocks. These building blocks are often separate projects and are managed as dependencies, which may in turn have their own dependencies. You end up with a tree or dependencies for each program, where parts in the tree may be the same for different programs. Only one copy of each part used in all trees is kept on the system so any program that needs it has it available. Usually all these libraries (dependencies) are kept in a few common locations on the system, and done global system settings list what those locations are. If you look you'll find a bunch of libraries all thrown into the same directory with one another as a result.

But what happens when program A needs a library X version between 1.0 and 1.9, and program B needs library X version between 2.0 and 5.0? Both programs assume the system will have only one version on library X, and need it to meet their version requirements. Enter Guix (and it's cousin Nix).

With Guix, instead of the single global setting listing a couple directories to find all libraries in, a unique environment is crafted for running program A where a large number of folders appear to be listed in the setting, each including exactly one version of one library. In parallel a different unique environment is crafted for program B in the same way. But in A's environment the folder for library X is the one with library X version 1.5, and in B's environment the folder for library X is the one for version 3.2, but they might both use the same folder for library Y version 0.6. This may seem obvious, but it's not as easy as this all sounds. There are actually a lot of global settings for where to find dependencies, some of them have to match each other, and which ones get used depends on what the programs being run are. Also there's more at play then just the version number of the library, what optional parts are included also plays a role. This gives a huge number of possible variations of packages, not all of which are easily summarized by something as simple as a version number. Therefore Guix had to have some way of consistently referring to a package that used a specific version of code, included specific options, and a while list of other factors that affect what results (called a "build closure"). How do you make sure you actually found everything that affects the build result though? You run it again under conditions that shouldn't change it and confirm it's the same output. Successfully doing this is called "deterministic builds", and allows you to conclusively state that a list of these inputs (the build closure) will always produce exactly these outputs. This may involve doing things like making the build of the package always think it's 1970-01-01 00:00:00 UTC+0 so builds that embed a timestamp always have the same timestamp, which is why Guix has had to create a large list of packages you need to pull from.
One you have these deterministic builds, you can ask for a package via build closure, Guix can download (or locally build and populate) the package into your cache, and every subsequent request in a Guix-managed she'll used to run a program can point to the library folder in the cache.

Making this even more difficult, building code has it's own dependencies added to the ones necessary for running it ("run time" vs "build time" dependencies). For compiled code this can be tools like the compiler used for example.

When trying to figure out what's in the build closure, it can be very difficult. One way to make sure you really do control everything is to isolate it. That's what containers do.