diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 6151d2ff85cf9169abe73754a4d96094326017df..80c1fcbacfae23fae9588279be1ba92ab467eed3 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -141,8 +141,9 @@ export default {
         await followUser(this.user.id);
         this.$emit('follow');
       } catch (error) {
+        const message = error.response?.data?.message || I18N_ERROR_FOLLOW;
         createAlert({
-          message: I18N_ERROR_FOLLOW,
+          message,
           error,
           captureError: true,
         });
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2ee9832547c35ce48d676ed42d5a95b5fca408dd..c35aa8e434643305261e588dd6902a9934989641 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -174,8 +174,9 @@ def exists
   end
 
   def follow
-    current_user.follow(user)
+    followee = current_user.follow(user)
 
+    flash[:alert] = followee.errors.full_messages.join(', ') if followee&.errors&.any?
     redirect_path = referer_path(request) || @user
 
     redirect_to redirect_path
diff --git a/app/models/user.rb b/app/models/user.rb
index e1186abb6c17c78ebbac137e9e130bcd9d7662cc..6349883fd2852181c8110c13fe89014408c5409b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1644,8 +1644,9 @@ def follow(user)
     begin
       followee = Users::UserFollowUser.create(follower_id: self.id, followee_id: user.id)
       self.followees.reset if followee.persisted?
+      followee
     rescue ActiveRecord::RecordNotUnique
-      false
+      nil
     end
   end
 
diff --git a/app/models/users/user_follow_user.rb b/app/models/users/user_follow_user.rb
index a94239a746c380e920db4ad1d5c7ec2ffcb9e62f..5a82a81364aa7e3d87011a8360de0fb84f93727f 100644
--- a/app/models/users/user_follow_user.rb
+++ b/app/models/users/user_follow_user.rb
@@ -1,7 +1,22 @@
 # frozen_string_literal: true
 module Users
   class UserFollowUser < ApplicationRecord
+    MAX_FOLLOWEE_LIMIT = 300
+
     belongs_to :follower, class_name: 'User'
     belongs_to :followee, class_name: 'User'
+
+    validate :max_follow_limit
+
+    private
+
+    def max_follow_limit
+      followee_count = self.class.where(follower_id: follower_id).limit(MAX_FOLLOWEE_LIMIT).count
+      return if followee_count < MAX_FOLLOWEE_LIMIT
+
+      errors.add(:base, format(
+                          _("You can't follow more than %{limit} users. To follow more users, unfollow some others."),
+                          limit: MAX_FOLLOWEE_LIMIT))
+    end
   end
 end
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 7cee2b2bb0a3bce5d5c2af422c679b7e1a4b66ff..50d6bd90dcdf8230c9f06eb5cd34c0028ecd457f 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -336,6 +336,9 @@ GitLab tracks user contribution activity. You can follow or unfollow other users
 - The small popover that appears when you hover over a user's name ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76050)
   in GitLab 15.0).
 
+In [GitLab 15.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/360755),
+the maximum number of users you can follow is 300.
+
 To view a user's activity in a top-level Activity view:
 
 1. From a user's profile, select **Follow**.
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 3a2bbc3fb321c1ba9aa514191042cd10078e2e35..0654c6540f6bd506e1eb0234b09ee3f02e15447a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -189,7 +189,10 @@ def reorder_users(users)
         user = find_user(params[:id])
         not_found!('User') unless user
 
-        if current_user.follow(user)
+        followee = current_user.follow(user)
+        if followee&.errors&.any?
+          render_api_error!(followee.errors.full_messages.join(', '), 400)
+        elsif followee&.persisted?
           present user, with: Entities::UserBasic
         else
           not_modified!
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c10e9eaba39537ce5ec35fcdb997c22c302ab113..09898b49bf222aa71cd76a12194b390294918a91 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -46011,6 +46011,9 @@ msgstr ""
 msgid "You can't add any more, but you can manage your existing members, for example, by removing inactive members and replacing them with new members. To get more members an owner of the group can start a trial or upgrade to a paid tier."
 msgstr ""
 
+msgid "You can't follow more than %{limit} users. To follow more users, unfollow some others."
+msgstr ""
+
 msgid "You cannot %{action} %{state} users."
 msgstr ""
 
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index e47fc518b2351d44d1f259bdad0e9194b8da2313..f6316af6ad8f50c1a28d9e79f516bba8975d8349 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -8,7 +8,9 @@ import {
   I18N_USER_BLOCKED,
   I18N_USER_LEARN,
   I18N_USER_FOLLOW,
+  I18N_ERROR_FOLLOW,
   I18N_USER_UNFOLLOW,
+  I18N_ERROR_UNFOLLOW,
 } from '~/vue_shared/components/user_popover/constants';
 import axios from '~/lib/utils/axios_utils';
 import { createAlert } from '~/flash';
