diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def
index 3e605b6a5df..3d4becb4e57 100644
--- a/include/hermes/VM/NativeFunctions.def
+++ b/include/hermes/VM/NativeFunctions.def
@@ -199,6 +199,7 @@ NATIVE_FUNCTION(jsonParse)
 NATIVE_FUNCTION(jsonStringify)
 NATIVE_FUNCTION(mapConstructor)
 NATIVE_FUNCTION(mapIteratorPrototypeNext)
+NATIVE_FUNCTION(mapGroupBy)
 NATIVE_FUNCTION(mapPrototypeClear)
 NATIVE_FUNCTION(mapPrototypeDelete)
 NATIVE_FUNCTION(mapPrototypeEntries)
diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def
index 6242697af14..3d354bb29a4 100644
--- a/include/hermes/VM/PredefinedStrings.def
+++ b/include/hermes/VM/PredefinedStrings.def
@@ -73,6 +73,7 @@ STR(getOwnPropertyDescriptor, "getOwnPropertyDescriptor")
 STR(getOwnPropertyDescriptors, "getOwnPropertyDescriptors")
 STR(getOwnPropertyNames, "getOwnPropertyNames")
 STR(getOwnPropertySymbols, "getOwnPropertySymbols")
+STR(groupBy, "groupBy")
 STR(seal, "seal")
 STR(freeze, "freeze")
 STR(fromEntries, "fromEntries")
diff --git a/lib/VM/JSLib/Map.cpp b/lib/VM/JSLib/Map.cpp
index e0aa330aa0e..93716269771 100644
--- a/lib/VM/JSLib/Map.cpp
+++ b/lib/VM/JSLib/Map.cpp
@@ -133,6 +133,15 @@ Handle<JSObject> createMapConstructor(Runtime &runtime) {
       0,
       CellKind::JSMapKind);
 
+  // Map.xxx static functions
+  defineMethod(
+      runtime,
+      cons,
+      Predefined::getSymbolID(Predefined::groupBy),
+      nullptr,
+      mapGroupBy,
+      2);
+
   return cons;
 }
 
@@ -345,5 +354,116 @@ mapIteratorPrototypeNext(void *, Runtime &runtime, NativeArgs args) {
   }
   return *cr;
 }
