diff --git a/src/autocomplete/QueryMatcher.ts b/src/autocomplete/QueryMatcher.ts
index 2c1899d813..7a0219e264 100644
--- a/src/autocomplete/QueryMatcher.ts
+++ b/src/autocomplete/QueryMatcher.ts
@@ -17,8 +17,6 @@ limitations under the License.
 */
 
 import _at from 'lodash/at';
-import _flatMap from 'lodash/flatMap';
-import _sortBy from 'lodash/sortBy';
 import _uniq from 'lodash/uniq';
 
 function stripDiacritics(str: string): string {
@@ -35,8 +33,9 @@ interface IOptions<T extends {}> {
 /**
  * Simple search matcher that matches any results with the query string anywhere
  * in the search string. Returns matches in the order the query string appears
- * in the search key, earliest first, then in the order the items appeared in
- * the source array.
+ * in the search key, earliest first, then in the order the search key appears
+ * in the provided array of keys, then in the order the items appeared in the
+ * source array.
  *
  * @param {Object[]} objects Initial list of objects. Equivalent to calling
  *     setObjects() after construction
@@ -49,7 +48,7 @@ export default class QueryMatcher<T extends Object> {
     private _options: IOptions<T>;
     private _keys: IOptions<T>["keys"];
     private _funcs: Required<IOptions<T>["funcs"]>;
-    private _items: Map<string, T[]>;
+    private _items: Map<string, {object: T, keyWeight: number}[]>;
 
     constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
         this._options = options;
@@ -85,13 +84,16 @@ export default class QueryMatcher<T extends Object> {
                 keyValues.push(f(object));
             }
 
-            for (const keyValue of keyValues) {
+            for (const [index, keyValue] of Object.entries(keyValues)) {
                 if (!keyValue) continue; // skip falsy keyValues
                 const key = stripDiacritics(keyValue).toLowerCase();
                 if (!this._items.has(key)) {
                     this._items.set(key, []);
                 }
-                this._items.get(key).push(object);
+                this._items.get(key).push({
+                    keyWeight: Number(index),
+                    object,
+                });
             }
         }
     }
@@ -104,32 +106,40 @@ export default class QueryMatcher<T extends Object> {
         if (query.length === 0) {
             return [];
         }
-        const results = [];
+        const matches = [];
         // Iterate through the map & check each key.
         // ES6 Map iteration order is defined to be insertion order, so results
         // here will come out in the order they were put in.
-        for (const key of this._items.keys()) {
+        for (const [key, candidates] of this._items.entries()) {
             let resultKey = key;
             if (this._options.shouldMatchWordsOnly) {
                 resultKey = resultKey.replace(/[^\w]/g, '');
             }
             const index = resultKey.indexOf(query);
             if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
-                results.push({key, index});
+                matches.push(
+                    ...candidates.map((candidate) => ({index, ...candidate}))
+                );
             }
         }
 
-        // Sort them by where the query appeared in the search key
-        // lodash sortBy is a stable sort, so results where the query
-        // appeared in the same place will retain their order with
-        // respect to each other.
-        const sortedResults = _sortBy(results, (candidate) => {
-            return candidate.index;
+        // Sort matches by where the query appeared in the search key, then by
+        // where the matched key appeared in the provided array of keys.
+        matches.sort((a, b) => {
+            if (a.index < b.index) {
+                return -1;
+            } else if (a.index === b.index) {
+                if (a.keyWeight < b.keyWeight) {
+                    return -1;
+                } else if (a.keyWeight === b.keyWeight) {
+                    return 0;
+                }
+            }
+
+            return 1;
         });
 
-        // Now map the keys to the result objects. Each result object is a list, so
-        // flatMap will flatten those lists out into a single list. Also remove any
-        // duplicates.
-        return _uniq(_flatMap(sortedResults, (candidate) => this._items.get(candidate.key)));
+        // Now map the keys to the result objects. Also remove any duplicates.
+        return _uniq(matches.map((match) => match.object));
     }
 }
diff --git a/test/autocomplete/QueryMatcher-test.js b/test/autocomplete/QueryMatcher-test.js
index 03f28eb984..2d0e10563b 100644
--- a/test/autocomplete/QueryMatcher-test.js
+++ b/test/autocomplete/QueryMatcher-test.js
@@ -81,7 +81,34 @@ describe('QueryMatcher', function() {
         expect(reverseResults[1].name).toBe('Victoria');
     });
 
-    it('Returns results with search string in same place in insertion order', function() {
+    it('Returns results with search string in same place according to key index', function() {
+        const objects = [
+            { name: "a", first: "hit", second: "miss", third: "miss" },
+            { name: "b", first: "miss", second: "hit", third: "miss" },
+            { name: "c", first: "miss", second: "miss", third: "hit" },
+        ];
+        const qm = new QueryMatcher(objects, {keys: ["second", "first", "third"]});
+        const results = qm.match('hit');
+
+        expect(results.length).toBe(3);
+        expect(results[0].name).toBe('b');
+        expect(results[1].name).toBe('a');
+        expect(results[2].name).toBe('c');
+
+
+        qm.setObjects(objects.slice().reverse());
+
+        const reverseResults = qm.match('hit');
+
+        // should still be in the same order: key index
+        // takes precedence over input order
+        expect(reverseResults.length).toBe(3);
+        expect(reverseResults[0].name).toBe('b');
+        expect(reverseResults[1].name).toBe('a');
+        expect(reverseResults[2].name).toBe('c');
+    });
+
+    it('Returns results with search string in same place and key in same place in insertion order', function() {
         const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
         const results = qm.match('Mel');
 
@@ -132,9 +159,9 @@ describe('QueryMatcher', function() {
 
         const results = qm.match('Emma');
         expect(results.length).toBe(3);
-        expect(results[0].name).toBe('Mel B');
-        expect(results[1].name).toBe('Mel C');
-        expect(results[2].name).toBe('Emma');
+        expect(results[0].name).toBe('Emma');
+        expect(results[1].name).toBe('Mel B');
+        expect(results[2].name).toBe('Mel C');
     });
 
     it('Matches words only by default', function() {