@@ -379,27 +381,49 @@ describe('User Popover Component', () => {
       itTracksToggleFollowButtonClick('follow_from_user_popover');
 
       describe('when an error occurs', () => {
-        beforeEach(() => {
-          followUser.mockRejectedValue({});
+        describe('api send error message', () => {
+          const mockedMessage = sprintf(I18N_ERROR_UNFOLLOW, { limit: 300 });
+          const apiResponse = { response: { data: { message: mockedMessage } } };
 
-          findToggleFollowButton().trigger('click');
-        });
+          beforeEach(() => {
+            followUser.mockRejectedValue(apiResponse);
+            findToggleFollowButton().trigger('click');
+          });
 
-        it('shows an error message', async () => {
-          await axios.waitForAll();
+          it('show an error message from api response', async () => {
+            await axios.waitForAll();
 
-          expect(createAlert).toHaveBeenCalledWith({
-            message: 'An error occurred while trying to follow this user, please try again.',
-            error: {},
-            captureError: true,
+            expect(createAlert).toHaveBeenCalledWith({
+              message: mockedMessage,
+              error: apiResponse,
+              captureError: true,
+            });
           });
         });
 
-        it('emits no events', async () => {
-          await axios.waitForAll();
+        describe('api did not send error message', () => {
+          beforeEach(() => {
+            followUser.mockRejectedValue({});
 
-          expect(wrapper.emitted().follow).toBeUndefined();
-          expect(wrapper.emitted().unfollow).toBeUndefined();
+            findToggleFollowButton().trigger('click');
+          });
+
+          it('shows an error message', async () => {
+            await axios.waitForAll();
+
+            expect(createAlert).toHaveBeenCalledWith({
+              message: I18N_ERROR_FOLLOW,
+              error: {},
+              captureError: true,
+            });
+          });
+
+          it('emits no events', async () => {
+            await axios.waitForAll();
+
+            expect(wrapper.emitted().follow).toBeUndefined();
+            expect(wrapper.emitted().unfollow).toBeUndefined();
+          });
         });
       });
     });
@@ -438,7 +462,7 @@ describe('User Popover Component', () => {
 
         it('shows an error message', () => {
           expect(createAlert).toHaveBeenCalledWith({
-            message: 'An error occurred while trying to unfollow this user, please try again.',
+            message: I18N_ERROR_UNFOLLOW,
             error: {},
             captureError: true,
           });
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a32eea7dfc9d36f8df9928f401b15843d2b89095..a163441590f5035f4b014c4dd5d635610edd61ad 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3723,6 +3723,22 @@ def login_method(login)
 
       expect(user.followees).to be_empty
     end
+
+    it 'does not follow if max followee limit is reached' do
+      stub_const('Users::UserFollowUser::MAX_FOLLOWEE_LIMIT', 2)
+
+      user = create(:user)
+      Users::UserFollowUser::MAX_FOLLOWEE_LIMIT.times { user.follow(create(:user)) }
+
+      followee = create(:user)
+      user_follow_user = user.follow(followee)
+
+      expect(user_follow_user).not_to be_persisted
+      expected_message = format(_("You can't follow more than %{limit} users. To follow more users, unfollow some others."), limit: Users::UserFollowUser::MAX_FOLLOWEE_LIMIT)
+      expect(user_follow_user.errors.messages[:base].first).to eq(expected_message)
+
+      expect(user.following?(followee)).to be_falsey
+    end
   end
 
   describe '#unfollow' do
@@ -3751,6 +3767,18 @@ def login_method(login)
 
       expect(user.followees).to be_empty
     end
+
+    it 'unfollows when over followee limit' do
+      user = create(:user)
+
+      followees = create_list(:user, 4)
+      followees.each { |f| expect(user.follow(f)).to be_truthy }
+
+      stub_const('Users::UserFollowUser::MAX_FOLLOWEE_LIMIT', followees.length - 2)
+
+      expect(user.unfollow(followees.first)).to be_truthy
+      expect(user.following?(followees.first)).to be_falsey
+    end
   end
 
   describe '#notification_email_or_default' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 96e23337411995fc5481a2e7cfe44b0d8532a447..7acbd12cae9df9ef1d74d4dd8d66bb92ebba8ea2 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -940,6 +940,17 @@
         expect(user.followees).to contain_exactly(followee)
         expect(response).to have_gitlab_http_status(:created)
       end
+
+      it 'alerts and not follow when over followee limit' do
+        stub_const('Users::UserFollowUser::MAX_FOLLOWEE_LIMIT', 2)
+        Users::UserFollowUser::MAX_FOLLOWEE_LIMIT.times { user.follow(create(:user)) }
+
+        post api("/users/#{followee.id}/follow", user)
+        expect(response).to have_gitlab_http_status(:bad_request)
+        expected_message = format(_("You can't follow more than %{limit} users. To follow more users, unfollow some others."), limit: Users::UserFollowUser::MAX_FOLLOWEE_LIMIT)
+        expect(json_response['message']).to eq(expected_message)
+        expect(user.following?(followee)).to be_falsey
+      end
     end
 
     context 'on a followed user' do
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index 42f1439211713322685dd4a7da0a2db1d3c8b693..e78d4cc326e07d680ae7dbf9d60600751f1fe3f7 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/spec/requests/users_controller_spec.rb
@@ -828,6 +828,26 @@ def create_note_event
     end
   end
 
+  describe 'POST #follow' do
+    context 'when over followee limit' do
+      before do
+        stub_const('Users::UserFollowUser::MAX_FOLLOWEE_LIMIT', 2)
+        sign_in(user)
+      end
+
+      it 'alerts and not follow' do
+        Users::UserFollowUser::MAX_FOLLOWEE_LIMIT.times { user.follow(create(:user)) }
+
+        post user_follow_url(username: public_user.username)
+        expect(response).to be_redirect
+
+        expected_message = format(_("You can't follow more than %{limit} users. To follow more users, unfollow some others."), limit: Users::UserFollowUser::MAX_FOLLOWEE_LIMIT)
+        expect(flash[:alert]).to eq(expected_message)
+        expect(user).not_to be_following(public_user)
+      end
+    end
+  end
+
   context 'token authentication' do
     it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
       let(:url) { user_url(user, format: :atom) }