diff --git a/lib/walberla/experimental/Sweep.hpp b/lib/walberla/experimental/Sweep.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d9f475324050ee5ee54ac22862305af7e520b632
--- /dev/null
+++ b/lib/walberla/experimental/Sweep.hpp
@@ -0,0 +1,4 @@
+#pragma once
+
+#include "./sweep/DomainSlices.hpp"
+#include "./sweep/SparseIndexList.hpp"
diff --git a/lib/walberla/experimental/sweep/DomainSlices.hpp b/lib/walberla/experimental/sweep/DomainSlices.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..450316dd1c6e7bc883a61f130d46b2d1c981bd4f
--- /dev/null
+++ b/lib/walberla/experimental/sweep/DomainSlices.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "blockforest/StructuredBlockForest.h"
+
+#include "core/cell/CellInterval.h"
+
+#include "domain_decomposition/IBlock.h"
+
+#include "stencil/Directions.h"
+
+namespace walberla::experimental::sweep
+{
+
+template< typename T >
+concept CellIntervalSweep = requires(T obj, IBlock* block, const CellInterval& ci) {
+   { obj.runOnCellInterval(block, ci) } -> std::same_as< void >;
+};
+
+namespace detail
+{
+template< stencil::Direction BorderDir >
+struct BorderSweepSlice
+{
+   shared_ptr< StructuredBlockForest > blocks;
+   cell_idx_t offset;
+
+   CellInterval ci();
+   bool isBlockAtBorder(IBlock& b);
+};
+
+template<>
+CellInterval BorderSweepSlice< stencil::Direction::T >::ci()
+{
+   return { { 0, 0, cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1, //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1, //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) } };
+};
+
+template<>
+bool BorderSweepSlice< stencil::Direction::B >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainZMinBorder(b);
+}
+
+template<>
+CellInterval BorderSweepSlice< stencil::Direction::B >::ci()
+{
+   return { { 0, 0, offset },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1, //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1, //
+              offset } };
+};
+
+template<>
+bool BorderSweepSlice< stencil::Direction::T >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainZMaxBorder(b);
+}
+} // namespace detail
+
+template< stencil::Direction BorderDir, CellIntervalSweep Sweep >
+class BorderSweep
+{
+ private:
+   Sweep sweep_;
+   detail::BorderSweepSlice< BorderDir > slice_;
+   CellInterval ci_;
+
+ public:
+   BorderSweep(const shared_ptr< StructuredBlockForest >& blocks, const Sweep& sweep, cell_idx_t offset = 0)
+      : sweep_{ sweep }, slice_{ blocks, offset }, ci_{ slice_.ci() }
+   {}
+
+   void operator()(IBlock* block)
+   {
+      if (slice_.isBlockAtBorder(*block)) { sweep_.runOnCellInterval(block, ci_); }
+   }
+};
+
+} // namespace walberla::experimental::sweep
\ No newline at end of file