+
+CallResult<HermesValue>
+mapGroupBy(void *, Runtime &runtime, NativeArgs args) {
+  GCScope gcScope{runtime};
+
+  Handle<> items = args.getArgHandle(0);
+  Handle<Callable> grouperFunc = args.dyncastArg<Callable>(1);
+
+  // 1. Perform ? RequireObjectCoercible(items).
+  if (LLVM_UNLIKELY(items->isNull() || items->isUndefined())) {
+    return runtime.raiseTypeError(
+        "groupBy first argument is not coercible to Object");
+  }
+
+  // 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
+  if (LLVM_UNLIKELY(!grouperFunc)) {
+    return runtime.raiseTypeError(
+        "groupBy second argument must be callable");
+  }
+
+  // 4. Let iteratorRecord be ? GetIterator(items, sync).
+  auto iteratorRecordRes = getIterator(runtime, items);
+  if (LLVM_UNLIKELY(iteratorRecordRes == ExecutionStatus::EXCEPTION)) {
+    return ExecutionStatus::EXCEPTION;
+  }
+  auto iteratorRecord = *iteratorRecordRes;
+
+  // 5. Let k be 0.
+  size_t k = 0;
+
+  auto O = runtime.makeHandle(JSMap::create(runtime, Handle<JSObject>::vmcast(&runtime.mapPrototype)));
+  JSMap::initializeStorage(O, runtime);
+  Handle<HermesValue> callbackThis = runtime.makeHandle(HermesValue::encodeUndefinedValue());
+
+  MutableHandle<> objectArrayHandle{runtime};
+  MutableHandle<HermesValue> groupKey{runtime};
+  MutableHandle<JSArray> targetGroupArray{runtime};
+  MutableHandle<> targetGroupIndex{runtime};
+  MutableHandle<JSObject> tmpHandle{runtime};
+  MutableHandle<> valueHandle{runtime};
+  auto marker = gcScope.createMarker();
+
+  // 6. Repeat,
+  // Check the length of the array after every iteration,
+  // to allow for the fact that the length could be modified during iteration.
+  for (;; k++) {
+    CallResult<Handle<JSObject>> nextRes = iteratorStep(runtime, iteratorRecord);
+    if (LLVM_UNLIKELY(nextRes == ExecutionStatus::EXCEPTION)) {
+      return ExecutionStatus::EXCEPTION;
+    }
+
+    if (!*nextRes) {
+      // Done with iteration.
+      break;
+    }
+
+    tmpHandle = vmcast<JSObject>(nextRes->getHermesValue());
+    auto nextValueRes = JSObject::getNamed_RJS(
+        tmpHandle, runtime, Predefined::getSymbolID(Predefined::value));
+    if (LLVM_UNLIKELY(nextValueRes == ExecutionStatus::EXCEPTION)) {
+      return ExecutionStatus::EXCEPTION;
+    }
+    valueHandle = runtime.makeHandle(std::move(*nextValueRes));
+
+    // compute key for current element
+    auto keyRes = Callable::executeCall2(grouperFunc, runtime, callbackThis, valueHandle.getHermesValue(), HermesValue::encodeTrustedNumberValue(k));
+    if (LLVM_UNLIKELY(keyRes == ExecutionStatus::EXCEPTION)) {
+      return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
+    }
+
+    groupKey = runtime.makeHandle(std::move(*keyRes));
+
+    // make it a property key
+    auto propertyKeyRes = toPropertyKey(runtime, groupKey);
+    if (LLVM_UNLIKELY(propertyKeyRes == ExecutionStatus::EXCEPTION)) {
+      return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
+    }
+    auto propertyKey = *propertyKeyRes;
+
+    // new group key, no array in object yet so create it
+    if (!JSMap::hasKey(O, runtime, propertyKey)) {
+      auto targetGroupArrayRes = JSArray::create(runtime, 0, 0);
+      if (LLVM_UNLIKELY(targetGroupArrayRes == ExecutionStatus::EXCEPTION)) {
+        return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
+      }
+
+      targetGroupArray = std::move(*targetGroupArrayRes);
+
+      // group already created, get the array
+      JSMap::addValue(O, runtime, propertyKey, targetGroupArray);
+    } else {
+      objectArrayHandle = runtime.makeHandle(std::move(JSMap::getValue(O, runtime, propertyKey)));
+      targetGroupArray = Handle<JSArray>::dyn_vmcast(objectArrayHandle);
+    }
+
+    targetGroupIndex = HermesValue::encodeTrustedNumberValue(JSArray::getLength(*targetGroupArray, runtime));
+
+    if (LLVM_UNLIKELY(
+            JSObject::putComputed_RJS(
+                targetGroupArray, runtime,
+                targetGroupIndex, valueHandle, PropOpFlags().plusThrowOnError()) ==
+            ExecutionStatus::EXCEPTION)) {
+      return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
+    }
+
+    gcScope.flushToMarker(marker);
+  }
+
+  // 8. Return O.
+  return O.getHermesValue();
+}
 } // namespace vm
 } // namespace hermes
diff --git a/test/hermes/map.js b/test/hermes/map.js
index ded1ce7f5bf..e8aeea787ed 100644
--- a/test/hermes/map.js
+++ b/test/hermes/map.js
@@ -363,3 +363,49 @@ testClear(o1, o2, o3);
 testIteration();
 testForEach();
 testZero();
+
+
+print("Map.groupBy");
+//CHECK-LABEL: Map.groupBy
+
+var map = Map.groupBy([1, 2, 3, 4, 5, 6, 7], function(key) { return key % 2; });
+print(map.get('0'))
+//CHECK-NEXT: 2,4,6
+print(map.get('1'))
+//CHECK-NEXT: 1,3,5,7
+print(map.size);
+//CHECK-NEXT: 2
+
+var map = Map.groupBy([1, 2, 3, 4, 5, 6, 7], function(key) { return key % 3; });
+print(map.get('0'))
+//CHECK-NEXT: 3,6
+print(map.get('1'))
+//CHECK-NEXT: 1,4,7
+print(map.get('2'))
+//CHECK-NEXT: 2,5
+print(map.size);
+//CHECK-NEXT: 3
+
+var map = Map.groupBy([1, 2, 3, 4, 5, 6, 7], function(key) { return key % 1; });
+print(map.get('0'))
+//CHECK-NEXT: 1,2,3,4,5,6,7
+print(map.size);
+//CHECK-NEXT: 1
+
+var map = Map.groupBy([1, 2, 3, 4], function(key) { return key; });
+print(map.size);
+//CHECK-NEXT: 4
+
+var team = [
+  {name: 'Peter', age: 15},
+  {name: 'Mike', age: 20},
+  {name: 'John', age: 22},
+];
+
+var map = Map.groupBy(team, function(p) { return p.age < 18 ? 'underage' : 'adult'; });
+print(map.size);
+//CHECK-NEXT: 2
+print(JSON.stringify(map.get('underage')))
+//CHECK-NEXT: [{"name":"Peter","age":15}]
+print(JSON.stringify(map.get('adult')))
+//CHECK-NEXT: [{"name":"Mike","age":20},{"name":"John","age":22}]
\ No newline at end of file