diff --git a/Core/include/Acts/Fitter/GainMatrixUpdater.hpp b/Core/include/Acts/Fitter/GainMatrixUpdater.hpp
index 5001fe3213980959694f6d4302ec76f3cdc565ee..e31adc5fc8a361e9bf5ad106a9e5d6d10a238aed 100644
--- a/Core/include/Acts/Fitter/GainMatrixUpdater.hpp
+++ b/Core/include/Acts/Fitter/GainMatrixUpdater.hpp
@@ -24,7 +24,7 @@ namespace Acts {
 
 /// @brief Update step of Kalman Filter using gain matrix formalism
 ///
-/// @tparam parameters_t Type of the parameters to be updated
+/// @tparam parameters_t Type of the parameters to be filtered
 /// @tparam jacobian_t Type of the Transport jacobian
 ///
 template <typename parameters_t>
@@ -46,13 +46,15 @@ class GainMatrixUpdater {
   ///
   /// @param gctx The current geometry context object, e.g. alignment
   /// @param trackState the measured track state
+  /// @param direction the navigation direction
   ///
   /// @return Bool indicating whether this update was 'successful'
   /// @note Non-'successful' updates could be holes or outliers,
   ///       which need to be treated differently in calling code.
   template <typename track_state_t>
-  Result<void> operator()(const GeometryContext& /*gctx*/,
-                          track_state_t trackState) const {
+  Result<void> operator()(
+      const GeometryContext& /*gctx*/, track_state_t trackState,
+      const NavigationDirection& direction = forward) const {
     ACTS_VERBOSE("Invoked GainMatrixUpdater");
     // let's make sure the types are consistent
     using SourceLink = typename track_state_t::SourceLink;
@@ -61,6 +63,9 @@ class GainMatrixUpdater {
     static_assert(std::is_same_v<track_state_t, TrackStateProxy>,
                   "Given track state type is not a track state proxy");
 
+    using CovMatrix_t = typename parameters_t::CovMatrix_t;
+    using ParVector_t = typename parameters_t::ParVector_t;
+
     // we should definitely have an uncalibrated measurement here
     assert(trackState.hasUncalibrated());
     // there should be a calibrated measurement
@@ -110,8 +115,11 @@ class GainMatrixUpdater {
           ACTS_VERBOSE("Gain Matrix K:\n" << K);
 
           if (K.hasNaN()) {
-            error = KalmanFitterError::UpdateFailed;  // set to error
-            return false;                             // abort execution
+            error =
+                (direction == forward)
+                    ? KalmanFitterError::ForwardUpdateFailed
+                    : KalmanFitterError::BackwardUpdateFailed;  // set to error
+            return false;  // abort execution
           }
 
           filtered = predicted + K * (calibrated - H * predicted);
@@ -138,6 +146,11 @@ class GainMatrixUpdater {
           return true;  // continue execution
         });
 
+    if (error) {
+      // error is set, return result
+      return *error;
+    }
+
     // always succeed, no outlier logic yet
     return Result<void>::success();
   }
diff --git a/Core/include/Acts/Fitter/KalmanFitter.hpp b/Core/include/Acts/Fitter/KalmanFitter.hpp
index 832bbe63bc3b51e400e18e16bcaded12605f468b..fb89afbacdf608df4aab20a83c564a04a68c009a 100644
--- a/Core/include/Acts/Fitter/KalmanFitter.hpp
+++ b/Core/include/Acts/Fitter/KalmanFitter.hpp
@@ -23,6 +23,7 @@
 #include "Acts/Propagator/ActionList.hpp"
 #include "Acts/Propagator/ConstrainedStep.hpp"
 #include "Acts/Propagator/DirectNavigator.hpp"
+#include "Acts/Propagator/Navigator.hpp"
 #include "Acts/Propagator/Propagator.hpp"
 #include "Acts/Propagator/detail/PointwiseMaterialInteraction.hpp"
 #include "Acts/Propagator/detail/StandardAborters.hpp"
@@ -39,8 +40,9 @@ namespace Acts {
 
 /// @brief Options struct how the Fitter is called
 ///
-/// It contains the context of the fitter call and the optional
-/// surface where to express the fit result
+/// It contains the context of the fitter call, the optional
+/// surface where to express the fit result and configurations for material
+/// effects and smoothing options
 ///
 /// @note the context objects must be provided
 struct KalmanFitterOptions {
@@ -53,17 +55,22 @@ struct KalmanFitterOptions {
   /// @param mctx The magnetic context for this fit
   /// @param cctx The calibration context for this fit
   /// @param rSurface The reference surface for the fit to be expressed at
+  /// @param mScattering Whether to include multiple scattering
+  /// @param eLoss Whether to include energy loss
+  /// @param bwdFiltering Whether to run backward filtering as smoothing
   KalmanFitterOptions(std::reference_wrapper<const GeometryContext> gctx,
                       std::reference_wrapper<const MagneticFieldContext> mctx,
                       std::reference_wrapper<const CalibrationContext> cctx,
                       const Surface* rSurface = nullptr,
-                      bool mScattering = true, bool eLoss = true)
+                      bool mScattering = true, bool eLoss = true,
+                      bool bwdFiltering = false)
       : geoContext(gctx),
         magFieldContext(mctx),
         calibrationContext(cctx),
         referenceSurface(rSurface),
         multipleScattering(mScattering),
-        energyLoss(eLoss) {}
+        energyLoss(eLoss),
+        backwardFiltering(bwdFiltering) {}
 
   /// Context object for the geometry
   std::reference_wrapper<const GeometryContext> geoContext;
@@ -75,11 +82,14 @@ struct KalmanFitterOptions {
   /// The reference Surface
   const Surface* referenceSurface = nullptr;
 
-  /// Whether to consider multiple scattering.
+  /// Whether to consider multiple scattering
   bool multipleScattering = true;
 
-  /// Whether to consider energy loss.
+  /// Whether to consider energy loss
   bool energyLoss = true;
+
+  /// Whether to run backward filtering.
+  bool backwardFiltering = false;
 };
 
 template <typename source_link_t>
@@ -108,7 +118,13 @@ struct KalmanFitterResult {
   bool initialized = false;
 
   // Measurement surfaces without hits
-  std::vector<const Surface*> missedActiveSurfaces = {};
+  std::vector<const Surface*> missedActiveSurfaces;
+
+  // Indicator if forward filtering has been done
+  bool forwardFiltered = false;
+
+  // Measurement surfaces handled in both forward and backward filtering
+  std::vector<const Surface*> passedAgainSurfaces;
 
   Result<void> result{Result<void>::success()};
 };
@@ -230,6 +246,9 @@ class KalmanFitter {
     /// Whether to consider energy loss.
     bool energyLoss = true;
 
+    /// Whether run smoothing as backward filtering
+    bool backwardFiltering = false;
+
     /// @brief Kalman actor operation
     ///
     /// @tparam propagator_state_t is the type of Propagagor state
@@ -242,6 +261,7 @@ class KalmanFitter {
     void operator()(propagator_state_t& state, const stepper_t& stepper,
                     result_type& result) const {
       ACTS_VERBOSE("KalmanFitter step");
+
       // Initialization:
       // - Only when track states are not set
       if (!result.initialized) {
@@ -256,41 +276,66 @@ class KalmanFitter {
       // - Waiting for a current surface that has material
       // -> a trackState will be created on surface with material
       auto surface = state.navigation.currentSurface;
-      if ((surface and surface->surfaceMaterial()) and not result.smoothed) {
+      if (surface and surface->surfaceMaterial()) {
         // Check if the surface is in the measurement map
         // -> Get the measurement / calibrate
         // -> Create the predicted state
         // -> Perform the kalman update
         // -> Check outlier behavior (@todo)
         // -> Fill strack state information & update stepper information
-        ACTS_VERBOSE("Perform filter step");
-        auto res = filter(surface, state, stepper, result);
-        if (!res.ok()) {
-          ACTS_ERROR("Error in filter: " << res.error());
-          result.result = res.error();
+        if (state.stepping.navDir == forward and not result.smoothed and
+            not result.forwardFiltered) {
+          ACTS_VERBOSE("Perform forward filter step");
+          auto res = filter(surface, state, stepper, result);
+          if (!res.ok()) {
+            ACTS_ERROR("Error in forward filter: " << res.error());
+            result.result = res.error();
+          }
+        } else if (state.stepping.navDir == backward) {
+          ACTS_VERBOSE("Perform backward filter step");
+          auto res = backwardFilter(surface, state, stepper, result);
+          if (!res.ok()) {
+            ACTS_ERROR("Error in backward filter: " << res.error());
+            result.result = res.error();
+          }
         }
       }
 
       // Finalization:
-      // When all track states have been handled or the navigation is breaked
-      if ((result.measurementStates == inputMeasurements.size() or
-           (result.measurementStates > 0 and
-            state.navigation.navigationBreak)) and
-          not result.smoothed) {
-        // -> Sort the track states (as now the path length is set)
-        // -> Call the smoothing
-        // -> Set a stop condition when all track states have been handled
-        ACTS_VERBOSE("Finalize/run smoothing");
-        auto res = finalize(state, stepper, result);
-        if (!res.ok()) {
-          ACTS_ERROR("Error in finalize: " << res.error());
-          result.result = res.error();
+      // when all track states have been handled or the navigation is breaked,
+      // reset navigation&stepping before run backward filtering or
+      // proceed to run smoothing
+      if (state.stepping.navDir == forward) {
+        if (result.measurementStates == inputMeasurements.size() or
+            (result.measurementStates > 0 and
+             state.navigation.navigationBreak)) {
+          if (backwardFiltering and not result.forwardFiltered) {
+            ACTS_VERBOSE("Forward filtering done");
+            result.forwardFiltered = true;
+            // Run backward filtering
+            // Reverse navigation direction and reset navigation and stepping
+            // state to last measurement
+            ACTS_VERBOSE("Reverse navigation direction.");
+            reverse(state, stepper, result);
+          } else if (not result.smoothed) {
+            // -> Sort the track states (as now the path length is set)
+            // -> Call the smoothing
+            // -> Set a stop condition when all track states have been handled
+            ACTS_VERBOSE("Finalize/run smoothing");
+            auto res = finalize(state, stepper, result);
+            if (!res.ok()) {
+              ACTS_ERROR("Error in finalize: " << res.error());
+              result.result = res.error();
+            }
+          }
         }
       }
+
       // Post-finalization:
       // - Progress to target/reference surface and built the final track
       // parameters
-      if (result.smoothed and targetReached(state, stepper, *targetSurface)) {
+      if ((result.smoothed or state.stepping.navDir == backward) and
+          targetReached(state, stepper, *targetSurface)) {
         ACTS_VERBOSE("Completing");
         // Transport & bind the parameter to the final surface
         auto fittedState =
@@ -299,6 +344,22 @@ class KalmanFitter {
         result.fittedParameters = std::get<BoundParameters>(fittedState);
         // Break the navigation for stopping the Propagation
         state.navigation.navigationBreak = true;
+
+        // Reset smoothed status of states missed in backward filtering
+        if (backwardFiltering) {
+          result.fittedStates.applyBackwards(result.trackTip, [&](auto state) {
+            auto fSurface = &state.referenceSurface();
+            auto surface_it = std::find_if(
+                result.passedAgainSurfaces.begin(),
+                result.passedAgainSurfaces.end(),
+                [=](const Surface* surface) { return surface == fSurface; });
+            if (surface_it == result.passedAgainSurfaces.end()) {
+              // If backward filtering missed this surface, then there is no
+              // smoothed parameter
+              state.data().ismoothed = detail_lt::IndexData::kInvalid;
+            }
+          });
+        }
       }
     }
 
@@ -314,6 +375,69 @@ class KalmanFitter {
     void initialize(propagator_state_t& /*state*/, const stepper_t& /*stepper*/,
                     result_type& /*result*/) const {}
 
+    /// @brief Kalman actor operation : reverse direction
+    ///
+    /// @tparam propagator_state_t is the type of Propagagor state
+    /// @tparam stepper_t Type of the stepper
+    ///
+    /// @param state is the mutable propagator state object
+    /// @param stepper The stepper in use
+    /// @param result is the mutable result state object
+    template <typename propagator_state_t, typename stepper_t>
+    void reverse(propagator_state_t& state, stepper_t& stepper,
+                 result_type& result) const {
+      // Reset propagator options
+      state.options.direction = backward;
+      state.options.maxStepSize = -1.0 * state.options.maxStepSize;
+      // Not sure if reset of pathLimit during propagation makes any sense
+      state.options.pathLimit = -1.0 * state.options.pathLimit;
+
+      // Reset stepping&navigation state using last measurement track state on
+      // sensitive surface
+      state.navigation = typename propagator_t::NavigatorState();
+      result.fittedStates.applyBackwards(result.trackTip, [&](auto st) {
+        if (st.hasUncalibrated()) {
+          // Set the navigation state
+          state.navigation.startSurface = &st.referenceSurface();
+          state.navigation.startLayer =
+              state.navigation.startSurface->associatedLayer();
+          state.navigation.startVolume =
+              state.navigation.startLayer->trackingVolume();
+          state.navigation.targetSurface = targetSurface;
+          state.navigation.currentSurface = state.navigation.startSurface;
+          state.navigation.currentVolume = state.navigation.startVolume;
+
+          // Update the stepping state
+          stepper.update(state.stepping,
+                         st.filteredParameters(state.options.geoContext));
+          // Reverse stepping direction
+          state.stepping.navDir = backward;
+          state.stepping.stepSize = ConstrainedStep(state.options.maxStepSize);
+          state.stepping.pathAccumulated = 0.;
+          // Reinitialize the stepping jacobian
+          st.referenceSurface().initJacobianToGlobal(
+              state.options.geoContext, state.stepping.jacToGlobal,
+              state.stepping.pos, state.stepping.dir,
+              st.filteredParameters(state.options.geoContext).parameters());
+          state.stepping.jacobian = BoundMatrix::Identity();
+          state.stepping.jacTransport = FreeMatrix::Identity();
+          state.stepping.derivative = FreeVector::Zero();
+
+          // For the last measurement state, smoothed is filtered
+          st.smoothed() = st.filtered();
+          st.smoothedCovariance() = st.filteredCovariance();
+          result.passedAgainSurfaces.push_back(&st.referenceSurface());
+
+          // Update material effects for last measurement state in backward
+          // direction
+          materialInteractor(state.navigation.currentSurface, state, stepper);
+
+          return false;  // abort execution
+        }
+        return true;  // continue execution
+      });
+    }
+
     /// @brief Kalman actor operation : update
     ///
     /// @tparam propagator_state_t is the type of Propagagor state
@@ -374,7 +498,7 @@ class KalmanFitter {
         typeFlags.set(TrackStateFlag::ParameterFlag);
 
         // If the update is successful, set covariance and
-        auto updateRes = m_updater(state.geoContext, trackStateProxy);
+        auto updateRes = m_updater(state.geoContext, trackStateProxy, forward);
         if (!updateRes.ok()) {
           ACTS_ERROR("Update step failed: " << updateRes.error());
           return updateRes.error();
@@ -396,71 +520,190 @@ class KalmanFitter {
         // We count the processed state
         ++result.processedStates;
       } else {
-        // add a non-measurement TrackState entry multi trajectory
-        // (this allocates storage for components except measurements, we will
-        // set them later)
-        result.trackTip = result.fittedStates.addTrackState(
-            ~(TrackStatePropMask::Uncalibrated |
-              TrackStatePropMask::Calibrated),
-            result.trackTip);
+        if (result.measurementStates > 0) {
+          // No source links on surface, add either hole or passive material
+          // TrackState entry multi trajectory. No storage allocation for
+          // uncalibrated/calibrated measurement and filtered parameter
+          result.trackTip = result.fittedStates.addTrackState(
+              ~(TrackStatePropMask::Uncalibrated |
+                TrackStatePropMask::Calibrated | TrackStatePropMask::Filtered),
+              result.trackTip);
+
+          // now get track state proxy back
+          auto trackStateProxy =
+              result.fittedStates.getTrackState(result.trackTip);
+
+          // Set the surface
+          trackStateProxy.setReferenceSurface(surface->getSharedPtr());
+
+          // Set the track state flags
+          auto& typeFlags = trackStateProxy.typeFlags();
+          typeFlags.set(TrackStateFlag::MaterialFlag);
+          typeFlags.set(TrackStateFlag::ParameterFlag);
+
+          if (surface->associatedDetectorElement() != nullptr) {
+            ACTS_VERBOSE("Detected hole on " << surface->geoID());
+            // If the surface is sensitive, set the hole type flag
+            typeFlags.set(TrackStateFlag::HoleFlag);
+
+            // Count the missed surface
+            result.missedActiveSurfaces.push_back(surface);
+
+            // Transport & bind the state to the current surface
+            auto [boundParams, jacobian, pathLength] =
+                stepper.boundState(state.stepping, *surface, true);
+
+            // Fill the track state
+            trackStateProxy.predicted() = boundParams.parameters();
+            trackStateProxy.predictedCovariance() = *boundParams.covariance();
+            trackStateProxy.jacobian() = jacobian;
+            trackStateProxy.pathLength() = pathLength;
+          } else {
+            ACTS_VERBOSE("Detected in-sensitive surface " << surface->geoID());
+
+            // Transport & get curvilinear state instead of bound state
+            auto [curvilinearParams, jacobian, pathLength] =
+                stepper.curvilinearState(state.stepping, true);
+
+            // Fill the track state
+            trackStateProxy.predicted() = curvilinearParams.parameters();
+            trackStateProxy.predictedCovariance() =
+                *curvilinearParams.covariance();
+            trackStateProxy.jacobian() = jacobian;
+            trackStateProxy.pathLength() = pathLength;
+          }
+
+          // Set the filtered parameter index to be the same with predicted
+          // parameter
+          trackStateProxy.data().ifiltered = trackStateProxy.data().ipredicted;
+
+          // We count the processed state
+          ++result.processedStates;
+        }
 
-        // now get track state proxy back
-        auto trackStateProxy =
-            result.fittedStates.getTrackState(result.trackTip);
+        // Update state and stepper with material effects
+        materialInteractor(surface, state, stepper, fullUpdate);
+      }
+      return Result<void>::success();
+    }
+
+    /// @brief Kalman actor operation : backward update
+    ///
+    /// @tparam propagator_state_t is the type of Propagagor state
+    /// @tparam stepper_t Type of the stepper
+    ///
+    /// @param surface The surface where the update happens
+    /// @param state The mutable propagator state object
+    /// @param stepper The stepper in use
+    /// @param result The mutable result state object
+    template <typename propagator_state_t, typename stepper_t>
+    Result<void> backwardFilter(const Surface* surface,
+                                propagator_state_t& state,
+                                const stepper_t& stepper,
+                                result_type& result) const {
+      // Try to find the surface in the measurement surfaces
+      auto sourcelink_it = inputMeasurements.find(surface);
+      if (sourcelink_it != inputMeasurements.end()) {
+        // Screen output message
+        ACTS_VERBOSE("Measurement surface "
+                     << surface->geoID()
+                     << " detected in backward propagation.");
+
+        // No backward filtering for last measurement state, but still update
+        // with material effects
+        if (state.stepping.navDir == backward and
+            surface == state.navigation.startSurface) {
+          materialInteractor(surface, state, stepper);
+          return Result<void>::success();
+        }
 
-        // Set the surface
-        trackStateProxy.setReferenceSurface(surface->getSharedPtr());
+        // Update state and stepper with pre material effects
+        materialInteractor(surface, state, stepper, preUpdate);
 
-        // Set the track state flags
-        auto& typeFlags = trackStateProxy.typeFlags();
-        typeFlags.set(TrackStateFlag::MaterialFlag);
-        typeFlags.set(TrackStateFlag::ParameterFlag);
+        // Transport & bind the state to the current surface
+        auto [boundParams, jacobian, pathLength] =
+            stepper.boundState(state.stepping, *surface, true);
+
+        // Create a detached track state proxy
+        auto tempTrackTip =
+            result.fittedStates.addTrackState(TrackStatePropMask::All);
+
+        // Get the detached track state proxy back
+        auto trackStateProxy = result.fittedStates.getTrackState(tempTrackTip);
+
+        // Assign the source link to the detached track state
+        trackStateProxy.uncalibrated() = sourcelink_it->second;
+
+        // Fill the track state
+        trackStateProxy.predicted() = boundParams.parameters();
+        trackStateProxy.predictedCovariance() = *boundParams.covariance();
+        trackStateProxy.jacobian() = jacobian;
+        trackStateProxy.pathLength() = pathLength;
+
+        // We have predicted parameters, so calibrate the uncalibrated input
+        // measuerement
+        std::visit(
+            [&](const auto& calibrated) {
+              trackStateProxy.setCalibrated(calibrated);
+            },
+            m_calibrator(trackStateProxy.uncalibrated(),
+                         trackStateProxy.predicted()));
+
+        // If the update is successful, set covariance and
+        auto updateRes = m_updater(state.geoContext, trackStateProxy, backward);
+        if (!updateRes.ok()) {
+          ACTS_ERROR("Backward update step failed: " << updateRes.error());
+          return updateRes.error();
+        } else {
+          // Update the stepping state with filtered parameters
+          ACTS_VERBOSE(
+              "Backward Filtering step successful, updated parameters are : \n"
+              << trackStateProxy.filtered().transpose());
+
+          // Fill the smoothed parameter for the existing track state
+          result.fittedStates.applyBackwards(result.trackTip, [&](auto state) {
+            auto fSurface = &state.referenceSurface();
+            if (fSurface == surface) {
+              result.passedAgainSurfaces.push_back(surface);
+              state.smoothed() = trackStateProxy.filtered();
+              state.smoothedCovariance() = trackStateProxy.filteredCovariance();
+              return false;
+            }
+            return true;
+          });
 
+          // update stepping state using filtered parameters after kalman update
+          // We need to (re-)construct a BoundParameters instance here, which is
+          // a bit awkward.
+          stepper.update(state.stepping, trackStateProxy.filteredParameters(
+                                             state.options.geoContext));
+
+          // Update state and stepper with post material effects
+          materialInteractor(surface, state, stepper, postUpdate);
+        }
+      } else {
+        // Transport covariance
         if (surface->associatedDetectorElement() != nullptr) {
-          ACTS_VERBOSE("Detected hole on " << surface->geoID());
-          // If the surface is sensitive, set the hole type flag
-          typeFlags.set(TrackStateFlag::HoleFlag);
-
-          // Count the missed surface
-          result.missedActiveSurfaces.push_back(surface);
-
-          // Transport & bind the state to the current surface
-          auto [boundParams, jacobian, pathLength] =
-              stepper.boundState(state.stepping, *surface, true);
-
-          // Fill the track state
-          trackStateProxy.predicted() = boundParams.parameters();
-          trackStateProxy.predictedCovariance() = *boundParams.covariance();
-          trackStateProxy.jacobian() = jacobian;
-          trackStateProxy.pathLength() = pathLength;
+          ACTS_VERBOSE("Detected hole on " << surface->geoID()
+                                           << " in backward filtering");
+          if (state.stepping.covTransport) {
+            stepper.covarianceTransport(state.stepping, *surface, true);
+          }
         } else {
-          ACTS_VERBOSE("Detected in-sensitive surface " << surface->geoID());
-
-          // Transport & get curvilinear state instead of bound state
-          auto [curvilinearParams, jacobian, pathLength] =
-              stepper.curvilinearState(state.stepping, true);
-
-          // Fill the track state
-          trackStateProxy.predicted() = curvilinearParams.parameters();
-          trackStateProxy.predictedCovariance() =
-              *curvilinearParams.covariance();
-          trackStateProxy.jacobian() = jacobian;
-          trackStateProxy.pathLength() = pathLength;
+          ACTS_VERBOSE("Detected in-sensitive surface "
+                       << surface->geoID() << " in backward filtering");
+          if (state.stepping.covTransport) {
+            stepper.covarianceTransport(state.stepping, true);
+          }
         }
 
-        // Update state and stepper with material effects
-        materialInteractor(surface, state, stepper, fullUpdate);
-
-        // Set the filtered parameter to be the same with predicted parameter
-        // @Todo: shall we update the filterd parameter with material effects?
-        // But it seems that the smoothing does not like this
-        trackStateProxy.filtered() = trackStateProxy.predicted();
-        trackStateProxy.filteredCovariance() =
-            trackStateProxy.predictedCovariance();
+        // Not creating bound state here, so need manually reinitialize jacobian
+        state.stepping.jacobian = BoundMatrix::Identity();
 
-        // We count the processed state
-        ++result.processedStates;
+        // Update state and stepper with material effects
+        materialInteractor(surface, state, stepper);
       }
+
       return Result<void>::success();
     }
 
@@ -475,9 +718,9 @@ class KalmanFitter {
     /// @param updateStage The materal update stage
     ///
     template <typename propagator_state_t, typename stepper_t>
-    void materialInteractor(const Surface* surface, propagator_state_t& state,
-                            stepper_t& stepper,
-                            const MaterialUpdateStage& updateStage) const {
+    void materialInteractor(
+        const Surface* surface, propagator_state_t& state, stepper_t& stepper,
+        const MaterialUpdateStage& updateStage = fullUpdate) const {
       // Prepare relevant input particle properties
       detail::PointwiseMaterialInteraction interaction(surface, state, stepper);
 
@@ -519,23 +762,55 @@ class KalmanFitter {
       // Remember you smoothed the track states
       result.smoothed = true;
 
+      // Get the index of measurement states;
+      std::vector<size_t> measurementIndices;
+      auto lastState = result.fittedStates.getTrackState(result.trackTip);
+      if (lastState.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) {
+        measurementIndices.push_back(result.trackTip);
+      }
+      // Count track states to be smoothed
+      size_t nStates = 0;
+      result.fittedStates.applyBackwards(result.trackTip, [&](auto st) {
+        // Smoothing will start from the last measurement state
+        if (measurementIndices.empty()) {
+          // No smoothed parameter for the last few non-measurment states
+          st.data().ismoothed = detail_lt::IndexData::kInvalid;
+        } else {
+          nStates++;
+        }
+        size_t iprevious = st.previous();
+        if (iprevious != Acts::detail_lt::IndexData::kInvalid) {
+          auto previousState = result.fittedStates.getTrackState(iprevious);
+          if (previousState.typeFlags().test(
+                  Acts::TrackStateFlag::MeasurementFlag)) {
+            measurementIndices.push_back(iprevious);
+          }
+        }
+      });
+      // Return error if the track has no measurement states (but this should
+      // not happen)
+      if (measurementIndices.empty()) {
+        return KalmanFitterError::SmoothFailed;
+      }
       // Screen output for debugging
       if (logger().doPrint(Logging::VERBOSE)) {
-        // need to count track states
-        size_t nStates = 0;
-        result.fittedStates.visitBackwards(result.trackTip,
-                                           [&](const auto) { nStates++; });
         ACTS_VERBOSE("Apply smoothing on " << nStates
                                            << " filtered track states.");
       }
-      // Smooth the track states and obtain the last smoothed track parameters
-      auto smoothRes =
-          m_smoother(state.geoContext, result.fittedStates, result.trackTip);
+      // Smooth the track states
+      auto smoothRes = m_smoother(state.geoContext, result.fittedStates,
+                                  measurementIndices.front());
+
       if (!smoothRes.ok()) {
         ACTS_ERROR("Smoothing step failed: " << smoothRes.error());
         return smoothRes.error();
       }
-      parameters_t smoothedPars = *smoothRes;
+      // Obtain the smoothed parameters at first measurement state
+      auto firstMeasurement =
+          result.fittedStates.getTrackState(measurementIndices.back());
+      parameters_t smoothedPars =
+          firstMeasurement.smoothedParameters(state.options.geoContext);
+
       // Update the stepping parameters - in order to progress to destination
       ACTS_VERBOSE(
           "Smoothing successful, updating stepping state, "
@@ -544,6 +819,10 @@ class KalmanFitter {
       // Reverse the propagation direction
       state.stepping.stepSize =
           ConstrainedStep(-1. * state.options.maxStepSize);
+      state.stepping.navDir = backward;
+      // Set accumulatd path to zero before targeting surface
+      state.stepping.pathAccumulated = 0.;
+      // Not sure if the following line helps anything
       state.options.direction = backward;
 
       return Result<void>::success();
@@ -639,6 +918,7 @@ class KalmanFitter {
     kalmanActor.targetSurface = kfOptions.referenceSurface;
     kalmanActor.multipleScattering = kfOptions.multipleScattering;
     kalmanActor.energyLoss = kfOptions.energyLoss;
+    kalmanActor.backwardFiltering = kfOptions.backwardFiltering;
 
     // also set logger on updater and smoother
     kalmanActor.m_updater.m_logger = m_logger;
@@ -658,6 +938,7 @@ class KalmanFitter {
 
     /// It could happen that the fit ends in zero processed states.
     /// The result gets meaningless so such case is regarded as fit failure.
+    //@TODO: should we require the number of measurments >0 ?
     if (kalmanResult.result.ok() and not kalmanResult.processedStates) {
       kalmanResult.result = Result<void>(KalmanFitterError::PropagationInVain);
     }
@@ -723,6 +1004,9 @@ class KalmanFitter {
     kalmanActor.m_logger = m_logger.get();
     kalmanActor.inputMeasurements = std::move(inputMeasurements);
     kalmanActor.targetSurface = kfOptions.referenceSurface;
+    kalmanActor.multipleScattering = kfOptions.multipleScattering;
+    kalmanActor.energyLoss = kfOptions.energyLoss;
+    kalmanActor.backwardFiltering = kfOptions.backwardFiltering;
 
     // also set logger on updater and smoother
     kalmanActor.m_updater.m_logger = m_logger;
diff --git a/Core/include/Acts/Fitter/KalmanFitterError.hpp b/Core/include/Acts/Fitter/KalmanFitterError.hpp
index 56be3facb94a0e65c058f797182c7d0d7c30f1c4..78d599ff090eb8c4cfeb7d631c12e6a31cd6dddd 100644
--- a/Core/include/Acts/Fitter/KalmanFitterError.hpp
+++ b/Core/include/Acts/Fitter/KalmanFitterError.hpp
@@ -15,10 +15,11 @@
 namespace Acts {
 // This is the custom error code enum
 enum class KalmanFitterError {
-  UpdateFailed = 1,
-  SmoothFailed = 2,
-  OutputConversionFailed = 3,
-  PropagationInVain = 4
+  ForwardUpdateFailed = 1,
+  BackwardUpdateFailed = 2,
+  SmoothFailed = 3,
+  OutputConversionFailed = 4,
+  PropagationInVain = 5
 };
 
 namespace detail {
@@ -30,8 +31,10 @@ class KalmanFitterErrorCategory : public std::error_category {
   // Return what each enum means in text
   std::string message(int c) const final {
     switch (static_cast<KalmanFitterError>(c)) {
-      case KalmanFitterError::UpdateFailed:
-        return "Kalman update failed";
+      case KalmanFitterError::ForwardUpdateFailed:
+        return "Kalman forward update failed";
+      case KalmanFitterError::BackwardUpdateFailed:
+        return "Kalman backward update failed";
       case KalmanFitterError::SmoothFailed:
         return "Kalman smooth failed";
       case KalmanFitterError::OutputConversionFailed:
diff --git a/Core/include/Acts/Propagator/detail/PointwiseMaterialInteraction.hpp b/Core/include/Acts/Propagator/detail/PointwiseMaterialInteraction.hpp
index 76990252591b314153655a9cf865feb3ef8ea0e6..766a6a166878818876bdcce86224e809616b97ea 100644
--- a/Core/include/Acts/Propagator/detail/PointwiseMaterialInteraction.hpp
+++ b/Core/include/Acts/Propagator/detail/PointwiseMaterialInteraction.hpp
@@ -144,9 +144,11 @@ struct PointwiseMaterialInteraction {
   ///
   /// @param [in] variance A diagonal entry of the covariance matrix
   /// @param [in] change The change that may be applied to it
+  /// @param [in] updateMode The noise update mode (in default: add noise)
   ///
   /// @return The updated variance
-  double updateVariance(double variance, double change) const;
+  double updateVariance(double variance, double change,
+                        NoiseUpdateMode updateMode = addNoise) const;
 };
 }  // namespace detail
 }  // end of namespace Acts
diff --git a/Core/include/Acts/Utilities/Definitions.hpp b/Core/include/Acts/Utilities/Definitions.hpp
index 562ea0321f606ef40474a97a719acc12e3a6d79c..7dbb61af0439c95dd26589225bbd78b2b40c3fb6 100644
--- a/Core/include/Acts/Utilities/Definitions.hpp
+++ b/Core/include/Acts/Utilities/Definitions.hpp
@@ -55,6 +55,12 @@ enum MaterialUpdateStage : int {
   postUpdate = 1
 };
 
+/// @enum NoiseUpdateMode to tell how to deal with noise term in covariance
+/// transport
+/// - removeNoise: subtract noise term
+/// - addNoise: add noise term
+enum NoiseUpdateMode : int { removeNoise = -1, addNoise = 1 };
+
 // Eigen definitions
 template <typename T, unsigned int rows, unsigned int cols>
 using ActsMatrix = Eigen::Matrix<T, rows, cols>;
diff --git a/Core/src/Propagator/detail/PointwiseMaterialInteraction.cpp b/Core/src/Propagator/detail/PointwiseMaterialInteraction.cpp
index efb940d8d6627ed0213232786931c121d88e25ca..8669d5a197634cbd95ffd5a9674a480421eafc96 100644
--- a/Core/src/Propagator/detail/PointwiseMaterialInteraction.cpp
+++ b/Core/src/Propagator/detail/PointwiseMaterialInteraction.cpp
@@ -44,11 +44,11 @@ void PointwiseMaterialInteraction::covarianceContributions(
   }
 }
 
-double PointwiseMaterialInteraction::updateVariance(double variance,
-                                                    double change) const {
-  // Add/Subtract the change (depending on the direction)
+double PointwiseMaterialInteraction::updateVariance(
+    double variance, double change, NoiseUpdateMode updateMode) const {
+  // Add/Subtract the change
   // Protect the variance against becoming negative
-  return std::max(0., variance + std::copysign(change, nav));
+  return std::max(0., variance + std::copysign(change, updateMode));
 }
 }  // namespace detail
 }  // end of namespace Acts
diff --git a/Tests/UnitTests/Core/Fitter/KalmanFitterTests.cpp b/Tests/UnitTests/Core/Fitter/KalmanFitterTests.cpp
index 6a32c65f12311ebcdbb037fa8a2485f345e0b3d6..7023d3278d67f1e730c3424fff115b2ca30d0c6f 100644
--- a/Tests/UnitTests/Core/Fitter/KalmanFitterTests.cpp
+++ b/Tests/UnitTests/Core/Fitter/KalmanFitterTests.cpp
@@ -373,6 +373,25 @@ BOOST_AUTO_TEST_CASE(kalman_fitter_zero_field) {
   BOOST_CHECK(!Acts::Test::checkCloseRel(fittedParameters.parameters(),
                                          fittedWithHoleParameters.parameters(),
                                          1e-6));
+  // Run KF fit in backward filtering mode
+  kfOptions.backwardFiltering = true;
+  // Fit the track
+  fitRes = kFitter.fit(sourcelinks, rStart, kfOptions);
+  BOOST_CHECK(fitRes.ok());
+  auto fittedWithBwdFiltering = *fitRes;
+  // Check the filtering and smoothing status flag
+  BOOST_CHECK(fittedWithBwdFiltering.forwardFiltered);
+  BOOST_CHECK(not fittedWithBwdFiltering.smoothed);
+
+  // Count the number of 'smoothed' states
+  auto& trackTip = fittedWithBwdFiltering.trackTip;
+  auto& mj = fittedWithBwdFiltering.fittedStates;
+  size_t nSmoothed = 0;
+  mj.visitBackwards(trackTip, [&](const auto& state) {
+    if (state.hasSmoothed())
+      nSmoothed++;
+  });
+  BOOST_CHECK_EQUAL(nSmoothed, 6u);
 }
 
 }  // namespace Test