diff --git a/news/4.bugfix.rst b/news/4.bugfix.rst
new file mode 100644
index 0000000..071d5ae
--- /dev/null
+++ b/news/4.bugfix.rst
@@ -0,0 +1,3 @@
+Ensure the result returned by the resolver only contains candidates that are
+actually needed. This is done by tracing the graph after resolution completes,
+snipping nodes that don’t have a route to the root.
diff --git a/setup.cfg b/setup.cfg
index 0bde529..a1f6e08 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -35,7 +35,6 @@ examples =
     packaging
     requests
 test =
-    commentjson
     packaging
     pytest
 release =
diff --git a/src/resolvelib/resolvers.py b/src/resolvelib/resolvers.py
index e733124..183b002 100644
--- a/src/resolvelib/resolvers.py
+++ b/src/resolvelib/resolvers.py
@@ -105,7 +105,7 @@ class ResolutionTooDeep(ResolutionError):
 
 
 # Resolution state in a round.
-State = collections.namedtuple("State", "mapping graph criteria")
+State = collections.namedtuple("State", "mapping criteria")
 
 
 class Resolution(object):
@@ -136,14 +136,10 @@ class Resolution(object):
         try:
             base = self._states[-1]
         except IndexError:
-            graph = DirectedGraph()
-            graph.add(None)  # Sentinel as root dependencies' parent.
-            state = State(mapping={}, graph=graph, criteria={})
+            state = State(mapping={}, criteria={})
         else:
             state = State(
-                mapping=base.mapping.copy(),
-                graph=base.graph.copy(),
-                criteria=base.criteria.copy(),
+                mapping=base.mapping.copy(), criteria=base.criteria.copy(),
             )
         self._states.append(state)
 
@@ -178,41 +174,16 @@ class Resolution(object):
 
     def _check_pinnability(self, candidate, dependencies):
         backup = self.state.criteria.copy()
-        contributed = set()
         try:
             for subdep in dependencies:
                 key = self._p.identify(subdep)
                 self._contribute_to_criteria(key, subdep, parent=candidate)
-                contributed.add(key)
         except RequirementsConflicted:
             criteria = self.state.criteria
             criteria.clear()
             criteria.update(backup)
-            return None
-        return contributed
-
-    def _pin_candidate(self, name, criterion, candidate, child_names):
-        try:
-            self.state.graph.remove(name)
-        except KeyError:
-            pass
-        self.state.mapping[name] = candidate
-        self.state.graph.add(name)
-        for parent in criterion.iter_parent():
-            parent_name = None if parent is None else self._p.identify(parent)
-            try:
-                self.state.graph.connect(parent_name, name)
-            except KeyError:
-                # Parent is not yet pinned. Skip now; this edge will be
-                # connected when the parent is being pinned.
-                pass
-        for child_name in child_names:
-            try:
-                self.state.graph.connect(name, child_name)
-            except KeyError:
-                # Child is not yet pinned. Skip now; this edge will be
-                # connected when the child is being pinned.
-                pass
+            return False
+        return True
 
     def _pin_criteria(self):
         criteria = self.state.criteria
@@ -233,15 +204,11 @@ class Resolution(object):
             if self._is_current_pin_satisfying(name, criterion):
                 # If the current pin already works, just use it.
                 continue
-            candidates = list(criterion.candidates)
-            while candidates:
-                candidate = candidates.pop()
+            for candidate in reversed(criterion.candidates):
                 dependencies = self._p.get_dependencies(candidate)
-                child_names = self._check_pinnability(candidate, dependencies)
-                if child_names is None:
-                    continue
-                self._pin_candidate(name, criterion, candidate, child_names)
-                break
+                if self._check_pinnability(candidate, dependencies):
+                    self.state.mapping[name] = candidate
+                    break
             else:
                 # All candidates tried, nothing works. This criterion is a dead
                 # end, signal for backtracking.
@@ -324,6 +291,56 @@ class Resolution(object):
         raise ResolutionTooDeep(max_rounds)
 
 
+def _has_route_to_root(criteria, key, all_keys, connected):
+    if key in connected:
+        return True
+    for p in criteria[key].iter_parent():
+        try:
+            pkey = all_keys[id(p)]
+        except KeyError:
+            continue
+        if pkey in connected:
+            connected.add(key)
+            return True
+        if _has_route_to_root(criteria, pkey, all_keys, connected):
+            connected.add(key)
+            return True
+    return False
+
+
+Result = collections.namedtuple("Result", "mapping graph criteria")
+
+
+def _build_result(state):
+    mapping = state.mapping
+    all_keys = {id(v): k for k, v in mapping.items()}
+    all_keys[id(None)] = None
+
+    graph = DirectedGraph()
+    graph.add(None)  # Sentinel as root dependencies' parent.
+
+    connected = {None}
+    for key, criterion in state.criteria.items():
+        if not _has_route_to_root(state.criteria, key, all_keys, connected):
+            continue
+        if key not in graph:
+            graph.add(key)
+        for p in criterion.iter_parent():
+            try:
+                pkey = all_keys[id(p)]
+            except KeyError:
+                continue
+            if pkey not in graph:
+                graph.add(pkey)
+            graph.connect(pkey, key)
+
+    return Result(
+        mapping={k: v for k, v in mapping.items() if k in connected},
+        graph=graph,
+        criteria=state.criteria,
+    )
+
+
 class Resolver(AbstractResolver):
     """The thing that performs the actual resolution work.
     """
@@ -358,4 +375,4 @@ class Resolver(AbstractResolver):
         """
         resolution = Resolution(self.provider, self.reporter)
         resolution.resolve(requirements, max_rounds=max_rounds)
-        return resolution.state
+        return _build_result(resolution.state)

Clean up graph after resolution
Resolver should clean up the graph to purge dead subtrees, and maping to remove unneeded pins after resolution (or maybe after each round?).
diff --git a/tests/functional/cocoapods/test_resolvers_cocoapods.py b/tests/functional/cocoapods/test_resolvers_cocoapods.py
index 6e8b68b..acae715 100644
--- a/tests/functional/cocoapods/test_resolvers_cocoapods.py
+++ b/tests/functional/cocoapods/test_resolvers_cocoapods.py
@@ -4,7 +4,6 @@ import operator
 import os
 import re
 
-import commentjson
 import packaging.specifiers
 import packaging.version
 import pytest
@@ -53,14 +52,14 @@ def _parse_specifier_set(inp):
 
 
 def _safe_json_load(filename):
-    # Some fixtures has comments so the stdlib implementation doesn't work.
-    # We only use commentjson if we absolutely need to because it's SLOW.
     try:
         with open(filename) as f:
             data = json.load(f)
     except ValueError:
+        # Some fixtures has comments. Try to fix the file.
         with open(filename) as f:
-            data = commentjson.load(f)
+            lines = [line for line in f if not line.lstrip().startswith("//")]
+            data = json.loads("".join(lines))
     return data
 
 
@@ -128,12 +127,11 @@ XFAIL_CASES = {
     "conflict_on_child.json": "different resolution",
     "deep_complex_conflict.json": "different resolution",
     "fixed_circular.json": "different resolution",
-    "previous_conflict.json": "different resolution",
+    "previous_conflict.json": "Fixture JSON parse error",
     "pruned_unresolved_orphan.json": "different resolution",
     "shared_parent_dependency_with_swapping.json": "KeyError: 'fog'",
     "simple_with_base.json": "different resolution",
     "spapping_and_rewinding.json": "different resolution",
-    "swapping_children_with_successors.json": "different resolution",
 }
 
 

pip install -e .[test]
parse_log_pytest
pytest
pytest
apt-get update apt-get install -y gcc
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
commentjson==0.9.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
lark-parser==0.7.8
packaging @ file:///croot/packaging_1720101850331/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042571233/work
pytest @ file:///croot/pytest_1717793244625/work
-e git+https://github.com/sarugaku/resolvelib.git@b6963a9af16346cf908bd2f7cacec80112835880#egg=resolvelib
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work

name: resolvelib
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py38h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.1=py38h06a4308_0
  - pip=24.2=py38h06a4308_0
  - pluggy=1.0.0=py38h06a4308_1
  - pytest=7.4.4=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py38h06a4308_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - commentjson==0.9.0
      - lark-parser==0.7.8
      - resolvelib==0.2.3.dev0
prefix: /opt/conda/envs/resolvelib

tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[swapping_children_with_successors]
tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[shared_parent_dependency] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[conflict_common_parent]
tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[unresolvable_child] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[three_way_conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[swapping_changes_transitive_dependency] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[simple_with_shared_dependencies] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[simple_with_dependencies] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[simple] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[root_conflict_on_child] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[previous_primary_conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[empty] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[contiguous_grouping] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[circular]
news/4.bugfix.rst setup.cfg src/resolvelib/resolvers.py
news/4.bugfix.rst setup.cfg src/resolvelib/resolvers.py