diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index eec55aa7bbecfafe183b7095cc59e84f075591a1..d1257e3ffac1bf3a166a068a958cb95bd38b9cbe 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -133,7 +133,6 @@ Gitlab/NamespacedClass:
     - 'app/models/commit_status.rb'
     - 'app/models/commit_user_mention.rb'
     - 'app/models/compare.rb'
-    - 'app/models/concerns/uniquify.rb'
     - 'app/models/container_expiration_policy.rb'
     - 'app/models/container_repository.rb'
     - 'app/models/context_commits_diff.rb'
diff --git a/.rubocop_todo/layout/space_in_lambda_literal.yml b/.rubocop_todo/layout/space_in_lambda_literal.yml
index 3abff1e8788ae9d8f344c2dbb04bb04ea4865024..362d9b20eb19380dda90e7bc7c67f92dc310c55e 100644
--- a/.rubocop_todo/layout/space_in_lambda_literal.yml
+++ b/.rubocop_todo/layout/space_in_lambda_literal.yml
@@ -400,7 +400,6 @@ Layout/SpaceInLambdaLiteral:
     - 'spec/models/ability_spec.rb'
     - 'spec/models/broadcast_message_spec.rb'
     - 'spec/models/concerns/participable_spec.rb'
-    - 'spec/models/concerns/uniquify_spec.rb'
     - 'spec/models/merge_request_spec.rb'
     - 'spec/support/shared_examples/lib/cache_helpers_shared_examples.rb'
     - 'spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb'
diff --git a/.rubocop_todo/lint/unused_block_argument.yml b/.rubocop_todo/lint/unused_block_argument.yml
index f226f04445a1b6c3c8635db8551826695e3de10f..188b57db7a76da2e949a3a25998170cf8aa14d56 100644
--- a/.rubocop_todo/lint/unused_block_argument.yml
+++ b/.rubocop_todo/lint/unused_block_argument.yml
@@ -376,7 +376,6 @@ Lint/UnusedBlockArgument:
     - 'spec/models/concerns/ci/partitionable/switch_spec.rb'
     - 'spec/models/concerns/ci/partitionable_spec.rb'
     - 'spec/models/concerns/each_batch_spec.rb'
-    - 'spec/models/concerns/uniquify_spec.rb'
     - 'spec/models/container_repository_spec.rb'
     - 'spec/models/network/graph_spec.rb'
     - 'spec/models/packages/debian/file_metadatum_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index b40b311591325b0f31e5c9921124d0305accab1b..9ceeab0404984ca64981feb6d81641eadcd57fa3 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -5954,7 +5954,6 @@ RSpec/MissingFeatureCategory:
     - 'spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb'
     - 'spec/models/concerns/transactions_spec.rb'
     - 'spec/models/concerns/triggerable_hooks_spec.rb'
-    - 'spec/models/concerns/uniquify_spec.rb'
     - 'spec/models/concerns/usage_statistics_spec.rb'
     - 'spec/models/concerns/vulnerability_finding_helpers_spec.rb'
     - 'spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb'
diff --git a/app/models/concerns/has_unique_internal_users.rb b/app/models/concerns/has_unique_internal_users.rb
index 4d60cfa03b075833f806c10f54993f811da30757..25b56f6d70f6d39e31986c9cfbf0a33009f5438e 100644
--- a/app/models/concerns/has_unique_internal_users.rb
+++ b/app/models/concerns/has_unique_internal_users.rb
@@ -28,7 +28,7 @@ def create_unique_internal(scope, username, email_pattern, &creation_block)
       existing_user = uncached { scope.first }
       return existing_user if existing_user.present?
 
-      uniquify = Uniquify.new
+      uniquify = Gitlab::Utils::Uniquify.new
 
       username = uniquify.string(username) { |s| User.find_by_username(s) }
 
diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb
deleted file mode 100644
index 382e826ec584f466e589158b3643c92e63be1a2b..0000000000000000000000000000000000000000
--- a/app/models/concerns/uniquify.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-# Uniquify
-#
-# Return a version of the given 'base' string that is unique
-# by appending a counter to it. Uniqueness is determined by
-# repeated calls to the passed block.
-#
-# You can pass an initial value for the counter, if not given
-# counting starts from 1.
-#
-# If `base` is a function/proc, we expect that calling it with a
-# candidate counter returns a string to test/return.
-class Uniquify
-  def initialize(counter = nil)
-    @counter = counter
-  end
-
-  def string(base)
-    @base = base
-
-    increment_counter! while yield(base_string)
-    base_string
-  end
-
-  private
-
-  def base_string
-    if @base.respond_to?(:call)
-      @base.call(@counter)
-    else
-      "#{@base}#{@counter}"
-    end
-  end
-
-  def increment_counter!
-    @counter ||= 0
-    @counter += 1
-  end
-end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index bea86168c8ddafed775fc6c2e848a379a642269b..483bfca259ccfd489381e89062338214c7d005b4 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -463,7 +463,7 @@ def suggested_branch_name
       "#{to_branch_name}-#{suffix}"
     end
 
-    Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name|
+    Gitlab::Utils::Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name|
       project.repository.branch_exists?(suggested_branch_name)
     end
   end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index c3bff24cb1af96ec65add83eb85ca9333486eb14..252fb9aa3088840b87043cba77e7d69cfc3b66fe 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -244,7 +244,7 @@ def search(query, include_parents: false)
     def clean_path(path, limited_to: Namespace.all)
       slug = Gitlab::Slug::Path.new(path).generate
       path = Namespaces::RandomizedSuffixPath.new(slug)
-      Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) }
+      Gitlab::Utils::Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) }
     end
 
     def clean_name(value)
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index c6948536053cec993dd1c7254e7ad9889fc985fc..f122fea6967c9d4d078c7b28876c1f0c08ad5ada 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -85,7 +85,7 @@ def generate_email
     end
 
     def uniquify
-      Uniquify.new
+      Gitlab::Utils::Uniquify.new
     end
 
     def create_personal_access_token(user)
diff --git a/ee/app/controllers/groups/sso_controller.rb b/ee/app/controllers/groups/sso_controller.rb
index 77f65858ff2c804f7cbd72992923edf040add904..c713e0ae03bb8a3901a32fce8e24cc8cdefbea6e 100644
--- a/ee/app/controllers/groups/sso_controller.rb
+++ b/ee/app/controllers/groups/sso_controller.rb
@@ -95,7 +95,7 @@ def new_user_params
 
   def generate_unique_username
     username = ::Namespace.clean_path(oauth_data.username)
-    Uniquify.new.string(username) { |s| !NamespacePathValidator.valid_path?(s) }
+    ::Gitlab::Utils::Uniquify.new.string(username) { |s| !NamespacePathValidator.valid_path?(s) }
   end
 
   def check_oauth_data
diff --git a/ee/lib/ee/gitlab/scim/base_provisioning_service.rb b/ee/lib/ee/gitlab/scim/base_provisioning_service.rb
index d3c5a6d594d880de4b99345802c9a633b3402b12..b6552b796a166a660031864ef538046beb5eed4d 100644
--- a/ee/lib/ee/gitlab/scim/base_provisioning_service.rb
+++ b/ee/lib/ee/gitlab/scim/base_provisioning_service.rb
@@ -35,7 +35,7 @@ def random_password
         def valid_username
           clean_username = ::Namespace.clean_path(@parsed_hash[:username])
 
-          Uniquify.new.string(clean_username) { |s| !NamespacePathValidator.valid_path?(s) }
+          ::Gitlab::Utils::Uniquify.new.string(clean_username) { |s| !NamespacePathValidator.valid_path?(s) }
         end
 
         def missing_params
diff --git a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb
index 19993629ff5a9702414f2f3c6371df6a07e8e569..18ef460385c5d867c336eb5765e2b7746faad519 100644
--- a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb
+++ b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb
@@ -65,9 +65,10 @@ def group_name(namespace, data)
             namespace_children_names = namespace.children.pluck(:name) # rubocop: disable CodeReuse/ActiveRecord
 
             if namespace_children_names.include?(data['name'])
