diff --git a/src/utils/SubArray.hpp b/src/utils/SubArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2ab94694febea144c527402848302d6bca7cd38b
--- /dev/null
+++ b/src/utils/SubArray.hpp
@@ -0,0 +1,101 @@
+#ifndef SUB_ARRAY_HPP
+#define SUB_ARRAY_HPP
+
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsMacros.hpp>
+#include <utils/PugsUtils.hpp>
+
+#include <algorithm>
+
+template <typename DataType>
+class [[nodiscard]] SubArray
+{
+ public:
+  using data_type  = DataType;
+  using index_type = size_t;
+
+ private:
+  // underlying array
+  Array<DataType> m_array;
+
+  DataType* m_sub_values;
+  size_t m_size;
+
+  // Allows const version to access our data
+  friend SubArray<std::add_const_t<DataType>>;
+
+ public:
+  PUGS_INLINE size_t size() const noexcept
+  {
+    return m_size;
+  }
+
+  PUGS_INLINE DataType& operator[](index_type i) const noexcept(NO_ASSERT)
+  {
+    Assert(i < m_size);
+    return m_sub_values[i];
+  }
+
+  PUGS_INLINE
+  void fill(const DataType& data) const
+  {
+    static_assert(not std::is_const<DataType>(), "Cannot modify SubArray of const");
+
+    // could consider to use std::fill
+    parallel_for(
+      this->size(), PUGS_LAMBDA(index_type i) { m_sub_values[i] = data; });
+  }
+
+  template <typename DataType2>
+  PUGS_INLINE SubArray& operator=(const SubArray<DataType2>& sub_array) noexcept
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>(),
+                  "Cannot assign SubArray of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const<DataType2>() and std::is_const<DataType>()) or not std::is_const<DataType2>()),
+                  "Cannot assign SubArray of const to SubArray of non-const");
+
+    m_array      = sub_array.m_array;
+    m_size       = sub_array.m_size;
+    m_sub_values = sub_array.m_sub_values;
+
+    return *this;
+  }
+
+  PUGS_INLINE
+  SubArray& operator=(const SubArray&) = default;
+
+  PUGS_INLINE
+  SubArray& operator=(SubArray&&) = default;
+
+  PUGS_INLINE
+  explicit SubArray(Array<DataType> array, size_t begin, size_t size)
+    : m_array{array}, m_sub_values{&array[0] + begin}, m_size{size}
+  {
+    Assert(begin + size <= array.size(), "SubView is not contained in the source Array");
+    static_assert(not std::is_const<DataType>(), "Cannot allocate SubArray of const data: only view is "
+                                                 "supported");
+  }
+
+  PUGS_INLINE
+  SubArray() = default;
+
+  PUGS_INLINE
+  SubArray(const SubArray&) = default;
+
+  template <typename DataType2>
+  PUGS_INLINE SubArray(const SubArray<DataType2>& sub_array) noexcept
+  {
+    this->operator=(sub_array);
+  }
+
+  PUGS_INLINE
+  SubArray(SubArray &&) = default;
+
+  PUGS_INLINE
+  ~SubArray() = default;
+};
+
+#endif   // SUB_ARRAY_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c4413ddc7c6f06665b4efb7b091548fb26140fab..c8df103aab80a8d3688868823b88da4289317f48 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -85,6 +85,7 @@ add_executable (unit_tests
   test_PugsUtils.cpp
   test_RevisionInfo.cpp
   test_SparseMatrixDescriptor.cpp
+  test_SubArray.cpp
   test_SymbolTable.cpp
   test_Timer.cpp
   test_TinyMatrix.cpp
