diff --git a/tree/dataframe/inc/ROOT/RDataSource.hxx b/tree/dataframe/inc/ROOT/RDataSource.hxx
index b981d359fa81b..288f05913c844 100644
--- a/tree/dataframe/inc/ROOT/RDataSource.hxx
+++ b/tree/dataframe/inc/ROOT/RDataSource.hxx
@@ -17,6 +17,7 @@
 #include "TString.h"
 
 #include <algorithm> // std::transform
+#include <cassert>
 #include <string>
 #include <typeinfo>
 #include <vector>
@@ -130,7 +131,12 @@ public:
    /// Slots numbers are used to simplify parallel execution: RDataFrame guarantees that different threads will always
    /// pass different slot values when calling methods concurrently.
    // clang-format on
-   virtual void SetNSlots(unsigned int nSlots) = 0;
+   virtual void SetNSlots(unsigned int nSlots)
+   {
+      assert(fNSlots == 0);
+      assert(nSlots > 0);
+      fNSlots = nSlots;
+   };
 
    /// \brief Returns the number of files from which the dataset is constructed
    virtual std::size_t GetNFiles() const { return 0; }
diff --git a/tree/dataframe/inc/ROOT/RVecDS.hxx b/tree/dataframe/inc/ROOT/RVecDS.hxx
index 4f380547f2718..4596b5839bdaa 100644
--- a/tree/dataframe/inc/ROOT/RVecDS.hxx
+++ b/tree/dataframe/inc/ROOT/RVecDS.hxx
@@ -173,7 +173,7 @@ public:
       return true;
    }
 
-   void SetNSlots(unsigned int nSlots)
+   void SetNSlots(unsigned int nSlots) final
    {
       fNSlots = nSlots;
       const auto nCols = fColNames.size();
diff --git a/tree/dataframe/test/RArraysDS.hxx b/tree/dataframe/test/RArraysDS.hxx
index 2289297f1199d..5e0e654c6f43d 100644
--- a/tree/dataframe/test/RArraysDS.hxx
+++ b/tree/dataframe/test/RArraysDS.hxx
@@ -48,8 +48,6 @@ public:
    RArraysDS &operator=(RArraysDS &&) = delete;
    ~RArraysDS() final = default;
 
-   void SetNSlots(unsigned int) final { }
-
    const std::vector<std::string> &GetColumnNames() const final { return fColumnNames; }
 
    bool HasColumn(std::string_view name) const final
diff --git a/tree/dataframe/test/RNonCopiableColumnDS.hxx b/tree/dataframe/test/RNonCopiableColumnDS.hxx
index ae53d503beca7..3fcfc0d69fad4 100644
--- a/tree/dataframe/test/RNonCopiableColumnDS.hxx
+++ b/tree/dataframe/test/RNonCopiableColumnDS.hxx
@@ -45,8 +45,7 @@ public:
       auto entryRanges(std::move(fEntryRanges)); // empty fEntryRanges
       return entryRanges;
    };
-   bool SetEntry(unsigned int, ULong64_t) final { return true;};
-   void SetNSlots(unsigned int) final {};
+   bool SetEntry(unsigned int, ULong64_t) final { return true; };
    std::string GetLabel() final {
       return "NonCopiableColumnDS";
    }
diff --git a/tree/dataframe/test/RStreamingDS.hxx b/tree/dataframe/test/RStreamingDS.hxx
index 6b99618b647e0..dc799d7f17dbb 100644
--- a/tree/dataframe/test/RStreamingDS.hxx
+++ b/tree/dataframe/test/RStreamingDS.hxx
@@ -22,7 +22,6 @@ public:
    RStreamingDS &operator=(RStreamingDS &&) = delete;
    ~RStreamingDS() final = default;
 
-   void SetNSlots(unsigned int nSlots) final { fNSlots = nSlots; }
    const std::vector<std::string> &GetColumnNames() const final { return fColumnNames; }
    bool HasColumn(std::string_view name) const final { return std::string(name) == "ans" ? true : false; }
    std::string GetTypeName(std::string_view) const final { return "int"; }