Four concepts are enough to read most Nix output and most Nix flakes. Past those four you can grow as needed.
The Nix Store (/nix/store). Every package, every version, every variant lives in /nix/store/<hash>-<name>/. The hash is computed from the entire build recipe — every input, every dependency, every flag. This is why two builds can never collide: different inputs produce different hashes, so they get different paths.
Generations and rollback. Every install or upgrade creates a new numbered 'generation' of your user profile. nix-env --rollback moves the active pointer back one generation — instant, atomic, undoable. List generations with nix-env --list-generations; jump to any specific one with nix-env --switch-generation <n>.
Flakes are the unit of Nix project structure. A flake.nix declares inputs (the nixpkgs version you depend on, other flakes you use) and outputs (dev shells, packages, NixOS modules, apps). A flake.lock sits next to it and pins the exact versions of every input — the same reproducibility guarantee as package-lock.json or uv.lock, but for entire dev environments.
nix-darwin is a Nix module set for macOS. With nix-darwin you declaratively manage your entire Mac — installed packages, Dock arrangement, Finder preferences, Homebrew casks, services, login items — all in version-controlled Nix code. Apply changes with darwin-rebuild switch. It's the most ambitious dotfiles setup imaginable, and it's reproducible across Macs.