-              data['name'] = Uniquify.new(1).string(-> (counter) { "#{data['name']}(#{counter})" }) do |base|
-                namespace_children_names.include?(base)
-              end
+              data['name'] =
+                Gitlab::Utils::Uniquify.new(1).string(-> (counter) { "#{data['name']}(#{counter})" }) do |base|
+                  namespace_children_names.include?(base)
+                end
             end
           end
 
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 01e126ec2f59431d084982c21fdd6d6e131ae166..bb47b4236fb919d49ae40b28e8eea88d57b55ea4 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -233,7 +233,7 @@ def user_attributes
           email ||= auth_hash.email
 
           valid_username = ::Namespace.clean_path(username)
-          valid_username = Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
+          valid_username = Gitlab::Utils::Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
 
           {
             name: name.strip.presence || valid_username,
diff --git a/lib/gitlab/utils/uniquify.rb b/lib/gitlab/utils/uniquify.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b5908d181035d85e6ed31690f8151c01a359e6f6
--- /dev/null
+++ b/lib/gitlab/utils/uniquify.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+# Uniquify
+#
+# Return a version of the given 'base' string that is unique
+# by appending a counter to it. Uniqueness is determined by
+# repeated calls to the passed block.
+#
+# You can pass an initial value for the counter, if not given
+# counting starts from 1.
+#
+# If `base` is a function/proc, we expect that calling it with a
+# candidate counter returns a string to test/return.
+
+module Gitlab
+  module Utils
+    class Uniquify
+      def initialize(counter = nil)
+        @counter = counter
+      end
+
+      def string(base)
+        @base = base
+
+        increment_counter! while yield(base_string)
+        base_string
+      end
+
+      private
+
+      def base_string
+        if @base.respond_to?(:call)
+          @base.call(@counter)
+        else
+          "#{@base}#{@counter}"
+        end
+      end
+
+      def increment_counter!
+        @counter ||= 0
+        @counter += 1
+      end
+    end
+  end
+end
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/lib/gitlab/utils/uniquify_spec.rb
similarity index 79%
rename from spec/models/concerns/uniquify_spec.rb
rename to spec/lib/gitlab/utils/uniquify_spec.rb
index 9b79e4d415438bbc4aef01d6fecd30a802283a5b..df02fbe8c82970f30b5ca64fa1c4dc6307ae764d 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/lib/gitlab/utils/uniquify_spec.rb
@@ -1,13 +1,13 @@
 # frozen_string_literal: true
 
-require 'spec_helper'
+require 'fast_spec_helper'
 
-RSpec.describe Uniquify do
-  let(:uniquify) { described_class.new }
+RSpec.describe Gitlab::Utils::Uniquify, feature_category: :shared do
+  subject(:uniquify) { described_class.new }
 
   describe "#string" do
     it 'returns the given string if it does not exist' do
-      result = uniquify.string('test_string') { |s| false }
+      result = uniquify.string('test_string') { |_s| false }
 
       expect(result).to eq('test_string')
     end
@@ -34,7 +34,7 @@
     end
 
     it 'allows passing in a base function that defines the location of the counter' do
-      result = uniquify.string(-> (counter) { "test_#{counter}_string" }) do |s|
+      result = uniquify.string(->(counter) { "test_#{counter}_string" }) do |s|
         s == 'test__string'
       end
 
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 7aa7d8e8abd3bc98ae8d1de5d39b4dc302132c81..d6c310d25f25c0ef32be146d0df8866463b6ebd9 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -7956,7 +7956,6 @@
 - './spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb'
 - './spec/models/concerns/transactions_spec.rb'
 - './spec/models/concerns/triggerable_hooks_spec.rb'
-- './spec/models/concerns/uniquify_spec.rb'
 - './spec/models/concerns/usage_statistics_spec.rb'
 - './spec/models/concerns/vulnerability_finding_helpers_spec.rb'
 - './spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb'