diff --git a/include/base_set.h b/include/base_set.h
new file mode 100644
index 0000000000..8385e0393a
--- /dev/null
+++ b/include/base_set.h
@@ -0,0 +1,173 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef BASE_SET_H
+#define BASE_SET_H
+
+#include <vector>
+#include <stdexcept>
+#include <iterator>
+
+class BASE_SET
+{
+public:
+    using iterator = std::vector<int>::iterator;
+    using const_iterator = std::vector<int>::const_iterator;
+
+    BASE_SET( size_t size = 0 ) : m_bits( size, 0 ) {}
+
+    bool test( size_t pos ) const
+    {
+        if( pos >= m_bits.size() )
+            throw std::out_of_range( "Index out of range" );
+
+        return m_bits[pos] == 1;
+    }
+
+    void set( size_t pos )
+    {
+        if( pos >= m_bits.size() )
+            throw std::out_of_range( "Index out of range" );
+
+        m_bits[pos] = 1;
+    }
+
+    void reset( size_t pos )
+    {
+        if( pos >= m_bits.size() )
+            throw std::out_of_range( "Index out of range" );
+
+        m_bits[pos] = 0;
+    }
+
+    size_t count() const { return std::count( m_bits.begin(), m_bits.end(), 1 ); }
+
+    size_t size() const { return m_bits.size(); }
+
+    void resize( size_t newSize ) { m_bits.resize( newSize, 0 ); }
+
+    iterator       begin() { return m_bits.begin(); }
+    iterator       end() { return m_bits.end(); }
+    const_iterator begin() const { return m_bits.begin(); }
+    const_iterator end() const { return m_bits.end(); }
+
+    // Custom iterator to iterate over set bits
+    class set_bits_iterator
+    {
+    public:
+        using iterator_category = std::forward_iterator_tag;
+        using value_type = size_t;
+        using difference_type = std::ptrdiff_t;
+        using pointer = const size_t*;
+        using reference = const size_t&;
+
+        set_bits_iterator( const BASE_SET& baseSet, size_t index ) :
+                m_baseSet( baseSet ), m_index( index )
+        {
+            advance_to_next_set_bit();
+        }
+
+        size_t operator*() const { return m_index; }
+
+        set_bits_iterator& operator++()
+        {
+            ++m_index;
+            advance_to_next_set_bit();
+            return *this;
+        }
+
+        bool operator!=( const set_bits_iterator& other ) const { return m_index != other.m_index; }
+
+        bool operator==( const set_bits_iterator& other ) const { return m_index == other.m_index; }
+
+    private:
+        void advance_to_next_set_bit()
+        {
+            while( m_index < m_baseSet.size() && !m_baseSet.test( m_index ) )
+                ++m_index;
+        }
+
+        const BASE_SET& m_baseSet;
+        size_t          m_index;
+    };
+
+    // Custom reverse iterator to iterate over set bits in reverse order
+    class set_bits_reverse_iterator
+    {
+    public:
+        using iterator_category = std::bidirectional_iterator_tag;
+        using value_type = ssize_t;
+        using difference_type = std::ptrdiff_t;
+        using pointer = const ssize_t*;
+        using reference = const ssize_t&;
+
+        set_bits_reverse_iterator( const BASE_SET& baseSet, ssize_t index ) :
+                m_baseSet( baseSet ), m_index( index )
+        {
+            advance_to_previous_set_bit();
+        }
+
+        ssize_t operator*() const { return m_index; }
+
+        set_bits_reverse_iterator& operator++()
+        {
+            --m_index;
+            advance_to_previous_set_bit();
+            return *this;
+        }
+
+        bool operator!=( const set_bits_reverse_iterator& other ) const
+        {
+            return m_index != other.m_index;
+        }
+
+        bool operator==( const set_bits_reverse_iterator& other ) const
+        {
+            return m_index == other.m_index;
+        }
+
+    private:
+        void advance_to_previous_set_bit()
+        {
+            while( m_index >= 0 && !m_baseSet.test( m_index ) )
+            {
+                --m_index;
+            }
+        }
+
+        const BASE_SET& m_baseSet;
+        ssize_t         m_index;
+    };
+
+    set_bits_iterator set_bits_begin() const { return set_bits_iterator( *this, 0 ); }
+    set_bits_iterator set_bits_end() const { return set_bits_iterator( *this, m_bits.size() ); }
+
+    set_bits_reverse_iterator set_bits_rbegin() const
+    {
+        return set_bits_reverse_iterator( *this, m_bits.size() - 1 );
+    }
+    set_bits_reverse_iterator set_bits_rend() const
+    {
+        return set_bits_reverse_iterator( *this, -1 );
+    }
+
+private:
+    std::vector<int> m_bits;
+};
+
+#endif // BASE_SET_H
diff --git a/qa/tests/common/CMakeLists.txt b/qa/tests/common/CMakeLists.txt
index e41699e68c..e18714b7cd 100644
--- a/qa/tests/common/CMakeLists.txt
+++ b/qa/tests/common/CMakeLists.txt
@@ -31,6 +31,7 @@ set( QA_COMMON_SRCS
     wximage_test_utils.cpp
 
     test_array_axis.cpp
+    test_base_set.cpp
     test_bitmap_base.cpp
     test_color4d.cpp
     test_coroutine.cpp
diff --git a/qa/tests/common/test_base_set.cpp b/qa/tests/common/test_base_set.cpp
new file mode 100644
index 0000000000..f5bf394269
--- /dev/null
+++ b/qa/tests/common/test_base_set.cpp
@@ -0,0 +1,81 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+#include "base_set.h"
+
+BOOST_AUTO_TEST_SUITE( BaseSetTests )
+
+BOOST_AUTO_TEST_CASE( ConstructionAndSize )
+{
+    BASE_SET bs(10);
+    BOOST_CHECK_EQUAL(bs.size(), 10);
+    BOOST_CHECK_EQUAL(bs.count(), 0);
+
+    bs.resize(20);
+    BOOST_CHECK_EQUAL(bs.size(), 20);
+    BOOST_CHECK_EQUAL(bs.count(), 0);
+}
+
+BOOST_AUTO_TEST_CASE( BitSettingAndResetting )
+{
+    BASE_SET bs(10);
+    bs.set(2);
+    BOOST_CHECK(bs.test(2));
+    BOOST_CHECK_EQUAL(bs.count(), 1);
+
+    bs.reset(2);
+    BOOST_CHECK(!bs.test(2));
+    BOOST_CHECK_EQUAL(bs.count(), 0);
+}
+
+BOOST_AUTO_TEST_CASE( OutOfRange )
+{
+    BASE_SET bs(10);
+    BOOST_CHECK_THROW(bs.set(10), std::out_of_range);
+    BOOST_CHECK_THROW(bs.reset(10), std::out_of_range);
+    BOOST_CHECK_THROW(bs.test(10), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE( IteratingSetBits )
+{
+    BASE_SET bs(10);
+    bs.set(2);
+    bs.set(4);
+
+    auto it = bs.set_bits_begin();
+    BOOST_CHECK_EQUAL(*it, 2);
+    ++it;
+    BOOST_CHECK_EQUAL(*it, 4);
+    ++it;
+    BOOST_CHECK(it == bs.set_bits_end());
+
+    // Custom reverse iterator test
+    std::vector<size_t> reverse_set_bits;
+    for (auto rit = bs.set_bits_rbegin(); rit != bs.set_bits_rend(); ++rit)
+    {
+        reverse_set_bits.push_back(*rit);
+    }
+
+    BOOST_CHECK_EQUAL(reverse_set_bits.size(), 2);
+    BOOST_CHECK_EQUAL(reverse_set_bits[0], 4);
+    BOOST_CHECK_EQUAL(reverse_set_bits[1], 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()