diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js
index 0e58ea2cb4e024..9f636f31a3d629 100644
--- a/lib/internal/util/comparisons.js
+++ b/lib/internal/util/comparisons.js
@@ -5,32 +5,52 @@ const { isArrayBufferView } = require('internal/util/types');
 const { internalBinding } = require('internal/bootstrap/loaders');
 const { isDate, isMap, isRegExp, isSet } = internalBinding('types');
 
-function objectToString(o) {
-  return Object.prototype.toString.call(o);
+const ReflectApply = Reflect.apply;
+
+function uncurryThis(func) {
+  return (thisArg, ...args) => ReflectApply(func, thisArg, args);
 }
 
+const kStrict = true;
+const kLoose = false;
+
+const kNoIterator = 0;
+const kIsArray = 1;
+const kIsSet = 2;
+const kIsMap = 3;
+
+const objectToString = uncurryThis(Object.prototype.toString);
+const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+const propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable);
+
+const objectKeys = Object.keys;
+const getPrototypeOf = Object.getPrototypeOf;
+const getOwnPropertySymbols = Object.getOwnPropertySymbols;
+const objectIs = Object.is;
+const numberIsNaN = Number.isNaN;
+
 // Check if they have the same source and flags
 function areSimilarRegExps(a, b) {
   return a.source === b.source && a.flags === b.flags;
 }
 
-// For small buffers it's faster to compare the buffer in a loop. The c++
-// barrier including the Uint8Array operation takes the advantage of the faster
-// binary compare otherwise. The break even point was at about 300 characters.
-function areSimilarTypedArrays(a, b, max) {
-  const len = a.byteLength;
-  if (len !== b.byteLength) {
+function areSimilarFloatArrays(a, b) {
+  if (a.byteLength !== b.byteLength) {
     return false;
   }
-  if (len < max) {
-    for (var offset = 0; offset < len; offset++) {
-      if (a[offset] !== b[offset]) {
-        return false;
-      }
+  for (var offset = 0; offset < a.byteLength; offset++) {
+    if (a[offset] !== b[offset]) {
+      return false;
     }
-    return true;
   }
-  return compare(new Uint8Array(a.buffer, a.byteOffset, len),
+  return true;
+}
+
+function areSimilarTypedArrays(a, b) {
+  if (a.byteLength !== b.byteLength) {
+    return false;
+  }
+  return compare(new Uint8Array(a.buffer, a.byteOffset, a.byteLength),
                  new Uint8Array(b.buffer, b.byteOffset, b.byteLength)) === 0;
 }
 
@@ -66,8 +86,8 @@ function isObjectOrArrayTag(tag) {
 // b) The same prototypes.
 function strictDeepEqual(val1, val2, memos) {
   if (typeof val1 !== 'object') {
-    return typeof val1 === 'number' && Number.isNaN(val1) &&
-      Number.isNaN(val2);
+    return typeof val1 === 'number' && numberIsNaN(val1) &&
+      numberIsNaN(val2);
   }
   if (typeof val2 !== 'object' || val1 === null || val2 === null) {
     return false;
@@ -78,21 +98,38 @@ function strictDeepEqual(val1, val2, memos) {
   if (val1Tag !== val2Tag) {
     return false;
   }
-  if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {
+  if (getPrototypeOf(val1) !== getPrototypeOf(val2)) {
     return false;
   }
   if (val1Tag === '[object Array]') {
     // Check for sparse arrays and general fast path
-    if (val1.length !== val2.length)
+    if (val1.length !== val2.length) {
+      return false;
+    }
+    const keys = objectKeys(val1);
+    if (keys.length !== objectKeys(val2).length) {
       return false;
-    // Skip testing the part below and continue with the keyCheck.
-    return keyCheck(val1, val2, true, memos);
+    }
+    // Fast path for non sparse arrays (no key comparison for indices
+    // properties).
+    // See https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys
+    if (val1.length === keys.length) {
+      if (keys.length === 0 || keys[val1.length - 1] === `${val1.length - 1}`) {
+        return keyCheck(val1, val2, kStrict, memos, kIsArray, []);
+      }
+    } else if (keys.length > val1.length &&
+               keys[val1.length - 1] === `${val1.length - 1}`) {
+      const minimalKeys = keys.slice(val1.length);
+      return keyCheck(val1, val2, kStrict, memos, kIsArray, minimalKeys);
+    }
+    // Only set this to kIsArray in case the array is not sparse!
+    return keyCheck(val1, val2, kStrict, memos, kNoIterator, keys);
   }
   if (val1Tag === '[object Object]') {
-    // Skip testing the part below and continue with the keyCheck.
-    return keyCheck(val1, val2, true, memos);
+    return keyCheck(val1, val2, kStrict, memos, kNoIterator);
   }
   if (isDate(val1)) {
+    // TODO: Make these safe.
     if (val1.getTime() !== val2.getTime()) {
       return false;
     }
@@ -108,35 +145,52 @@ function strictDeepEqual(val1, val2, memos) {
       return false;
     }
   } else if (isArrayBufferView(val1)) {
-    if (!areSimilarTypedArrays(val1, val2,
-                               isFloatTypedArrayTag(val1Tag) ? 0 : 300)) {
+    if (!areSimilarTypedArrays(val1, val2)) {
       return false;
     }
     // Buffer.compare returns true, so val1.length === val2.length
-    // if they both only contain numeric keys, we don't need to exam further
-    return keyCheck(val1, val2, true, memos, val1.length,
-                    val2.length);
+    // if they both only contain numeric keys, we don't need to exam further.
+    const keys = objectKeys(val1);
+    if (keys.length !== objectKeys(val2).length) {
+      return false;
+    }
+    if (keys.length === val1.length) {
+      return keyCheck(val1, val2, kStrict, memos, kNoIterator, []);
+    }
+    // Only compare the special keys.
+    const minimalKeys = keys.slice(val1.length);
+    return keyCheck(val1, val2, kStrict, memos, kNoIterator, minimalKeys);
+  } else if (isSet(val1)) {
+    if (!isSet(val2) || val1.size !== val2.size) {
+      return false;
+    }
+    return keyCheck(val1, val2, kStrict, memos, kIsSet);
+  } else if (isMap(val1)) {
+    if (!isMap(val2) || val1.size !== val2.size) {
+      return false;
+    }
+    return keyCheck(val1, val2, kStrict, memos, kIsMap);
+  // TODO: Make the valueOf checks safe.
   } else if (typeof val1.valueOf === 'function') {
     const val1Value = val1.valueOf();
-    // Note: Boxed string keys are going to be compared again by Object.keys
     if (val1Value !== val1) {
       if (typeof val2.valueOf !== 'function') {
         return false;
       }
       if (!innerDeepEqual(val1Value, val2.valueOf(), true))
         return false;
-      // Fast path for boxed primitives
-      var lengthval1 = 0;
-      var lengthval2 = 0;
+      // Fast path for boxed primitive strings.
       if (typeof val1Value === 'string') {
-        lengthval1 = val1.length;
-        lengthval2 = val2.length;
+        const keys = objectKeys(val1);
+        if (keys.length !== objectKeys(val2).length) {
+          return false;
+        }
+        const minimalKeys = keys.slice(val1.length);
+        return keyCheck(val1, val2, kStrict, memos, kNoIterator, minimalKeys);
       }
-      return keyCheck(val1, val2, true, memos, lengthval1,
-                      lengthval2);
     }
   }
-  return keyCheck(val1, val2, true, memos);
+  return keyCheck(val1, val2, kStrict, memos, kNoIterator);
 }
 
 function looseDeepEqual(val1, val2, memos) {
@@ -150,33 +204,54 @@ function looseDeepEqual(val1, val2, memos) {
   if (val2 === null || typeof val2 !== 'object') {
     return false;
   }
-  if (isDate(val1) && isDate(val2)) {
-    return val1.getTime() === val2.getTime();
-  }
-  if (isRegExp(val1) && isRegExp(val2)) {
-    return areSimilarRegExps(val1, val2);
-  }
-  if (val1 instanceof Error && val2 instanceof Error) {
-    if (val1.message !== val2.message || val1.name !== val2.name)
-      return false;
-  }
   const val1Tag = objectToString(val1);
   const val2Tag = objectToString(val2);
   if (val1Tag === val2Tag) {
-    if (!isObjectOrArrayTag(val1Tag) && isArrayBufferView(val1)) {
-      return areSimilarTypedArrays(val1, val2,
-                                   isFloatTypedArrayTag(val1Tag) ?
-                                     Infinity : 300);
+    if (isObjectOrArrayTag(val1Tag)) {
+      return keyCheck(val1, val2, kLoose, memos, kNoIterator);
+    }
+    if (isArrayBufferView(val1)) {
+      if (isFloatTypedArrayTag(val1Tag)) {
+        return areSimilarFloatArrays(val1, val2);
+      }
+      return areSimilarTypedArrays(val1, val2);
+    }
+    if (isDate(val1) && isDate(val2)) {
+      return val1.getTime() === val2.getTime();
+    }
+    if (isRegExp(val1) && isRegExp(val2)) {
+      return areSimilarRegExps(val1, val2);
+    }
+    if (val1 instanceof Error && val2 instanceof Error) {
+      if (val1.message !== val2.message || val1.name !== val2.name)
+        return false;
     }
   // Ensure reflexivity of deepEqual with `arguments` objects.
   // See https://github.com/nodejs/node-v0.x-archive/pull/7178
   } else if (isArguments(val1Tag) || isArguments(val2Tag)) {
     return false;
   }
-  return keyCheck(val1, val2, false, memos);
+  if (isSet(val1)) {
+    if (!isSet(val2) || val1.size !== val2.size) {
+      return false;
+    }
+    return keyCheck(val1, val2, kLoose, memos, kIsSet);
+  } else if (isMap(val1)) {
+    if (!isMap(val2) || val1.size !== val2.size) {
+      return false;
+    }
+    return keyCheck(val1, val2, kLoose, memos, kIsMap);
+  } else if (isSet(val2) || isMap(val2)) {
+    return false;
+  }
+  return keyCheck(val1, val2, kLoose, memos, kNoIterator);
+}
+
+function getEnumerables(val, keys) {
+  return keys.filter((k) => propertyIsEnumerable(val, k));
 }
 
-function keyCheck(val1, val2, strict, memos, lengthA, lengthB) {
+function keyCheck(val1, val2, strict, memos, iterationType, aKeys) {
   // For all remaining Object pairs, including Array, objects and Maps,
   // equivalence is determined by having:
   // a) The same number of owned enumerable properties
@@ -184,50 +259,59 @@ function keyCheck(val1, val2, strict, memos, lengthA, lengthB) {
   // c) Equivalent values for every corresponding key/index
   // d) For Sets and Maps, equal contents
   // Note: this accounts for both named and indexed properties on Arrays.
-  var aKeys = Object.keys(val1);
-  var bKeys = Object.keys(val2);
-  var i;
+  if (arguments.length === 5) {
+    aKeys = objectKeys(val1);
+    const bKeys = objectKeys(val2);
 
-  // The pair must have the same number of owned properties.
-  if (aKeys.length !== bKeys.length)
-    return false;
+    // The pair must have the same number of owned properties.
+    if (aKeys.length !== bKeys.length) {
+      return false;
+    }
+  }
+
+  // Cheap key test
+  let i = 0;
+  for (; i < aKeys.length; i++) {
+    if (!hasOwnProperty(val2, aKeys[i])) {
+      return false;
+    }
+  }
 
   if (strict) {
-    var symbolKeysA = Object.getOwnPropertySymbols(val1);
-    var symbolKeysB = Object.getOwnPropertySymbols(val2);
+    const symbolKeysA = getOwnPropertySymbols(val1);
     if (symbolKeysA.length !== 0) {
-      symbolKeysA = symbolKeysA.filter((k) =>
-        propertyIsEnumerable.call(val1, k));
-      symbolKeysB = symbolKeysB.filter((k) =>
-        propertyIsEnumerable.call(val2, k));
-      if (symbolKeysA.length !== symbolKeysB.length)
+      let count = 0;
+      for (i = 0; i < symbolKeysA.length; i++) {
+        const key = symbolKeysA[i];
+        if (propertyIsEnumerable(val1, key)) {
+          if (!propertyIsEnumerable(val2, key)) {
+            return false;
+          }
+          aKeys.push(key);
+          count++;
+        } else if (propertyIsEnumerable(val2, key)) {
+          return false;
+        }
+      }
+      const symbolKeysB = getOwnPropertySymbols(val2);
+      if (symbolKeysA.length !== symbolKeysB.length &&
+          getEnumerables(val2, symbolKeysB).length !== count) {
         return false;
-    } else if (symbolKeysB.length !== 0 && symbolKeysB.filter((k) =>
-      propertyIsEnumerable.call(val2, k)).length !== 0) {
-      return false;
-    }
-    if (lengthA !== undefined) {
-      if (aKeys.length !== lengthA || bKeys.length !== lengthB)
+      }
+    } else {
+      const symbolKeysB = getOwnPropertySymbols(val2);
+      if (symbolKeysB.length !== 0 &&
+          getEnumerables(val2, symbolKeysB).length !== 0) {
         return false;
-      if (symbolKeysA.length === 0)
-        return true;
-      aKeys = [];
-      bKeys = [];
-    }
-    if (symbolKeysA.length !== 0) {
-      aKeys.push(...symbolKeysA);
-      bKeys.push(...symbolKeysB);
+      }
     }
   }
 
-  // Cheap key test:
-  const keys = {};
-  for (i = 0; i < aKeys.length; i++) {
-    keys[aKeys[i]] = true;
-  }
-  for (i = 0; i < aKeys.length; i++) {
-    if (keys[bKeys[i]] === undefined)
-      return false;
+  if (aKeys.length === 0 &&
+      (iterationType === kNoIterator ||
+        iterationType === kIsArray && val1.length === 0 ||
+      val1.size === 0)) {
+    return true;
   }
 
   // Use memos to handle cycles.
@@ -254,7 +338,7 @@ function keyCheck(val1, val2, strict, memos, lengthA, lengthB) {
   memos.val1.set(val1, memos.position);
   memos.val2.set(val2, memos.position);
 
-  const areEq = objEquiv(val1, val2, strict, aKeys, memos);
+  const areEq = objEquiv(val1, val2, strict, aKeys, memos, iterationType);
 
   memos.val1.delete(val1);
   memos.val2.delete(val2);
@@ -267,7 +351,7 @@ function innerDeepEqual(val1, val2, strict, memos) {
   if (val1 === val2) {
     if (val1 !== 0)
       return true;
-    return strict ? Object.is(val1, val2) : true;
+    return strict ? objectIs(val1, val2) : true;
   }
 
   // Check more closely if val1 and val2 are equal.
@@ -297,7 +381,7 @@ function setHasLoosePrim(a, b, val) {
   if (altValues === undefined)
     return false;
 
-  var matches = 1;
+  let matches = 1;
   for (var i = 0; i < altValues.length; i++) {
     if (b.has(altValues[i])) {
       matches--;
@@ -310,19 +394,9 @@ function setHasLoosePrim(a, b, val) {
 }
 
 function setEquiv(a, b, strict, memo) {
-  // This code currently returns false for this pair of sets:
-  //   assert.deepEqual(new Set(['1', 1]), new Set([1]))
-  //
-  // In theory, all the items in the first set have a corresponding == value in
-  // the second set, but the sets have different sizes. Its a silly case,
-  // and more evidence that deepStrictEqual should always be preferred over
-  // deepEqual.
-  if (a.size !== b.size)
-    return false;
-
   // This is a lazily initiated Set of entries which have to be compared
   // pairwise.
-  var set = null;
+  let set = null;
   for (const val of a) {
     // Note: Checking for the objects first improves the performance for object
     // heavy sets but it is a minor slow down for primitives. As they are fast
@@ -405,7 +479,7 @@ function mapHasLoosePrim(a, b, key1, memo, item1, item2) {
   const setA = new Set();
   const setB = new Set();
 
-  var keyCount = 1;
+  let keyCount = 1;
 
   setA.add(item1);
   if (b.has(key1)) {
@@ -454,10 +528,7 @@ function mapHasEqualEntry(set, map, key1, item1, strict, memo) {
 }
 
 function mapEquiv(a, b, strict, memo) {
-  if (a.size !== b.size)
-    return false;
-
-  var set = null;
+  let set = null;
 
   for (const [key, item1] of a) {
     if (typeof key === 'object' && key !== null) {
@@ -492,35 +563,44 @@ function mapEquiv(a, b, strict, memo) {
   return true;
 }
 
-function objEquiv(a, b, strict, keys, memos) {
+function objEquiv(a, b, strict, keys, memos, iterationType) {
   // Sets and maps don't have their entries accessible via normal object
   // properties.
-  if (isSet(a)) {
-    if (!isSet(b) || !setEquiv(a, b, strict, memos))
+  let i = 0;
+
+  if (iterationType === kIsSet) {
+    if (!setEquiv(a, b, strict, memos)) {
       return false;
-  } else if (isMap(a)) {
-    if (!isMap(b) || !mapEquiv(a, b, strict, memos))
+    }
+  } else if (iterationType === kIsMap) {
+    if (!mapEquiv(a, b, strict, memos)) {
       return false;
-  } else if (isSet(b) || isMap(b)) {
-    return false;
+    }
+  } else if (iterationType === kIsArray) {
+    for (; i < a.length; i++) {
+      if (!innerDeepEqual(a[i], b[i], strict, memos)) {
+        return false;
+      }
+    }
   }
 
   // The pair must have equivalent values for every corresponding key.
   // Possibly expensive deep test:
-  for (var i = 0; i < keys.length; i++) {
+  for (i = 0; i < keys.length; i++) {
     const key = keys[i];
-    if (!innerDeepEqual(a[key], b[key], strict, memos))
+    if (!innerDeepEqual(a[key], b[key], strict, memos)) {
       return false;
+    }
   }
   return true;
 }
 
 function isDeepEqual(val1, val2) {
-  return innerDeepEqual(val1, val2, false);
+  return innerDeepEqual(val1, val2, kLoose);
 }
 
 function isDeepStrictEqual(val1, val2) {
-  return innerDeepEqual(val1, val2, true);
+  return innerDeepEqual(val1, val2, kStrict);
 }
 
 module.exports = {
diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js
index 12876232ed141d..2360a935b73776 100644
--- a/test/parallel/test-assert-deep.js
+++ b/test/parallel/test-assert-deep.js
@@ -901,3 +901,10 @@ assert.deepStrictEqual(obj1, obj2);
   a.valueOf = undefined;
   assertNotDeepOrStrict(a, new String(1));
 }
+
+// Basic array out of bounds check.
+{
+  const arr = [1, 2, 3];
+  arr[2 ** 32] = true;
+  assertNotDeepOrStrict(arr, [1, 2, 3]);
+}