diff --git a/tests/test_SubArray.cpp b/tests/test_SubArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..98e303dd1681378a50fdb281ab65bb83273a50e2
--- /dev/null
+++ b/tests/test_SubArray.cpp
@@ -0,0 +1,107 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/SubArray.hpp>
+#include <utils/Types.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class SubArray<int>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubArray", "[utils]")
+{
+  Array<int> a(10);
+  REQUIRE(a.size() == 10);
+
+  SECTION("shared values")
+  {
+    SubArray sub_a{a, 0, 10};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = 2 * i;
+    }
+
+    REQUIRE(((a[0] == 0) and (a[1] == 2) and (a[2] == 4) and (a[3] == 6) and (a[4] == 8) and (a[5] == 10) and
+             (a[6] == 12) and (a[7] == 14) and (a[8] == 16) and (a[9] == 18)));
+
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = (i + 1) * (2 * i + 1);
+    }
+
+    REQUIRE(((sub_a[0] == 1) and (sub_a[1] == 6) and (sub_a[2] == 15) and (sub_a[3] == 28) and (sub_a[4] == 45) and
+             (sub_a[5] == 66) and (sub_a[6] == 91) and (sub_a[7] == 120) and (sub_a[8] == 153) and (sub_a[9] == 190)));
+  }
+
+  SECTION("sub array")
+  {
+    a.fill(0);
+    SubArray sub_a{a, 5, 5};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = i + 1;
+    }
+
+    REQUIRE(((a[0] == 0) and (a[1] == 0) and (a[2] == 0) and (a[3] == 0) and (a[4] == 0) and (a[5] == 1) and
+             (a[6] == 2) and (a[7] == 3) and (a[8] == 4) and (a[9] == 5)));
+
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = (i + 1) * (2 * i + 1);
+    }
+
+    REQUIRE(((sub_a[0] == 66) and (sub_a[1] == 91) and (sub_a[2] == 120) and (sub_a[3] == 153) and (sub_a[4] == 190)));
+  }
+
+  SECTION("sub array copy")
+  {
+    a.fill(0);
+    SubArray<int> sub_a;
+    sub_a = SubArray{a, 5, 5};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = i + 1;
+    }
+
+    REQUIRE(((a[0] == 0) and (a[1] == 0) and (a[2] == 0) and (a[3] == 0) and (a[4] == 0) and (a[5] == 1) and
+             (a[6] == 2) and (a[7] == 3) and (a[8] == 4) and (a[9] == 5)));
+
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = (i + 1) * (2 * i + 1);
+    }
+
+    REQUIRE(((sub_a[0] == 66) and (sub_a[1] == 91) and (sub_a[2] == 120) and (sub_a[3] == 153) and (sub_a[4] == 190)));
+
+    SubArray sub_b = sub_a;
+
+    REQUIRE(((sub_b[0] == 66) and (sub_b[1] == 91) and (sub_b[2] == 120) and (sub_b[3] == 153) and (sub_b[4] == 190)));
+
+    sub_b = sub_a;
+
+    REQUIRE(((sub_b[0] == 66) and (sub_b[1] == 91) and (sub_b[2] == 120) and (sub_b[3] == 153) and (sub_b[4] == 190)));
+
+    SubArray<const int> const_sub_a{sub_a};
+
+    REQUIRE(((const_sub_a[0] == 66) and (const_sub_a[1] == 91) and (const_sub_a[2] == 120) and
+             (const_sub_a[3] == 153) and (const_sub_a[4] == 190)));
+
+    sub_a.fill(2);
+    REQUIRE(((const_sub_a[0] == 2) and (const_sub_a[1] == 2) and (const_sub_a[2] == 2) and (const_sub_a[3] == 2) and
+             (const_sub_a[4] == 2)));
+
+    SubArray sub_c{SubArray{a, 3, 6}};
+    REQUIRE(((sub_c[0] == a[3]) and (sub_c[1] == a[4]) and (sub_c[2] == a[5]) and (sub_c[3] == a[6]) and
+             (sub_c[4] == a[7]) and (sub_c[5] == a[8])));
+  }
+
+#ifndef NDEBUG
+  SECTION("errors")
+  {
+    a.fill(0);
+    SubArray<int> sub_a;
+    sub_a = SubArray{a, 5, 5};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = i + 1;
+    }
+
+    REQUIRE_THROWS_AS(sub_a[5], AssertError);
+  }
+#endif   // NDEBUG
+}