diff --git a/examples/pypi_wheel_provider.py b/examples/pypi_wheel_provider.py
index 027eb03..4d3dba6 100644
--- a/examples/pypi_wheel_provider.py
+++ b/examples/pypi_wheel_provider.py
@@ -1,9 +1,10 @@
-import platform
+from email.message import EmailMessage
+from email.parser import BytesParser
 from io import BytesIO
+from operator import attrgetter
+from platform import python_version
 from urllib.parse import urlparse
 from zipfile import ZipFile
-from email.parser import BytesParser
-from email.message import EmailMessage
 
 import requests
 import html5lib
@@ -14,7 +15,7 @@ from packaging.utils import canonicalize_name
 
 from extras_provider import ExtrasProvider
 
-PYTHON_VERSION = Version(platform.python_version())
+PYTHON_VERSION = Version(python_version())
 
 
 class Candidate:
@@ -123,19 +124,23 @@ class PyPIProvider(ExtrasProvider):
     def get_preference(self, resolution, candidates, information):
         return len(candidates)
 
-    def find_matches(self, requirement):
-        candidates = []
-        name = requirement.name
-        extras = requirement.extras
+    def find_matches(self, requirements):
+        assert requirements, "resolver promises at least one requirement"
+        assert not any(
+            r.extras for r in requirements[1:]
+        ), "extras not supported in this example"
+
+        name = canonicalize_name(requirements[0].name)
 
         # Need to pass the extras to the search, so they
         # are added to the candidate at creation - we
         # treat candidates as immutable once created.
-        for c in get_project_from_pypi(name, extras):
+        candidates = []
+        for c in get_project_from_pypi(name, set()):
             version = c.version
-            if version in requirement.specifier:
+            if all(version in r.specifier for r in requirements):
                 candidates.append(c)
-        return candidates
+        return sorted(candidates, key=attrgetter("version"), reverse=True)
 
     def is_satisfied_by(self, requirement, candidate):
         if canonicalize_name(requirement.name) != candidate.name:
diff --git a/examples/reporter_demo.py b/examples/reporter_demo.py
index 8d10582..cce2e2a 100644
--- a/examples/reporter_demo.py
+++ b/examples/reporter_demo.py
@@ -50,14 +50,10 @@ class Provider(resolvelib.AbstractProvider):
     def get_preference(self, resolution, candidates, information):
         return len(candidates)
 
-    def find_matches(self, requirement):
-        deps = [
-            (n, v)
-            for (n, v) in sorted(self.candidates, reverse=True)
-            if n == requirement[0] and v in requirement[1]
-        ]
-        deps.sort()
-        return deps
+    def find_matches(self, requirements):
+        for n, v in sorted(self.candidates, reverse=True):
+            if all(n == r[0] and v in r[1] for r in requirements):
+                yield (n, v)
 
     def is_satisfied_by(self, requirement, candidate):
         assert candidate[0] == requirement[0]
diff --git a/setup.cfg b/setup.cfg
index a60e435..7a21627 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,20 +8,20 @@ author_email = uranusjr@gmail.com
 long_description = file: README.rst
 license = ISC License
 keywords =
-    dependency
-    resolution
+	dependency
+	resolution
 classifier =
-    Development Status :: 3 - Alpha
-    Intended Audience :: Developers
-    License :: OSI Approved :: ISC License (ISCL)
-    Operating System :: OS Independent
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 3
-    Topic :: Software Development :: Libraries :: Python Modules
+	Development Status :: 3 - Alpha
+	Intended Audience :: Developers
+	License :: OSI Approved :: ISC License (ISCL)
+	Operating System :: OS Independent
+	Programming Language :: Python :: 2
+	Programming Language :: Python :: 3
+	Topic :: Software Development :: Libraries :: Python Modules
 
 [options]
 package_dir =
-    = src
+	= src
 packages = find:
 include_package_data = true
 zip_safe = false
@@ -31,19 +31,19 @@ where = src
 
 [options.extras_require]
 examples =
-    html5lib
-    packaging
-    requests
+	html5lib
+	packaging
+	requests
 lint =
 	black
 	flake8
 test =
-    commentjson
-    packaging
-    pytest
+	commentjson
+	packaging
+	pytest
 release =
 	setl
-    towncrier
+	towncrier
 
 [bdist_wheel]
 universal = 1
@@ -51,8 +51,8 @@ universal = 1
 [flake8]
 max-line-length = 80
 exclude =
-    .git,
-    .venvs,
-    __pycache__,
-    build,
-    dist,
+	.git,
+	.venvs,
+	__pycache__,
+	build,
+	dist,
diff --git a/src/resolvelib/compat/__init__.py b/src/resolvelib/compat/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/resolvelib/compat/collections_abc.py b/src/resolvelib/compat/collections_abc.py
new file mode 100644
index 0000000..366cc5e
--- /dev/null
+++ b/src/resolvelib/compat/collections_abc.py
@@ -0,0 +1,6 @@
+__all__ = ["Sequence"]
+
+try:
+    from collections.abc import Sequence
+except ImportError:
+    from collections import Sequence
diff --git a/src/resolvelib/providers.py b/src/resolvelib/providers.py
index ba86512..326b1ec 100644
--- a/src/resolvelib/providers.py
+++ b/src/resolvelib/providers.py
@@ -48,17 +48,19 @@ class AbstractProvider(object):
         """
         raise NotImplementedError
 
-    def find_matches(self, requirement):
-        """Find all possible candidates that satisfy a requirement.
+    def find_matches(self, requirements):
+        """Find all possible candidates that satisfy the given requirements.
 
-        This should try to get candidates based on the requirement's type.
+        This should try to get candidates based on the requirements' types.
         For VCS, local, and archive requirements, the one-and-only match is
         returned, and for a "named" requirement, the index(es) should be
         consulted to find concrete candidates for this requirement.
 
-        The returned candidates should be sorted by reversed preference, e.g.
-        the most preferred should be LAST. This is done so list-popping can be
-        as efficient as possible.
+        :param requirements: A collection of requirements which all of the the
+            returned candidates must match. All requirements are guaranteed to
+            have the same identifier. The collection is never empty.
+        :returns: An iterable that orders candidates by preference, e.g. the
+            most preferred candidate should come first.
         """
         raise NotImplementedError
 
diff --git a/src/resolvelib/resolvers.py b/src/resolvelib/resolvers.py
index e45f5a1..4497f97 100644
--- a/src/resolvelib/resolvers.py
+++ b/src/resolvelib/resolvers.py
@@ -1,5 +1,6 @@
 import collections
 
+from .compat import collections_abc
 from .providers import AbstractResolver
 from .structs import DirectedGraph
 
@@ -77,7 +78,9 @@ class Criterion(object):
     def from_requirement(cls, provider, requirement, parent):
         """Build an instance from a requirement.
         """
-        candidates = provider.find_matches(requirement)
+        candidates = provider.find_matches([requirement])
+        if not isinstance(candidates, collections_abc.Sequence):
+            candidates = list(candidates)
         criterion = cls(
             candidates=candidates,
             information=[RequirementInformation(requirement, parent)],
@@ -98,11 +101,9 @@ class Criterion(object):
         """
         infos = list(self.information)
         infos.append(RequirementInformation(requirement, parent))
-        candidates = [
-            c
-            for c in self.candidates
-            if provider.is_satisfied_by(requirement, c)
-        ]
+        candidates = provider.find_matches([r for r, _ in infos])
+        if not isinstance(candidates, collections_abc.Sequence):
+            candidates = list(candidates)
         criterion = type(self)(candidates, infos, list(self.incompatibilities))
         if not candidates:
             raise RequirementsConflicted(criterion)
@@ -218,7 +219,7 @@ class Resolution(object):
 
     def _attempt_to_pin_criterion(self, name, criterion):
         causes = []
-        for candidate in reversed(criterion.candidates):
+        for candidate in criterion.candidates:
             try:
                 criteria = self._get_criteria_to_update(candidate)
             except RequirementsConflicted as e:

diff --git a/tests/functional/cocoapods/test_resolvers_cocoapods.py b/tests/functional/cocoapods/test_resolvers_cocoapods.py
index 19d5b9f..25ac872 100644
--- a/tests/functional/cocoapods/test_resolvers_cocoapods.py
+++ b/tests/functional/cocoapods/test_resolvers_cocoapods.py
@@ -106,14 +106,14 @@ class CocoaPodsInputProvider(AbstractProvider):
     def get_preference(self, resolution, candidates, information):
         return len(candidates)
 
-    def _iter_matches(self, requirement):
+    def _iter_matches(self, name, requirements):
         try:
-            data = self.index[requirement.name]
+            data = self.index[name]
         except KeyError:
             return
         for entry in data:
             version = packaging.version.parse(entry["version"])
-            if version not in requirement.spec:
+            if any(version not in r.spec for r in requirements):
                 continue
             # Some fixtures incorrectly set dependencies to an empty list.
             dependencies = entry["dependencies"] or {}
@@ -123,13 +123,18 @@ class CocoaPodsInputProvider(AbstractProvider):
             ]
             yield Candidate(entry["name"], version, dependencies)
 
-    def find_matches(self, requirement):
-        mapping = {c.ver: c for c in self._iter_matches(requirement)}
-        try:
-            version = self.pinned_versions[requirement.name]
-        except KeyError:
-            return sorted(mapping.values(), key=operator.attrgetter("ver"))
-        return [mapping.pop(version)]
+    def find_matches(self, requirements):
+        name = requirements[0].name
+        candidates = sorted(
+            self._iter_matches(name, requirements),
+            key=operator.attrgetter("ver"),
+            reverse=True,
+        )
+        pinned = self.pinned_versions.get(name)
+        for c in candidates:
+            if pinned is not None and c.ver != pinned:
+                continue
+            yield c
 
     def is_satisfied_by(self, requirement, candidate):
         return candidate.ver in requirement.spec
diff --git a/tests/functional/python/test_resolvers_python.py b/tests/functional/python/test_resolvers_python.py
index 1acf0ac..4c702f8 100644
--- a/tests/functional/python/test_resolvers_python.py
+++ b/tests/functional/python/test_resolvers_python.py
@@ -67,26 +67,28 @@ class PythonInputProvider(AbstractProvider):
     def get_preference(self, resolution, candidates, information):
         return len(candidates)
 
-    def _iter_matches(self, requirement):
-        name = packaging.utils.canonicalize_name(requirement.name)
-
+    def _iter_matches(self, name, requirements):
+        extras = {e for r in requirements for e in r.extras}
         for key, value in self.index[name].items():
             version = packaging.version.parse(key)
-            if version not in requirement.specifier:
+            if any(version not in r.specifier for r in requirements):
                 continue
             yield Candidate(
-                name=requirement.name,
-                version=version,
-                extras=requirement.extras,
+                name=name, version=version, extras=extras,
             )
 
-    def find_matches(self, requirement):
-        mapping = {c.version: c for c in self._iter_matches(requirement)}
-        try:
-            version = self.pinned_versions[requirement.name]
-        except KeyError:
-            return sorted(mapping.values(), key=operator.attrgetter("version"))
-        return [mapping.pop(version)]
+    def find_matches(self, requirements):
+        name = packaging.utils.canonicalize_name(requirements[0].name)
+        candidates = sorted(
+            (c for c in self._iter_matches(name, requirements)),
+            key=operator.attrgetter("version"),
+            reverse=True,
+        )
+        pinned = self.pinned_versions.get(name)
+        for candidate in candidates:
+            if pinned is not None and pinned != candidate.version:
+                continue
+            yield candidate
 
     def is_satisfied_by(self, requirement, candidate):
         return candidate.version in requirement.specifier
diff --git a/tests/functional/swift-package-manager/test_resolvers_swift.py b/tests/functional/swift-package-manager/test_resolvers_swift.py
index ce0a340..51b5f46 100644
--- a/tests/functional/swift-package-manager/test_resolvers_swift.py
+++ b/tests/functional/swift-package-manager/test_resolvers_swift.py
@@ -84,21 +84,27 @@ class SwiftInputProvider(AbstractProvider):
     def get_preference(self, resolution, candidates, information):
         return len(candidates)
 
-    def _iter_matches(self, requirement):
-        container = requirement.container
-        constraint_requirement = requirement.constraint["requirement"]
+    def _iter_matches(self, requirements):
+        container = requirements[0].container
         for version in container["versions"]:
-            parsed_version = _parse_version(version)
-            if not _is_version_allowed(parsed_version, constraint_requirement):
+            ver = _parse_version(version)
+            satisfied = all(
+                _is_version_allowed(ver, r.constraint["requirement"])
+                for r in requirements
+            )
+            if not satisfied:
                 continue
-            preference = _calculate_preference(parsed_version)
+            preference = _calculate_preference(ver)
             yield (preference, Candidate(container, version))
 
-    def find_matches(self, requirement):
+    def find_matches(self, requirements):
         matches = sorted(
-            self._iter_matches(requirement), key=operator.itemgetter(0),
+            self._iter_matches(requirements),
+            key=operator.itemgetter(0),
+            reverse=True,
         )
-        return [candidate for _, candidate in matches]
+        for _, candidate in matches:
+            yield candidate
 
     def is_satisfied_by(self, requirement, candidate):
         return _is_version_allowed(
diff --git a/tests/test_resolvers.py b/tests/test_resolvers.py
index a6cbde8..b0c2d38 100644
--- a/tests/test_resolvers.py
+++ b/tests/test_resolvers.py
@@ -23,8 +23,8 @@ def test_candidate_inconsistent_error():
         def get_dependencies(self, _):
             return []
 
-        def find_matches(self, r):
-            assert r is requirement
+        def find_matches(self, rs):
+            assert len(rs) == 1 and rs[0] is requirement
             return [candidate]
 
         def is_satisfied_by(self, r, c):

Resolving depends on the order of the root requirements in some cases
The following test fails.

```python
def test_requirements_different_candidate_sets():
    requirements = {
        "r1": ["c1"],
        "r2": ["c2"],
    }

    class Provider(AbstractProvider):
        def identify(self, d):
            return "r" if d.startswith("r") else d

        def get_preference(self, *_):
            return 0

        def get_dependencies(self, _):
            return []

        def find_matches(self, r):
            return requirements.get(r, [])

        def is_satisfied_by(self, r, c):
            if r == "r1":
                return c in requirements[r]
            elif r == "r2":
                # r2 accepts anything, even stuff it doesn't
                # return in find_matches
                return True
            return False

    resolver = Resolver(Provider(), BaseReporter())
    result = resolver.resolve(["r1", "r2"])
    assert result.mapping["r"] == "c1"

    resolver = Resolver(Provider(), BaseReporter())
    result = resolver.resolve(["r2", "r1"])
    assert result.mapping["r"] == "c1"
```

Note that the two tests at the end are the same request, just with the order of the requirements reversed.

The requirements have been constructed so that:

1. They are both for the same "project".
2. r1 is a normal sort of requirement.
3. r2 accepts the candidate r1 returns as well as its own one, but *doesn't* return that candidate in `find_matches`.

This specific configuration came up in https://github.com/pypa/pip/pull/8136.

I don't think the result of a `resolve()` call should be order-dependent like this, even if we argue that the provider is pathological. If we don't want to support providers like this, we need to document precisely what rules the provider has to follow - and we should validate them and produce a proper error, not just return an incorrect result.

(Personally, I'd like to get this case to work, as it's far easier to implement pip's constraints if we can).
tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[unresolvable_child] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[swapping_changes_transitive_dependency] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[previous_primary_conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[contiguous_grouping] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[simple_with_shared_dependencies] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[simple_with_base] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[swapping_children_with_successors] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[root_conflict_on_child] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[complex_conflict_unwinding] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[conflict_on_child] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[previous_conflict] 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[complex_conflict] tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[three_way_conflict] tests/functional/python/test_resolvers_python.py::test_resolver[with-without-extras] tests/functional/python/test_resolvers_python.py::test_resolver[conflict-with-dependency] tests/functional/python/test_resolvers_python.py::test_resolver[chalice] tests/functional/swift-package-manager/test_resolvers_swift.py::test_resolver[ZewoHTTPServer] tests/functional/swift-package-manager/test_resolvers_swift.py::test_resolver[kitura] tests/functional/swift-package-manager/test_resolvers_swift.py::test_resolver[SourceKitten] tests/functional/swift-package-manager/test_resolvers_swift.py::test_resolver[PerfectHTTPServer] tests/test_resolvers.py::test_candidate_inconsistent_error
tests/functional/cocoapods/test_resolvers_cocoapods.py::test_resolver[empty]