diff --git a/Cargo.lock b/Cargo.lock
index 8ed6693f1ae82932d6bcbfcd36136c29a053943d..0abfbe428528c0cb71d360d2d2098131eb67b56c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1161,7 +1161,6 @@ dependencies = [
  "serde",
  "serde_yaml",
  "tarantool",
- "traversal",
  "uuid 1.2.2",
 ]
 
@@ -1439,12 +1438,6 @@ dependencies = [
  "syn 1.0.107",
 ]
 
-[[package]]
-name = "traversal"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0ec9745d7517c8b8e8c0a65cba2d84e42f95fd348a01693c5e4da1bc6d00c99"
-
 [[package]]
 name = "typenum"
 version = "1.16.0"
diff --git a/sbroad-benches/src/engine.rs b/sbroad-benches/src/engine.rs
index f768da0a7e49761f80c47cb74bc8c5925778987b..c48e8010b7a2a2315e2acbfe6d1b773addb2ee7d 100644
--- a/sbroad-benches/src/engine.rs
+++ b/sbroad-benches/src/engine.rs
@@ -415,7 +415,7 @@ impl Coordinator for RouterRuntimeMock {
         } else {
             Err(SbroadError::NotFound(
                 Entity::VirtualTable,
-                format!("for motion node {}", motion_node_id),
+                format!("for motion node {motion_node_id}"),
             ))
         }
     }
diff --git a/sbroad-core/Cargo.toml b/sbroad-core/Cargo.toml
index 6d3c537ad54ac1361551db894dcf74976accadc9..0f9e568d44301999dc280418c3049964c86e4162 100644
--- a/sbroad-core/Cargo.toml
+++ b/sbroad-core/Cargo.toml
@@ -25,7 +25,6 @@ sbroad-proc = { path = "../sbroad-proc", version = "0.1" }
 serde = { version = "1.0", features = ["derive", "rc"] }
 serde_yaml = "0.8"
 tarantool = { git = "https://git.picodata.io/picodata/picodata/tarantool-module.git", features = ["picodata", "schema"] }
-traversal = "0.1"
 uuid = { version = "1.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
 
 [dev-dependencies]
diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs
index e16e65bb4a303715daa7993950cc17df29f61f19..73a2966fc637c82300974b1c608f859f700c786f 100644
--- a/sbroad-core/src/backend/sql/tree.rs
+++ b/sbroad-core/src/backend/sql/tree.rs
@@ -3,12 +3,12 @@ use std::collections::HashMap;
 use std::mem::take;
 
 use serde::{Deserialize, Serialize};
-use traversal::DftPost;
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::ir::ExecutionPlan;
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
+use crate::ir::tree::traversal::PostOrder;
 use crate::ir::tree::Snapshot;
 use crate::ir::Node;
 use crate::otm::child_span;
@@ -975,23 +975,26 @@ impl<'p> SyntaxPlan<'p> {
         let ir_plan = plan.get_ir_plan();
 
         // Wrap plan's nodes and preserve their ids.
+        let capacity = ir_plan.next_id();
         match snapshot {
             Snapshot::Latest => {
-                let dft_post = DftPost::new(&top, |node| ir_plan.subtree_iter(node));
-                for (_, id) in dft_post {
+                let mut dft_post =
+                    PostOrder::with_capacity(|node| ir_plan.subtree_iter(node), capacity);
+                for (_, id) in dft_post.iter(top) {
                     // it works only for post-order traversal
-                    let sn_id = sp.add_plan_node(*id)?;
-                    if *id == top {
+                    let sn_id = sp.add_plan_node(id)?;
+                    if id == top {
                         sp.set_top(sn_id)?;
                     }
                 }
             }
             Snapshot::Oldest => {
-                let dft_post = DftPost::new(&top, |node| ir_plan.flashback_subtree_iter(node));
-                for (_, id) in dft_post {
+                let mut dft_post =
+                    PostOrder::with_capacity(|node| ir_plan.flashback_subtree_iter(node), capacity);
+                for (_, id) in dft_post.iter(top) {
                     // it works only for post-order traversal
-                    let sn_id = sp.add_plan_node(*id)?;
-                    if *id == top {
+                    let sn_id = sp.add_plan_node(id)?;
+                    if id == top {
                         sp.set_top(sn_id)?;
                     }
                 }
diff --git a/sbroad-core/src/executor/bucket.rs b/sbroad-core/src/executor/bucket.rs
index 45ddb92f4abfd000b3d881c6518573a3b8e81025..30d07c9f599174df0dd6894ebc928c6f50d5ec2c 100644
--- a/sbroad-core/src/executor/bucket.rs
+++ b/sbroad-core/src/executor/bucket.rs
@@ -1,7 +1,5 @@
 use std::collections::HashSet;
 
-use traversal::DftPost;
-
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::Coordinator;
 use crate::executor::Query;
@@ -10,6 +8,7 @@ use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::operator::{Bool, Relational};
 use crate::ir::transformation::redistribution::MotionPolicy;
+use crate::ir::tree::traversal::PostOrder;
 use crate::ir::value::Value;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
@@ -134,7 +133,7 @@ where
                                 *right_columns.get(*position).ok_or_else(|| {
                                     SbroadError::NotFound(
                                         Entity::Column,
-                                        format!("at position {} for right row", position),
+                                        format!("at position {position} for right row"),
                                     )
                                 })?;
                             let right_column_expr = ir_plan.get_expression_node(right_column_id)?;
@@ -204,11 +203,13 @@ where
         let ir_plan = self.exec_plan.get_ir_plan();
         // We use a `subtree_iter()` because we need DNF version of the filter/condition
         // expressions to determine buckets.
-        let tree = DftPost::new(&top_id, |node| ir_plan.subtree_iter(node));
+        let capacity = ir_plan.next_id();
+        let mut tree = PostOrder::with_capacity(|node| ir_plan.subtree_iter(node), capacity);
         let nodes: Vec<usize> = tree
+            .iter(top_id)
             .filter_map(|(_, id)| {
-                if ir_plan.get_relation_node(*id).is_ok() {
-                    Some(*id)
+                if ir_plan.get_relation_node(id).is_ok() {
+                    Some(id)
                 } else {
                     None
                 }
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index ec7a9fc2f8f8f27b5983c751d80ef587f6f34e71..46412227cfeb57988d2201de65de2d1988b1121a 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -169,7 +169,7 @@ pub fn sharding_keys_from_tuple<'rec>(
             let value = tuple.get(*position).ok_or_else(|| {
                 SbroadError::NotFound(
                     Entity::ShardingKey,
-                    format!("position {:?} in the tuple {:?}", position, tuple),
+                    format!("position {position:?} in the tuple {tuple:?}"),
                 )
             })?;
             sharding_tuple.push(value);
diff --git a/sbroad-core/src/executor/engine/mock.rs b/sbroad-core/src/executor/engine/mock.rs
index 39e6993aea63c5f9d2049c27dc2658c9664c8cc5..72be683814089573e7ee8805c34506951ece1f1e 100644
--- a/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad-core/src/executor/engine/mock.rs
@@ -300,7 +300,7 @@ impl Coordinator for RouterRuntimeMock {
         } else {
             Err(SbroadError::NotFound(
                 Entity::VirtualTable,
-                format!("for motion node {}", motion_node_id),
+                format!("for motion node {motion_node_id}"),
             ))
         }
     }
diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs
index d92ad547d7b59349100b5171003ceb9a915d2a43..6469ce8c8912c4bd4a2c02a16dca1c806e275c20 100644
--- a/sbroad-core/src/executor/ir.rs
+++ b/sbroad-core/src/executor/ir.rs
@@ -3,13 +3,13 @@ use std::rc::Rc;
 
 use ahash::AHashMap;
 use serde::{Deserialize, Serialize};
-use traversal::DftPost;
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::vtable::{VirtualTable, VirtualTableMap};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
 use crate::ir::transformation::redistribution::MotionPolicy;
+use crate::ir::tree::traversal::PostOrder;
 use crate::ir::{Node, Plan};
 
 /// Query type (used to parse the returned results).
@@ -243,11 +243,13 @@ impl ExecutionPlan {
             HashMap::with_capacity(vtables_capacity);
         let mut new_plan = Plan::new();
         new_plan.nodes.reserve(nodes_capacity);
-        let subtree = DftPost::new(&top_id, |node| {
-            self.get_ir_plan().exec_plan_subtree_iter(node)
-        });
-        let nodes: Vec<usize> = subtree.map(|(_, id)| *id).collect();
-        for node_id in nodes {
+        let mut subtree = PostOrder::with_capacity(
+            |node| self.get_ir_plan().exec_plan_subtree_iter(node),
+            self.get_ir_plan().next_id(),
+        );
+        subtree.populate_nodes(top_id);
+        let nodes = subtree.take_nodes();
+        for (_, node_id) in nodes {
             // We have already processed this node (sub-queries in BETWEEN can be referred twice).
             if translation.contains_key(&node_id) {
                 continue;
diff --git a/sbroad-core/src/executor/shard.rs b/sbroad-core/src/executor/shard.rs
index 6f80b84485029152ce320a8a4fffcd9593e32540..aec54bab27bbc55295f9edad887531a66a11fca3 100644
--- a/sbroad-core/src/executor/shard.rs
+++ b/sbroad-core/src/executor/shard.rs
@@ -1,7 +1,5 @@
 use std::collections::HashSet;
 
-use traversal::DftPost;
-
 use crate::executor::ir::ExecutionPlan;
 use crate::{
     errors::{SbroadError, Entity},
@@ -14,11 +12,11 @@ impl<'e> ExecutionPlan<'e> {
     fn get_bool_eq_with_rows(&self, top_node_id: usize) -> Vec<usize> {
         let mut nodes: Vec<usize> = Vec::new();
         let ir_plan = self.get_ir_plan();
-
-        let post_tree = DftPost::new(&top_node_id, |node| ir_plan.subtree_iter(node));
-        for (_, node_id) in post_tree {
-            if ir_plan.is_bool_eq_with_rows(*node_id) {
-                nodes.push(*node_id);
+        let capacity = ir_plan.next_id();
+        let post_tree = PostOrder::with_capacity(|node| ir_plan.subtree_iter(node), capacity);
+        for (_, node_id) in post_tree.iter(top_node_id) {
+            if ir_plan.is_bool_eq_with_rows(node_id) {
+                nodes.push(node_id);
             }
         }
         nodes
@@ -36,10 +34,10 @@ impl<'e> ExecutionPlan<'e> {
         let mut result = HashSet::new();
 
         let ir_plan = self.get_ir_plan();
-        let post_tree = DftPost::new(&top_node_id, |node| ir_plan.nodes.rel_iter(node));
-        for (_, node) in post_tree {
-            if ir_plan.get_relation_node(*node)?.is_motion() {
-                let vtable = self.get_motion_vtable(*node)?;
+        let mut post_tree = PostOrder::with_capacity(|node| ir_plan.subtree_iter(node), ir_plan.next_id());
+        for (_, id) in post_tree.iter(top_node_id) {
+            if ir_plan.get_relation_node(id)?.is_motion() {
+                let vtable = self.get_motion_vtable(id)?;
                 result.extend(&vtable.get_sharding_keys()?);
             }
         }
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 710e9ce0795a5136a671d7d7cbc814f4053beae4..ec22742793177956e1d89194f564bfa133d7ec69 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -5,7 +5,6 @@
 
 use pest::Parser;
 use std::collections::{HashMap, HashSet};
-use traversal::DftPost;
 
 use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::{normalize_name_from_sql, CoordinatorMetadata};
@@ -17,6 +16,7 @@ use crate::frontend::Ast;
 use crate::ir::expression::cast::Type as CastType;
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Unary};
+use crate::ir::tree::traversal::PostOrder;
 use crate::ir::value::Value;
 use crate::ir::{Node, Plan};
 use crate::otm::child_span;
@@ -123,15 +123,16 @@ impl Ast for AbstractSyntaxTree {
             Some(t) => t,
             None => return Err(SbroadError::Invalid(Entity::AST, None)),
         };
-        let dft_post = DftPost::new(&top, |node| self.nodes.ast_iter(node));
+        let capacity = self.nodes.arena.len();
+        let mut dft_post = PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity);
         let mut map = Translation::with_capacity(self.nodes.next_id());
         let mut rows: HashSet<usize> = HashSet::with_capacity(self.nodes.next_id());
         let mut col_idx: usize = 0;
 
         let mut betweens: Vec<Between> = Vec::new();
 
-        for (_, id) in dft_post {
-            let node = self.nodes.get_node(*id)?;
+        for (_, id) in dft_post.iter(top) {
+            let node = self.nodes.get_node(id)?;
             match &node.rule {
                 Type::Scan => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
@@ -140,7 +141,7 @@ impl Ast for AbstractSyntaxTree {
                         )
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
-                    map.add(*id, plan_child_id);
+                    map.add(id, plan_child_id);
                     if let Some(ast_scan_id) = node.children.get(1) {
                         let ast_scan = self.nodes.get_node(*ast_scan_id)?;
                         if let Type::ScanName = ast_scan.rule {
@@ -163,7 +164,7 @@ impl Ast for AbstractSyntaxTree {
                         let t = metadata.get_table_segment(table)?;
                         plan.add_rel(t);
                         let scan_id = plan.add_scan(&normalize_name_from_sql(table), None)?;
-                        map.add(*id, scan_id);
+                        map.add(id, scan_id);
                     } else {
                         return Err(SbroadError::Invalid(
                             Entity::Type,
@@ -196,10 +197,10 @@ impl Ast for AbstractSyntaxTree {
                         None
                     };
                     let plan_sq_id = plan.add_sub_query(plan_child_id, alias_name.as_deref())?;
-                    map.add(*id, plan_sq_id);
+                    map.add(id, plan_sq_id);
                 }
                 Type::Reference => {
-                    let ast_rel_list = self.get_referred_relational_nodes(*id)?;
+                    let ast_rel_list = self.get_referred_relational_nodes(id)?;
                     let mut plan_rel_list = Vec::new();
                     for ast_id in ast_rel_list {
                         let plan_id = map.get(ast_id)?;
@@ -269,7 +270,7 @@ impl Ast for AbstractSyntaxTree {
                                             &[&col_name],
                                         )?;
                                         rows.insert(ref_id);
-                                        map.add(*id, ref_id);
+                                        map.add(id, ref_id);
                                     } else {
                                         return Err(SbroadError::NotFound(
                                             Entity::Column,
@@ -290,7 +291,7 @@ impl Ast for AbstractSyntaxTree {
                                             &[&col_name],
                                         )?;
                                         rows.insert(ref_id);
-                                        map.add(*id, ref_id);
+                                        map.add(id, ref_id);
                                     } else {
                                         return Err(SbroadError::NotFound(
                                             Entity::Column,
@@ -327,7 +328,7 @@ impl Ast for AbstractSyntaxTree {
                                     &[&col_name],
                                 )?;
                                 rows.insert(ref_id);
-                                map.add(*id, ref_id);
+                                map.add(id, ref_id);
                             }
                             let right_col_map = plan
                                 .get_relation_node(*plan_right_id)?
@@ -339,11 +340,11 @@ impl Ast for AbstractSyntaxTree {
                                     &[&col_name],
                                 )?;
                                 rows.insert(ref_id);
-                                map.add(*id, ref_id);
+                                map.add(id, ref_id);
                             }
                             return Err(SbroadError::NotFound(
                                 Entity::Column,
-                                format!("'{}' for the join left or right children", col_name),
+                                format!("'{col_name}' for the join left or right children"),
                             ));
                         } else {
                             return Err(SbroadError::UnexpectedNumberOfValues(
@@ -408,7 +409,7 @@ impl Ast for AbstractSyntaxTree {
                                 "Referred column is not found.".into(),
                             )
                         })?;
-                        map.add(*id, ref_id);
+                        map.add(id, ref_id);
                     } else {
                         return Err(SbroadError::UnexpectedNumberOfValues(
                             "expected one or two referred relational nodes, got less or more."
@@ -425,14 +426,14 @@ impl Ast for AbstractSyntaxTree {
                 | Type::True
                 | Type::False => {
                     let val = Value::from_node(node)?;
-                    map.add(*id, plan.add_const(val));
+                    map.add(id, plan.add_const(val));
                 }
                 Type::Parameter => {
-                    map.add(*id, plan.add_param());
+                    map.add(id, plan.add_param());
                 }
                 Type::Asterisk => {
                     // We can get an asterisk only in projection.
-                    let ast_rel_list = self.get_referred_relational_nodes(*id)?;
+                    let ast_rel_list = self.get_referred_relational_nodes(id)?;
                     let mut plan_rel_list = Vec::new();
                     for ast_id in ast_rel_list {
                         let plan_id = map.get(ast_id)?;
@@ -450,7 +451,7 @@ impl Ast for AbstractSyntaxTree {
                         )
                     })?;
                     let plan_asterisk_id = plan.add_row_for_output(plan_rel_id, &[], false)?;
-                    map.add(*id, plan_asterisk_id);
+                    map.add(id, plan_asterisk_id);
                 }
                 Type::Alias => {
                     let ast_ref_id = node.children.first().ok_or_else(|| {
@@ -472,14 +473,14 @@ impl Ast for AbstractSyntaxTree {
                     let plan_alias_id = plan
                         .nodes
                         .add_alias(&normalize_name_from_sql(name), plan_ref_id)?;
-                    map.add(*id, plan_alias_id);
+                    map.add(id, plan_alias_id);
                 }
                 Type::Column => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues("Column has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
-                    map.add(*id, plan_child_id);
+                    map.add(id, plan_child_id);
                 }
                 Type::Row => {
                     let mut plan_col_list = Vec::new();
@@ -498,7 +499,7 @@ impl Ast for AbstractSyntaxTree {
                         plan_col_list.push(plan_id);
                     }
                     let plan_row_id = plan.nodes.add_row(plan_col_list, None);
-                    map.add(*id, plan_row_id);
+                    map.add(id, plan_row_id);
                 }
                 Type::And
                 | Type::Or
@@ -523,7 +524,7 @@ impl Ast for AbstractSyntaxTree {
                     let plan_right_id = plan.as_row(map.get(*ast_right_id)?, &mut rows)?;
                     let op = Bool::from_node_type(&node.rule)?;
                     let cond_id = plan.add_cond(plan_left_id, op, plan_right_id)?;
-                    map.add(*id, cond_id);
+                    map.add(id, cond_id);
                 }
                 Type::IsNull | Type::IsNotNull => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
@@ -535,7 +536,7 @@ impl Ast for AbstractSyntaxTree {
                     let plan_child_id = plan.as_row(map.get(*ast_child_id)?, &mut rows)?;
                     let op = Unary::from_node_type(&node.rule)?;
                     let unary_id = plan.add_unary(op, plan_child_id)?;
-                    map.add(*id, unary_id);
+                    map.add(id, unary_id);
                 }
                 Type::Between => {
                     // left BETWEEN center AND right
@@ -558,7 +559,7 @@ impl Ast for AbstractSyntaxTree {
                     let greater_eq_id = plan.add_cond(plan_left_id, Bool::GtEq, plan_center_id)?;
                     let less_eq_id = plan.add_cond(plan_left_id, Bool::LtEq, plan_right_id)?;
                     let and_id = plan.add_cond(greater_eq_id, Bool::And, less_eq_id)?;
-                    map.add(*id, and_id);
+                    map.add(id, and_id);
                     betweens.push(Between::new(plan_left_id, less_eq_id));
                 }
                 Type::Cast => {
@@ -602,7 +603,7 @@ impl Ast for AbstractSyntaxTree {
                         CastType::try_from(&ast_type.rule)
                     }?;
                     let cast_id = plan.add_cast(plan_child_id, cast_type)?;
-                    map.add(*id, cast_id);
+                    map.add(id, cast_id);
                 }
                 Type::Concat => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
@@ -614,14 +615,14 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_right_id = plan.as_row(map.get(*ast_right_id)?, &mut rows)?;
                     let concat_id = plan.add_concat(plan_left_id, plan_right_id)?;
-                    map.add(*id, concat_id);
+                    map.add(id, concat_id);
                 }
                 Type::Condition => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues("Condition has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
-                    map.add(*id, plan_child_id);
+                    map.add(id, plan_child_id);
                 }
                 Type::Function => {
                     if let Some((first, other)) = node.children.split_first() {
@@ -637,7 +638,7 @@ impl Ast for AbstractSyntaxTree {
                         let func = metadata.get_function(function_name)?;
                         if func.is_stable() {
                             let plan_func_id = plan.add_stable_function(func, plan_arg_list)?;
-                            map.add(*id, plan_func_id);
+                            map.add(id, plan_func_id);
                         } else {
                             // At the moment we don't support any non-stable functions.
                             // Later this code block should handle other function behaviors.
@@ -669,7 +670,7 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_cond_id = map.get(*ast_cond_id)?;
                     let plan_join_id = plan.add_join(plan_left_id, plan_right_id, plan_cond_id)?;
-                    map.add(*id, plan_join_id);
+                    map.add(id, plan_join_id);
                 }
                 Type::Selection => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
@@ -684,7 +685,7 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_filter_id = map.get(*ast_filter_id)?;
                     let plan_selection_id = plan.add_select(&[plan_child_id], plan_filter_id)?;
-                    map.add(*id, plan_selection_id);
+                    map.add(id, plan_selection_id);
                 }
                 Type::Projection => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
@@ -735,7 +736,7 @@ impl Ast for AbstractSyntaxTree {
                         }
                     }
                     let projection_id = plan.add_proj_internal(plan_child_id, &columns)?;
-                    map.add(*id, projection_id);
+                    map.add(id, projection_id);
                 }
                 Type::Except => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
@@ -747,7 +748,7 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_right_id = map.get(*ast_right_id)?;
                     let plan_except_id = plan.add_except(plan_left_id, plan_right_id)?;
-                    map.add(*id, plan_except_id);
+                    map.add(id, plan_except_id);
                 }
                 Type::UnionAll => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
@@ -762,7 +763,7 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_right_id = map.get(*ast_right_id)?;
                     let plan_union_all_id = plan.add_union_all(plan_left_id, plan_right_id)?;
-                    map.add(*id, plan_union_all_id);
+                    map.add(id, plan_union_all_id);
                 }
                 Type::ValuesRow => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
@@ -770,7 +771,7 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let values_row_id = plan.add_values_row(plan_child_id, &mut col_idx)?;
-                    map.add(*id, values_row_id);
+                    map.add(id, values_row_id);
                 }
                 Type::Values => {
                     let mut plan_children_ids: Vec<usize> = Vec::with_capacity(node.children.len());
@@ -779,7 +780,7 @@ impl Ast for AbstractSyntaxTree {
                         plan_children_ids.push(plan_child_id);
                     }
                     let plan_values_id = plan.add_values(plan_children_ids)?;
-                    map.add(*id, plan_values_id);
+                    map.add(id, plan_values_id);
                 }
                 Type::Insert => {
                     let ast_table_id = node.children.first().ok_or_else(|| {
@@ -836,7 +837,7 @@ impl Ast for AbstractSyntaxTree {
                         let plan_child_id = map.get(*ast_child_id)?;
                         plan.add_insert(relation, plan_child_id, &[])?
                     };
-                    map.add(*id, plan_insert_id);
+                    map.add(id, plan_insert_id);
                 }
                 Type::Explain => {
                     plan.mark_as_explain();
diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs
index 98fad9d621ad6c0e6e08ebecfb5dc9b56eff385c..0427f9def2248b63c3d7f30c462eb4182cab18e8 100644
--- a/sbroad-core/src/frontend/sql/ast.rs
+++ b/sbroad-core/src/frontend/sql/ast.rs
@@ -10,9 +10,9 @@ use std::mem::swap;
 
 use pest::iterators::Pair;
 use serde::{Deserialize, Serialize};
-use traversal::DftPost;
 
 use crate::errors::{Action, Entity, SbroadError};
+use crate::ir::tree::traversal::{PostOrder, EXPR_CAPACITY};
 
 /// Parse tree
 #[derive(Parser)]
@@ -382,10 +382,7 @@ impl AbstractSyntaxTree {
         for (parent_id, children_pos) in parents {
             let parent = self.nodes.get_node(parent_id)?;
             let child_id = *parent.children.get(children_pos).ok_or_else(|| {
-                SbroadError::NotFound(
-                    Entity::Node,
-                    format!("at expected position {}", children_pos),
-                )
+                SbroadError::NotFound(Entity::Node, format!("at expected position {children_pos}"))
             })?;
             let child = self.nodes.get_node(child_id)?;
             let mut node_id = *child.children.first().ok_or_else(|| {
@@ -841,9 +838,10 @@ impl AbstractSyntaxTree {
         // Traverse relational nodes in Post Order and then enter their subtrees
         // and map expressions to relational nodes.
         let top = self.get_top()?;
-        let tree = DftPost::new(&top, |node| self.nodes.ast_iter(node));
-        for (_, node_id) in tree {
-            let rel_node = self.nodes.get_node(*node_id)?;
+        let capacity = self.nodes.arena.len();
+        let mut tree = PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity);
+        for (_, node_id) in tree.iter(top) {
+            let rel_node = self.nodes.get_node(node_id)?;
             match rel_node.rule {
                 Type::Projection => {
                     let rel_id = rel_node.children.first().ok_or_else(|| {
@@ -852,11 +850,13 @@ impl AbstractSyntaxTree {
                         )
                     })?;
                     for top in rel_node.children.iter().skip(1) {
-                        let subtree = DftPost::new(top, |node| self.nodes.ast_iter(node));
-                        for (_, id) in subtree {
-                            let node = self.nodes.get_node(*id)?;
+                        let capacity = EXPR_CAPACITY * 3;
+                        let mut subtree =
+                            PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity);
+                        for (_, id) in subtree.iter(*top) {
+                            let node = self.nodes.get_node(id)?;
                             if let Type::Reference | Type::Asterisk = node.rule {
-                                if let Entry::Vacant(entry) = map.entry(*id) {
+                                if let Entry::Vacant(entry) = map.entry(id) {
                                     entry.insert(vec![*rel_id]);
                                 }
                             }
@@ -875,11 +875,13 @@ impl AbstractSyntaxTree {
                             "that is AST selection filter child with index 1".into(),
                         )
                     })?;
-                    let subtree = DftPost::new(filter, |node| self.nodes.ast_iter(node));
-                    for (_, id) in subtree {
-                        let node = self.nodes.get_node(*id)?;
+                    let capacity = EXPR_CAPACITY * 2;
+                    let mut subtree =
+                        PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity);
+                    for (_, id) in subtree.iter(*filter) {
+                        let node = self.nodes.get_node(id)?;
                         if node.rule == Type::Reference {
-                            if let Entry::Vacant(entry) = map.entry(*id) {
+                            if let Entry::Vacant(entry) = map.entry(id) {
                                 entry.insert(vec![*rel_id]);
                             }
                         }
@@ -904,11 +906,13 @@ impl AbstractSyntaxTree {
                         )
                     })?;
                     // ast_iter is not working here - we have to ignore sub-queries in the join condition.
-                    let subtree = DftPost::new(cond_id, |node| self.nodes.ast_iter(node));
-                    for (_, id) in subtree {
-                        let node = self.nodes.get_node(*id)?;
+                    let capacity = EXPR_CAPACITY * 4;
+                    let mut subtree =
+                        PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity);
+                    for (_, id) in subtree.iter(*cond_id) {
+                        let node = self.nodes.get_node(id)?;
                         if node.rule == Type::Reference {
-                            if let Entry::Vacant(entry) = map.entry(*id) {
+                            if let Entry::Vacant(entry) = map.entry(id) {
                                 entry.insert(vec![*left_id, *right_id]);
                             }
                         }
diff --git a/sbroad-core/src/frontend/sql/ast/tests.rs b/sbroad-core/src/frontend/sql/ast/tests.rs
index caf4329ade3bda1d90d981a31f57e2d49071672c..7440a8469b84e425b35d429ed2d16fb2d4a5f84f 100644
--- a/sbroad-core/src/frontend/sql/ast/tests.rs
+++ b/sbroad-core/src/frontend/sql/ast/tests.rs
@@ -3,7 +3,6 @@ use crate::frontend::Ast;
 use pretty_assertions::assert_eq;
 use std::fs;
 use std::path::Path;
-use traversal::DftPost;
 
 #[test]
 fn ast() {
@@ -91,61 +90,62 @@ fn traversal() {
     let query = r#"select a from t where a = 1"#;
     let ast = AbstractSyntaxTree::new(query).unwrap();
     let top = ast.top.unwrap();
-    let mut dft_post = DftPost::new(&top, |node| ast.nodes.ast_iter(node));
+    let mut dft_post = PostOrder::with_capacity(|node| ast.nodes.ast_iter(node), 64);
+    let mut iter = dft_post.iter(top);
 
-    let (_, table_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*table_id).unwrap();
+    let (_, table_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(table_id).unwrap();
     assert_eq!(node.rule, Type::Table);
 
-    let (_, scan_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*scan_id).unwrap();
+    let (_, scan_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(scan_id).unwrap();
     assert_eq!(node.rule, Type::Scan);
 
-    let (_, sel_name_a_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*sel_name_a_id).unwrap();
+    let (_, sel_name_a_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(sel_name_a_id).unwrap();
     assert_eq!(node.rule, Type::ColumnName);
 
-    let (_, a_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*a_id).unwrap();
+    let (_, a_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(a_id).unwrap();
     assert_eq!(node.rule, Type::Reference);
 
-    let (_, num_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*num_id).unwrap();
+    let (_, num_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(num_id).unwrap();
     assert_eq!(node.rule, Type::Unsigned);
 
-    let (_, eq_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*eq_id).unwrap();
+    let (_, eq_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(eq_id).unwrap();
     assert_eq!(node.rule, Type::Eq);
 
-    let (_, selection_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*selection_id).unwrap();
+    let (_, selection_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(selection_id).unwrap();
     assert_eq!(node.rule, Type::Selection);
 
-    let (_, prj_name_a_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*prj_name_a_id).unwrap();
+    let (_, prj_name_a_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(prj_name_a_id).unwrap();
     assert_eq!(node.rule, Type::ColumnName);
 
-    let (_, str_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*str_id).unwrap();
+    let (_, str_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(str_id).unwrap();
     assert_eq!(node.rule, Type::Reference);
 
-    let (_, alias_name_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*alias_name_id).unwrap();
+    let (_, alias_name_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(alias_name_id).unwrap();
     assert_eq!(node.rule, Type::AliasName);
 
-    let (_, alias_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*alias_id).unwrap();
+    let (_, alias_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(alias_id).unwrap();
     assert_eq!(node.rule, Type::Alias);
 
-    let (_, col_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*col_id).unwrap();
+    let (_, col_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(col_id).unwrap();
     assert_eq!(node.rule, Type::Column);
 
-    let (_, projection_id) = dft_post.next().unwrap();
-    let node = ast.nodes.get_node(*projection_id).unwrap();
+    let (_, projection_id) = iter.next().unwrap();
+    let node = ast.nodes.get_node(projection_id).unwrap();
     assert_eq!(node.rule, Type::Projection);
 
-    assert_eq!(None, dft_post.next());
+    assert_eq!(None, iter.next());
 }
 
 #[test]
diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs
index 5791ae48b3de9e77669cc6ccbdb515af11085f43..3da97977b43f083e0c9bc349143eab5af069807a 100644
--- a/sbroad-core/src/frontend/sql/ir.rs
+++ b/sbroad-core/src/frontend/sql/ir.rs
@@ -2,13 +2,13 @@ use std::collections::{HashMap, HashSet};
 
 use ahash::AHashMap;
 use tarantool::decimal::Decimal;
-use traversal::DftPost;
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::frontend::sql::ast::{ParseNode, Type};
 use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::operator::{Bool, Relational, Unary};
+use crate::ir::tree::traversal::{PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use crate::ir::value::double::Double;
 use crate::ir::value::Value;
 use crate::ir::{Node, Plan};
@@ -128,7 +128,7 @@ impl Translation {
         self.map.get(&old).copied().ok_or_else(|| {
             SbroadError::NotFound(
                 Entity::Node,
-                format!("(parse node) [{}] in translation map", old),
+                format!("(parse node) [{old}] in translation map"),
             )
         })
     }
@@ -156,28 +156,32 @@ impl Plan {
     fn gather_sq_for_replacement(&self) -> Result<HashSet<SubQuery, RepeatableState>, SbroadError> {
         let mut set: HashSet<SubQuery, RepeatableState> = HashSet::with_hasher(RepeatableState);
         let top = self.get_top()?;
-        let rel_post = DftPost::new(&top, |node| self.nodes.rel_iter(node));
+        let mut rel_post = PostOrder::with_capacity(|node| self.nodes.rel_iter(node), REL_CAPACITY);
         // Traverse expression trees of the selection and join nodes.
         // Gather all sub-queries in the boolean expressions there.
-        for (_, rel_id) in rel_post {
-            match self.get_node(*rel_id)? {
+        for (_, rel_id) in rel_post.iter(top) {
+            match self.get_node(rel_id)? {
                 Node::Relational(
                     Relational::Selection { filter: tree, .. }
                     | Relational::InnerJoin {
                         condition: tree, ..
                     },
                 ) => {
-                    let expr_post = DftPost::new(tree, |node| self.nodes.expr_iter(node, false));
-                    for (_, id) in expr_post {
+                    let capacity = self.nodes.len();
+                    let mut expr_post = PostOrder::with_capacity(
+                        |node| self.nodes.expr_iter(node, false),
+                        capacity,
+                    );
+                    for (_, id) in expr_post.iter(*tree) {
                         if let Node::Expression(Expression::Bool { left, right, .. }) =
-                            self.get_node(*id)?
+                            self.get_node(id)?
                         {
                             let children = &[*left, *right];
                             for child in children {
                                 if let Node::Relational(Relational::ScanSubQuery { .. }) =
                                     self.get_node(*child)?
                                 {
-                                    set.insert(SubQuery::new(*rel_id, *id, *child));
+                                    set.insert(SubQuery::new(rel_id, id, *child));
                                 }
                             }
                         }
@@ -319,10 +323,12 @@ impl Plan {
     }
 
     fn clone_expr_subtree(&mut self, top_id: usize) -> Result<usize, SbroadError> {
-        let subtree = DftPost::new(&top_id, |node| self.nodes.expr_iter(node, false));
-        let nodes = subtree.map(|(_, node_id)| *node_id).collect::<Vec<_>>();
         let mut map = HashMap::new();
-        for id in nodes {
+        let mut subtree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), EXPR_CAPACITY);
+        subtree.populate_nodes(top_id);
+        let nodes = subtree.take_nodes();
+        for (_, id) in nodes {
             let next_id = self.nodes.next_id();
             let mut expr = self.get_expression_node(id)?.clone();
             match expr {
diff --git a/sbroad-core/src/frontend/sql/tree.rs b/sbroad-core/src/frontend/sql/tree.rs
index 86ee3447207c93f7ae7ab392c3eaa64685cfc48e..eb93621f4049013f32473185a44a3572fe0df32a 100644
--- a/sbroad-core/src/frontend/sql/tree.rs
+++ b/sbroad-core/src/frontend/sql/tree.rs
@@ -6,20 +6,20 @@ use std::cell::RefCell;
 /// AST traversal iterator.
 #[derive(Debug)]
 pub struct AstIterator<'n> {
-    current: &'n usize,
+    current: usize,
     child: RefCell<usize>,
     nodes: &'n ParseNodes,
 }
 
 impl<'n> Iterator for AstIterator<'n> {
-    type Item = &'n usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        if let Some(node) = self.nodes.arena.get(*self.current) {
+        if let Some(node) = self.nodes.arena.get(self.current) {
             let step = *self.child.borrow();
             if step < node.children.len() {
                 *self.child.borrow_mut() += 1;
-                return node.children.get(step);
+                return node.children.get(step).copied();
             }
             None
         } else {
@@ -32,7 +32,7 @@ impl<'n> ParseNodes {
     /// Returns an iterator over the children of the node.
     #[allow(dead_code)]
     #[must_use]
-    pub fn ast_iter(&'n self, current: &'n usize) -> AstIterator<'n> {
+    pub fn ast_iter(&'n self, current: usize) -> AstIterator<'n> {
         AstIterator {
             current,
             child: RefCell::new(0),
diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs
index 5d3e9c11eb7365154b54ebb8677c1362779bd7d1..6c9204323648ebbf8a124418196e0c8e62b8f228 100644
--- a/sbroad-core/src/ir.rs
+++ b/sbroad-core/src/ir.rs
@@ -563,7 +563,7 @@ impl Plan {
                         .ok_or_else(|| {
                             SbroadError::NotFound(
                                 Entity::Column,
-                                format!("at position {} in row list", position),
+                                format!("at position {position} in row list"),
                             )
                         })?;
 
diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs
index 6921c38d236fff048a93d7f48104d7c26dfcc81a..91ae115bddb074768e4f9ce81a352f29ab12d6b5 100644
--- a/sbroad-core/src/ir/api/parameter.rs
+++ b/sbroad-core/src/ir/api/parameter.rs
@@ -1,6 +1,7 @@
 use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
+use crate::ir::tree::traversal::PostOrder;
 use crate::ir::value::Value;
 use crate::ir::{Node, Plan};
 use crate::otm::child_span;
@@ -8,7 +9,6 @@ use sbroad_proc::otm_child_span;
 
 use ahash::RandomState;
 use std::collections::{HashMap, HashSet};
-use traversal::DftPost;
 
 impl Plan {
     pub fn add_param(&mut self) -> usize {
@@ -47,9 +47,11 @@ impl Plan {
             return Ok(());
         }
 
+        let capacity = self.next_id();
+        let mut tree = PostOrder::with_capacity(|node| self.subtree_iter(node), capacity);
         let top_id = self.get_top()?;
-        let tree = DftPost::new(&top_id, |node| self.subtree_iter(node));
-        let nodes: Vec<usize> = tree.map(|(_, id)| *id).collect();
+        tree.populate_nodes(top_id);
+        let nodes = tree.take_nodes();
 
         // Transform parameters to values. The result values are stored in the
         // opposite to parameters order.
@@ -77,7 +79,7 @@ impl Plan {
 
         // Populate rows.
         let mut idx = value_ids.len();
-        for id in &nodes {
+        for (_, id) in &nodes {
             let node = self.get_node(*id)?;
             match node {
                 Node::Relational(rel) => match rel {
@@ -160,7 +162,7 @@ impl Plan {
 
         // Replace parameters in the plan.
         idx = value_ids.len();
-        for id in &nodes {
+        for (_, id) in &nodes {
             let node = self.get_mut_node(*id)?;
             match node {
                 Node::Relational(rel) => match rel {
@@ -240,7 +242,7 @@ impl Plan {
         }
 
         // Update values row output.
-        for id in nodes {
+        for (_, id) in nodes {
             if let Ok(Relational::ValuesRow { .. }) = self.get_relation_node(id) {
                 self.update_values_row(id)?;
             }
diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs
index 0cbfbc0f26cc37c58272f6ecc635e2eb5cef49d1..ae7c909c3b61f94dcc3724c0bb8cc78019cbdcf1 100644
--- a/sbroad-core/src/ir/distribution.rs
+++ b/sbroad-core/src/ir/distribution.rs
@@ -454,10 +454,7 @@ impl Plan {
         row_id: usize,
     ) -> Result<(), SbroadError> {
         let table: &Table = self.relations.get(table_name).ok_or_else(|| {
-            SbroadError::NotFound(
-                Entity::Table,
-                format!("{} among plan relations", table_name),
-            )
+            SbroadError::NotFound(Entity::Table, format!("{table_name} among plan relations"))
         })?;
         let mut new_key: Key = Key::new(Vec::new());
         let all_found = table.key.positions.iter().all(|pos| {
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index b3312ab4f497a85d358bf0fb0af1e86e231e98a3..3dc0bdd2f0dd4732b83d81e64b798934868fcfaa 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -3,7 +3,6 @@ use std::fmt::{Display, Formatter, Write as _};
 
 use itertools::Itertools;
 use serde::Serialize;
-use traversal::DftPost;
 
 use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::cast::Type as CastType;
@@ -15,6 +14,7 @@ use crate::ir::transformation::redistribution::{
 use crate::ir::Plan;
 
 use super::operator::{Bool, Unary};
+use super::tree::traversal::{PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use super::value::Value;
 
 #[derive(Debug, Serialize)]
@@ -57,11 +57,12 @@ impl Default for ColExpr {
 impl ColExpr {
     #[allow(dead_code)]
     fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
-        let dft_post = DftPost::new(&subtree_top, |node| plan.nodes.expr_iter(node, false));
         let mut stack: Vec<ColExpr> = Vec::new();
+        let mut dft_post =
+            PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, false), EXPR_CAPACITY);
 
-        for (_, id) in dft_post {
-            let current_node = plan.get_expression_node(*id)?;
+        for (_, id) in dft_post.iter(subtree_top) {
+            let current_node = plan.get_expression_node(id)?;
 
             match &current_node {
                 Expression::Cast { to, .. } => {
@@ -76,7 +77,7 @@ impl ColExpr {
                 Expression::Reference { position, .. } => {
                     let mut col_name = String::new();
 
-                    let rel_id: usize = *plan.get_relational_from_reference_node(*id)?;
+                    let rel_id: usize = *plan.get_relational_from_reference_node(id)?;
                     let rel_node = plan.get_relation_node(rel_id)?;
 
                     if let Some(name) = rel_node.scan_name(plan, *position)? {
@@ -163,14 +164,15 @@ impl Col {
     fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
         let mut column = Col::default();
 
-        let dft_post = DftPost::new(&subtree_top, |node| plan.nodes.expr_iter(node, true));
-        for (_, id) in dft_post {
-            let current_node = plan.get_expression_node(*id)?;
+        let mut dft_post =
+            PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, true), EXPR_CAPACITY);
+        for (_, id) in dft_post.iter(subtree_top) {
+            let current_node = plan.get_expression_node(id)?;
 
             if let Expression::Alias { name, .. } = &current_node {
                 column.alias = Some(name.to_string());
             } else {
-                column.col = ColExpr::new(plan, *id)?;
+                column.col = ColExpr::new(plan, id)?;
             }
         }
 
@@ -669,10 +671,10 @@ impl FullExplain {
         let mut stack: Vec<ExplainTreePart> = Vec::with_capacity(ir.nodes.relation_node_amount());
         let mut result = FullExplain::default();
 
-        let dft_post = DftPost::new(&top_id, |node| ir.nodes.rel_iter(node));
-        for (level, id) in dft_post {
+        let mut dft_post = PostOrder::with_capacity(|node| ir.nodes.rel_iter(node), REL_CAPACITY);
+        for (level, id) in dft_post.iter(top_id) {
             let mut current_node = ExplainTreePart::with_level(level);
-            let node = ir.get_relation_node(*id)?;
+            let node = ir.get_relation_node(id)?;
             current_node.current = match &node {
                 Relational::Except { .. } => {
                     if let (Some(right), Some(left)) = (stack.pop(), stack.pop()) {
@@ -789,7 +791,7 @@ impl FullExplain {
                                         let col_id = *col_list.get(*pos).ok_or_else(|| {
                                             SbroadError::NotFound(
                                                 Entity::Target,
-                                                format!("reference with position {}", pos),
+                                                format!("reference with position {pos}"),
                                             )
                                         })?;
                                         let col_name = ir
diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs
index ef8db278af2b05dc59d05b487115d3037650df61..242cb8f801260c608f0fc0549dd07f9c700cc0a3 100644
--- a/sbroad-core/src/ir/expression.rs
+++ b/sbroad-core/src/ir/expression.rs
@@ -9,12 +9,12 @@
 use ahash::RandomState;
 use serde::{Deserialize, Serialize};
 use std::collections::{HashMap, HashSet};
-use traversal::DftPost;
 
 use crate::errors::{Entity, SbroadError};
 use crate::ir::operator::{Bool, Relational};
 
 use super::distribution::Distribution;
+use super::tree::traversal::{PostOrder, EXPR_CAPACITY};
 use super::value::Value;
 use super::{operator, Node, Nodes, Plan};
 
@@ -247,7 +247,7 @@ impl Nodes {
     /// - name is empty
     pub fn add_alias(&mut self, name: &str, child: usize) -> Result<usize, SbroadError> {
         self.arena.get(child).ok_or_else(|| {
-            SbroadError::NotFound(Entity::Node, format!("from arena with index {}", child))
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {child}"))
         })?;
         if name.is_empty() {
             return Err(SbroadError::Invalid(
@@ -332,7 +332,7 @@ impl Nodes {
                 self.arena.get(*alias_node).ok_or_else(|| {
                     SbroadError::NotFound(
                         Entity::Node,
-                        format!("(Alias) from arena with index {}", alias_node),
+                        format!("(Alias) from arena with index {alias_node}"),
                     )
                 })?
             {
@@ -362,7 +362,7 @@ impl Nodes {
         child: usize,
     ) -> Result<usize, SbroadError> {
         self.arena.get(child).ok_or_else(|| {
-            SbroadError::NotFound(Entity::Node, format!("from arena with index {}", child))
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {child}"))
         })?;
         Ok(self.push(Node::Expression(Expression::Unary { op, child })))
     }
@@ -431,7 +431,7 @@ impl Plan {
                 } else {
                     return Err(SbroadError::NotFound(
                         Entity::Node,
-                        format!("pointed by target child {}", target_child),
+                        format!("pointed by target child {target_child}"),
                     ));
                 };
                 let relational_op = self.get_relation_node(child_node)?;
@@ -479,7 +479,7 @@ impl Plan {
                             let table = self.get_relation(relation).ok_or_else(|| {
                                 SbroadError::NotFound(
                                     Entity::Table,
-                                    format!("{} among the plan relations", relation),
+                                    format!("{relation} among the plan relations"),
                                 )
                             })?;
                             let sharding_column_pos = table.get_bucket_id_position()?;
@@ -599,7 +599,7 @@ impl Plan {
         if !all_found {
             return Err(SbroadError::NotFound(
                 Entity::Column,
-                format!("with name {:?}", col_names),
+                format!("with name {col_names:?}"),
             ));
         }
 
@@ -803,18 +803,21 @@ impl Plan {
         row_id: usize,
     ) -> Result<HashSet<usize, RandomState>, SbroadError> {
         let row = self.get_expression_node(row_id)?;
-        if let Expression::Row { .. } = row {
+        let capacity = if let Expression::Row { list, .. } = row {
+            list.len() * 2
         } else {
             return Err(SbroadError::Invalid(
                 Entity::Node,
                 Some("Node is not a row".into()),
             ));
-        }
-        let post_tree = DftPost::new(&row_id, |node| self.nodes.expr_iter(node, false));
-        let nodes: Vec<usize> = post_tree.map(|(_, id)| *id).collect();
+        };
+        let mut post_tree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), capacity);
+        post_tree.populate_nodes(row_id);
+        let nodes = post_tree.take_nodes();
         let mut rel_nodes: HashSet<usize, RandomState> =
             HashSet::with_capacity_and_hasher(nodes.len(), RandomState::new());
-        for id in nodes {
+        for (_, id) in nodes {
             let reference = self.get_expression_node(id)?;
             if let Expression::Reference {
                 targets, parent, ..
@@ -951,10 +954,11 @@ impl Plan {
         to_id: Option<usize>,
     ) -> Result<(), SbroadError> {
         let mut references: Vec<usize> = Vec::new();
-        let subtree = DftPost::new(&node_id, |node| self.nodes.expr_iter(node, false));
-        for (_, id) in subtree {
-            if let Node::Expression(Expression::Reference { .. }) = self.get_node(*id)? {
-                references.push(*id);
+        let mut subtree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), EXPR_CAPACITY);
+        for (_, id) in subtree.iter(node_id) {
+            if let Node::Expression(Expression::Reference { .. }) = self.get_node(id)? {
+                references.push(id);
             }
         }
         for id in references {
diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs
index 91538ca33b3de6870c5562734f175c74856f7f14..d5b43d94b62d6c3bea5312b56953b45f6dcbaa97 100644
--- a/sbroad-core/src/ir/operator.rs
+++ b/sbroad-core/src/ir/operator.rs
@@ -4,7 +4,6 @@
 
 use ahash::RandomState;
 use serde::{Deserialize, Serialize};
-use serde_yaml::mapping::Entry;
 use std::collections::{HashMap, HashSet};
 use std::fmt::{Display, Formatter};
 
@@ -12,11 +11,11 @@ use crate::errors::{Action, Entity, SbroadError};
 
 use super::expression::Expression;
 use super::transformation::redistribution::{DataGeneration, MotionPolicy};
+use super::tree::traversal::{BreadthFirst, EXPR_CAPACITY, REL_CAPACITY};
 use super::{Node, Nodes, Plan};
 use crate::collection;
 use crate::ir::distribution::{Distribution, KeySet};
 use crate::ir::relation::ColumnRole;
-use traversal::Bft;
 
 /// Binary operator returning Bool expression.
 #[derive(Serialize, Deserialize, PartialEq, Debug, Eq, Hash, Clone)]
@@ -467,10 +466,7 @@ impl Relational {
                 let output_row = plan.get_expression_node(self.output())?;
                 let list = output_row.get_row_list()?;
                 let col_id = *list.get(position).ok_or_else(|| {
-                    SbroadError::NotFound(
-                        Entity::Column,
-                        format!("at position {} of Row", position),
-                    )
+                    SbroadError::NotFound(Entity::Column, format!("at position {position} of Row"))
                 })?;
                 let col_node = plan.get_expression_node(col_id)?;
                 if let Expression::Alias { child, .. } = col_node {
@@ -670,7 +666,7 @@ impl Plan {
         }
         Err(SbroadError::NotFound(
             Entity::Table,
-            format!("{} among the plan relations", table),
+            format!("{table} among the plan relations"),
         ))
     }
 
@@ -709,10 +705,7 @@ impl Plan {
             {
                 // We'll need it later to update the condition expression (borrow checker).
                 let table = self.get_relation(relation).ok_or_else(|| {
-                    SbroadError::NotFound(
-                        Entity::Table,
-                        format!("{} among plan relations", relation),
-                    )
+                    SbroadError::NotFound(Entity::Table, format!("{relation} among plan relations"))
                 })?;
                 let sharding_column_pos = table.get_bucket_id_position()?;
 
@@ -727,12 +720,17 @@ impl Plan {
                 children.push(sq_id);
 
                 // Update references to the sub-query's output in the condition.
-                let condition_iter = Bft::new(&condition, |node| self.nodes.expr_iter(node, false));
-                let refs = condition_iter
+                let mut condition_tree = BreadthFirst::with_capacity(
+                    |node| self.nodes.expr_iter(node, false),
+                    EXPR_CAPACITY,
+                    EXPR_CAPACITY,
+                );
+                let refs = condition_tree
+                    .iter(condition)
                     .filter_map(|(_, id)| {
-                        let expr = self.get_expression_node(*id).ok();
+                        let expr = self.get_expression_node(id).ok();
                         if let Some(Expression::Reference { .. }) = expr {
-                            Some(*id)
+                            Some(id)
                         } else {
                             None
                         }
@@ -1141,9 +1139,13 @@ impl Plan {
     /// - Node returned by the relational iterator is not relational (bug)
     pub fn is_additional_child(&self, node_id: usize) -> Result<bool, SbroadError> {
         let top_id = self.get_top()?;
-        let rel_tree = Bft::new(&top_id, |node| self.nodes.rel_iter(node));
-        for (_, id) in rel_tree {
-            let rel = self.get_relation_node(*id)?;
+        let mut rel_tree = BreadthFirst::with_capacity(
+            |node| self.nodes.rel_iter(node),
+            REL_CAPACITY,
+            REL_CAPACITY,
+        );
+        for (_, id) in rel_tree.iter(top_id) {
+            let rel = self.get_relation_node(id)?;
             match rel {
                 Relational::Selection { children, .. } => {
                     if children.iter().skip(1).any(|&c| c == node_id) {
diff --git a/sbroad-core/src/ir/operator/tests.rs b/sbroad-core/src/ir/operator/tests.rs
index e3c12e58fc9f5313248c842a2f44d413a44e31df..5c126cda51eeac9407e2c4e10ca4f05b77582f46 100644
--- a/sbroad-core/src/ir/operator/tests.rs
+++ b/sbroad-core/src/ir/operator/tests.rs
@@ -115,7 +115,7 @@ fn projection() {
 
     // Try to build projection from the non-existing node
     assert_eq!(
-        SbroadError::NotFound(Entity::Node, format!("from arena with index 42")),
+        SbroadError::NotFound(Entity::Node, "from arena with index 42".to_string()),
         plan.add_proj(42, &["a"]).unwrap_err()
     );
 }
diff --git a/sbroad-core/src/ir/transformation.rs b/sbroad-core/src/ir/transformation.rs
index 654333e68852c998d1c3872cbe0ad27dbd2d990e..875ea634f2f648b8a79aefc41222eb1490d325c0 100644
--- a/sbroad-core/src/ir/transformation.rs
+++ b/sbroad-core/src/ir/transformation.rs
@@ -14,7 +14,8 @@ use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
 use crate::ir::Plan;
 use std::collections::HashMap;
-use traversal::DftPost;
+
+use super::tree::traversal::{PostOrder, EXPR_CAPACITY};
 
 impl Plan {
     /// Concatenates trivalents (boolean or NULL expressions) to the AND node.
@@ -90,9 +91,10 @@ impl Plan {
         f: &dyn Fn(&mut Plan, usize) -> Result<usize, SbroadError>,
     ) -> Result<(), SbroadError> {
         let top_id = self.get_top()?;
-        let ir_tree = DftPost::new(&top_id, |node| self.nodes.rel_iter(node));
-        let nodes: Vec<usize> = ir_tree.map(|(_, id)| *id).collect();
-        for id in &nodes {
+        let mut ir_tree = PostOrder::with_capacity(|node| self.nodes.rel_iter(node), EXPR_CAPACITY);
+        ir_tree.populate_nodes(top_id);
+        let nodes = ir_tree.take_nodes();
+        for (_, id) in &nodes {
             let rel = self.get_relation_node(*id)?;
             let (new_tree_id, old_tree_id) = match rel {
                 Relational::Selection {
@@ -140,9 +142,11 @@ impl Plan {
         ops: &[Bool],
     ) -> Result<usize, SbroadError> {
         let mut map: HashMap<usize, usize> = HashMap::new();
-        let subtree = DftPost::new(&top_id, |node| self.nodes.expr_iter(node, false));
-        let nodes: Vec<usize> = subtree.map(|(_, id)| *id).collect();
-        for id in &nodes {
+        let mut subtree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), EXPR_CAPACITY);
+        subtree.populate_nodes(top_id);
+        let nodes = subtree.take_nodes();
+        for (_, id) in &nodes {
             let expr = self.get_expression_node(*id)?;
             if let Expression::Bool { op, .. } = expr {
                 if ops.contains(op) || ops.is_empty() {
@@ -152,7 +156,7 @@ impl Plan {
             }
         }
         let mut new_top_id = top_id;
-        for id in &nodes {
+        for (_, id) in &nodes {
             let expr = self.get_mut_expression_node(*id)?;
             // For all expressions in the subtree tries to replace their children
             // with the new nodes from the map.
diff --git a/sbroad-core/src/ir/transformation/merge_tuples.rs b/sbroad-core/src/ir/transformation/merge_tuples.rs
index 9bbd1b1e4ec7203692b5805336887dac36e5505f..b4371fde046d048fa9641e7f08e73050bc3b4e30 100644
--- a/sbroad-core/src/ir/transformation/merge_tuples.rs
+++ b/sbroad-core/src/ir/transformation/merge_tuples.rs
@@ -14,11 +14,12 @@ use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::operator::Bool;
+use crate::ir::tree::traversal::BreadthFirst;
+use crate::ir::tree::traversal::EXPR_CAPACITY;
 use crate::ir::Plan;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 use std::collections::{hash_map::Entry, HashMap, HashSet};
-use traversal::Bft;
 
 fn call_expr_tree_merge_tuples(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_modify_and_chains(top_id, &call_build_and_chains, &call_as_plan)
@@ -216,8 +217,12 @@ impl Plan {
             }
             visited.insert(*id);
 
-            let tree_and = Bft::new(id, |node| self.nodes.and_iter(node));
-            let nodes_and: Vec<usize> = tree_and.map(|(_, id)| *id).collect();
+            let mut tree_and = BreadthFirst::with_capacity(
+                |node| self.nodes.and_iter(node),
+                EXPR_CAPACITY,
+                EXPR_CAPACITY,
+            );
+            let nodes_and: Vec<usize> = tree_and.iter(*id).map(|(_, id)| id).collect();
             let mut nodes_for_chain: Vec<usize> = Vec::with_capacity(nodes_and.len());
             for and_id in nodes_and {
                 let expr = self.get_expression_node(and_id)?;
@@ -273,8 +278,12 @@ impl Plan {
             -> Result<HashMap<usize, Chain, RepeatableState>, SbroadError>,
         f_to_plan: &dyn Fn(&Chain, &mut Plan) -> Result<usize, SbroadError>,
     ) -> Result<usize, SbroadError> {
-        let tree = Bft::new(&expr_id, |node| self.nodes.expr_iter(node, false));
-        let nodes: Vec<usize> = tree.map(|(_, id)| *id).collect();
+        let mut tree = BreadthFirst::with_capacity(
+            |node| self.nodes.expr_iter(node, false),
+            EXPR_CAPACITY,
+            EXPR_CAPACITY,
+        );
+        let nodes: Vec<usize> = tree.iter(expr_id).map(|(_, id)| id).collect();
         let chains = f_build_chains(self, &nodes)?;
 
         // Replace nodes' children with the merged tuples.
diff --git a/sbroad-core/src/ir/transformation/redistribution.rs b/sbroad-core/src/ir/transformation/redistribution.rs
index 537de38813a382ca7b769f8625bba70f9bf8efc0..5246b612242dca9274c1e7fa037df1d9addf7e5f 100644
--- a/sbroad-core/src/ir/transformation/redistribution.rs
+++ b/sbroad-core/src/ir/transformation/redistribution.rs
@@ -5,13 +5,13 @@ use serde::{Deserialize, Serialize};
 use std::cmp::Ordering;
 use std::collections::{hash_map::Entry, HashMap, HashSet};
 use std::fmt::{Display, Formatter};
-use traversal::{Bft, DftPost};
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::ir::distribution::{Distribution, Key, KeySet};
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
 use crate::ir::relation::Column;
+use crate::ir::tree::traversal::{BreadthFirst, PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use crate::ir::value::Value;
 use crate::ir::{Node, Plan};
 use crate::otm::child_span;
@@ -157,8 +157,9 @@ impl Plan {
     /// - plan doesn't contain the top node
     fn get_relational_nodes_dfs_post(&self) -> Result<Vec<usize>, SbroadError> {
         let top = self.get_top()?;
-        let post_tree = DftPost::new(&top, |node| self.nodes.rel_iter(node));
-        let nodes: Vec<usize> = post_tree.map(|(_, id)| *id).collect();
+        let mut post_tree =
+            PostOrder::with_capacity(|node| self.nodes.rel_iter(node), REL_CAPACITY);
+        let nodes: Vec<usize> = post_tree.iter(top).map(|(_, id)| id).collect();
         Ok(nodes)
     }
 
@@ -172,10 +173,11 @@ impl Plan {
     ) -> Result<Vec<usize>, SbroadError> {
         let mut nodes: Vec<usize> = Vec::new();
 
-        let post_tree = DftPost::new(&top, |node| self.nodes.expr_iter(node, false));
-        for (_, node) in post_tree {
+        let mut post_tree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), EXPR_CAPACITY);
+        for (_, id) in post_tree.iter(top) {
             // Append only booleans with row children.
-            if let Node::Expression(Expression::Bool { left, right, .. }) = self.get_node(*node)? {
+            if let Node::Expression(Expression::Bool { left, right, .. }) = self.get_node(id)? {
                 let left_is_row = matches!(
                     self.get_node(*left)?,
                     Node::Expression(Expression::Row { .. })
@@ -185,7 +187,7 @@ impl Plan {
                     Node::Expression(Expression::Row { .. })
                 );
                 if left_is_row && right_is_row {
-                    nodes.push(*node);
+                    nodes.push(id);
                 }
             }
         }
@@ -480,7 +482,7 @@ impl Plan {
         let mut children_set: HashSet<usize> = HashSet::new();
         for pos in &key.positions {
             let column_id = *row_map.get(pos).ok_or_else(|| {
-                SbroadError::NotFound(Entity::Column, format!("{} in row map {:?}", pos, row_map))
+                SbroadError::NotFound(Entity::Column, format!("{pos} in row map {row_map:?}"))
             })?;
             if let Expression::Reference { targets, .. } = self.get_expression_node(column_id)? {
                 if let Some(targets) = targets {
@@ -488,7 +490,7 @@ impl Plan {
                         let child_id = *join_children.get(*target).ok_or_else(|| {
                             SbroadError::NotFound(
                                 Entity::Target,
-                                format!("{} in join children {:?}", target, join_children),
+                                format!("{target} in join children {join_children:?}"),
                             )
                         })?;
                         children_set.insert(child_id);
@@ -690,11 +692,12 @@ impl Plan {
         })?;
         let mut inner_map: HashMap<usize, MotionPolicy> = HashMap::new();
         let mut new_inner_policy = MotionPolicy::Full;
-        let expr_tree = DftPost::new(&expr_id, |node| self.nodes.expr_iter(node, true));
-        for (_, node_id) in expr_tree {
-            let expr = self.get_expression_node(*node_id)?;
+        let mut expr_tree =
+            PostOrder::with_capacity(|node| self.nodes.expr_iter(node, true), EXPR_CAPACITY);
+        for (_, node_id) in expr_tree.iter(expr_id) {
+            let expr = self.get_expression_node(node_id)?;
             let bool_op = if let Expression::Bool { .. } = expr {
-                BoolOp::from_expr(self, *node_id)?
+                BoolOp::from_expr(self, node_id)?
             } else {
                 continue;
             };
@@ -702,7 +705,7 @@ impl Plan {
             // Try to improve full motion policy in the sub-queries.
             // We don't influence the inner child here, so the inner map is empty
             // for the current node id.
-            let sq_strategies = self.get_sq_node_strategies(rel_id, *node_id)?;
+            let sq_strategies = self.get_sq_node_strategies(rel_id, node_id)?;
             let sq_strategies_len = sq_strategies.len();
             for (id, policy) in sq_strategies {
                 strategy.insert(id, (policy, DataGeneration::None));
@@ -766,7 +769,7 @@ impl Plan {
                     ))
                 }
             };
-            inner_map.insert(*node_id, new_inner_policy.clone());
+            inner_map.insert(node_id, new_inner_policy.clone());
         }
         strategy.insert(inner_child, (new_inner_policy, DataGeneration::None));
         Ok(strategy)
@@ -1014,11 +1017,15 @@ impl Plan {
         // Gather motions (revert levels in bft)
         let mut motions: Vec<Vec<usize>> = Vec::new();
         let top = self.get_top()?;
-        let bft_tree = Bft::new(&top, |node| self.nodes.rel_iter(node));
+        let mut bft_tree = BreadthFirst::with_capacity(
+            |node| self.nodes.rel_iter(node),
+            REL_CAPACITY,
+            REL_CAPACITY,
+        );
         let mut map: HashMap<usize, usize> = HashMap::new();
         let mut max_level: usize = 0;
-        for (level, node) in bft_tree {
-            if let Node::Relational(Relational::Motion { .. }) = self.get_node(*node)? {
+        for (level, id) in bft_tree.iter(top) {
+            if let Node::Relational(Relational::Motion { .. }) = self.get_node(id)? {
                 let key: usize = match map.entry(level) {
                     Entry::Occupied(o) => *o.into_mut(),
                     Entry::Vacant(v) => {
@@ -1029,8 +1036,8 @@ impl Plan {
                     }
                 };
                 match motions.get_mut(key) {
-                    Some(list) => list.push(*node),
-                    None => motions.push(vec![*node]),
+                    Some(list) => list.push(id),
+                    None => motions.push(vec![id]),
                 }
             }
         }
diff --git a/sbroad-core/src/ir/tree.rs b/sbroad-core/src/ir/tree.rs
index d0df786dc320a14ac97a4234b94ae3c62f654467..d2ffd50990a59a84f501245e4eff18b73c77a2ee 100644
--- a/sbroad-core/src/ir/tree.rs
+++ b/sbroad-core/src/ir/tree.rs
@@ -4,7 +4,7 @@ use super::{Nodes, Plan};
 use std::cell::RefCell;
 
 trait TreeIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize;
+    fn get_current(&self) -> usize;
     fn get_child(&self) -> &RefCell<usize>;
     fn get_nodes(&self) -> &'nodes Nodes;
 }
@@ -22,10 +22,10 @@ pub enum Snapshot {
 }
 
 pub mod and;
-pub mod eq_class;
 pub mod expression;
 pub mod relation;
 pub mod subtree;
+pub mod traversal;
 
 #[cfg(test)]
 mod tests;
diff --git a/sbroad-core/src/ir/tree/and.rs b/sbroad-core/src/ir/tree/and.rs
index 1c6c8377f98fb2217f031f0352398089b220bbbe..3b4a080c14be0bb1ee0ed3a594de09fe26bbfffc 100644
--- a/sbroad-core/src/ir/tree/and.rs
+++ b/sbroad-core/src/ir/tree/and.rs
@@ -13,13 +13,13 @@ trait AndTreeIterator<'nodes>: TreeIterator<'nodes> {}
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug)]
 pub struct AndIterator<'n> {
-    current: &'n usize,
+    current: usize,
     child: RefCell<usize>,
     nodes: &'n Nodes,
 }
 
 impl<'nodes> TreeIterator<'nodes> for AndIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -36,7 +36,7 @@ impl<'nodes> AndTreeIterator<'nodes> for AndIterator<'nodes> {}
 
 impl<'n> Nodes {
     #[must_use]
-    pub fn and_iter(&'n self, current: &'n usize) -> AndIterator<'n> {
+    pub fn and_iter(&'n self, current: usize) -> AndIterator<'n> {
         AndIterator {
             current,
             child: RefCell::new(0),
@@ -46,15 +46,15 @@ impl<'n> Nodes {
 }
 
 impl<'n> Iterator for AndIterator<'n> {
-    type Item = &'n usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        and_next(self)
+        and_next(self).copied()
     }
 }
 
 fn and_next<'nodes>(iter: &mut impl AndTreeIterator<'nodes>) -> Option<&'nodes usize> {
-    let node = iter.get_nodes().arena.get(*iter.get_current());
+    let node = iter.get_nodes().arena.get(iter.get_current());
     if let Some(Node::Expression(Expression::Bool {
         left, op, right, ..
     })) = node
diff --git a/sbroad-core/src/ir/tree/eq_class.rs b/sbroad-core/src/ir/tree/eq_class.rs
deleted file mode 100644
index c8febae73d0f8fc8630c8ad0a48f3b7525cccae9..0000000000000000000000000000000000000000
--- a/sbroad-core/src/ir/tree/eq_class.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use std::cell::RefCell;
-
-use super::TreeIterator;
-use crate::ir::expression::Expression;
-use crate::ir::operator::Bool;
-use crate::ir::{Node, Nodes};
-
-trait EqClassTreeIterator<'nodes>: TreeIterator<'nodes> {}
-
-/// Children iterator for "and"-ed equivalent expressions.
-///
-/// The iterator returns the next child for the chained `Bool::And`
-/// and `Bool::Eq` nodes.
-#[allow(clippy::module_name_repetitions)]
-#[derive(Debug)]
-pub struct EqClassIterator<'n> {
-    current: &'n usize,
-    child: RefCell<usize>,
-    nodes: &'n Nodes,
-}
-
-impl<'nodes> TreeIterator<'nodes> for EqClassIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
-        self.current
-    }
-
-    fn get_child(&self) -> &RefCell<usize> {
-        &self.child
-    }
-
-    fn get_nodes(&self) -> &'nodes Nodes {
-        self.nodes
-    }
-}
-
-impl<'nodes> EqClassTreeIterator<'nodes> for EqClassIterator<'nodes> {}
-
-impl<'n> Nodes {
-    #[must_use]
-    pub fn eq_iter(&'n self, current: &'n usize) -> EqClassIterator<'n> {
-        EqClassIterator {
-            current,
-            child: RefCell::new(0),
-            nodes: self,
-        }
-    }
-}
-
-impl<'n> Iterator for EqClassIterator<'n> {
-    type Item = &'n usize;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        eq_class_next(self)
-    }
-}
-
-fn eq_class_next<'nodes>(iter: &mut impl EqClassTreeIterator<'nodes>) -> Option<&'nodes usize> {
-    if let Some(Node::Expression(Expression::Bool {
-        left, op, right, ..
-    })) = iter.get_nodes().arena.get(*iter.get_current())
-    {
-        if (*op != Bool::And) && (*op != Bool::Eq) {
-            return None;
-        }
-        let child_step = *iter.get_child().borrow();
-        if child_step == 0 {
-            *iter.get_child().borrow_mut() += 1;
-            return Some(left);
-        } else if child_step == 1 {
-            *iter.get_child().borrow_mut() += 1;
-            return Some(right);
-        }
-        None
-    } else {
-        None
-    }
-}
diff --git a/sbroad-core/src/ir/tree/expression.rs b/sbroad-core/src/ir/tree/expression.rs
index f46e4ebb6958568c21c83df29891dc74e870cd37..e37b87155e9abc3dd810c2c470c1d0b5eed238eb 100644
--- a/sbroad-core/src/ir/tree/expression.rs
+++ b/sbroad-core/src/ir/tree/expression.rs
@@ -15,7 +15,7 @@ trait ExpressionTreeIterator<'nodes>: TreeIterator<'nodes> {
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug)]
 pub struct ExpressionIterator<'n> {
-    current: &'n usize,
+    current: usize,
     child: RefCell<usize>,
     nodes: &'n Nodes,
     make_row_leaf: bool,
@@ -23,7 +23,7 @@ pub struct ExpressionIterator<'n> {
 
 impl<'n> Nodes {
     #[must_use]
-    pub fn expr_iter(&'n self, current: &'n usize, make_row_leaf: bool) -> ExpressionIterator<'n> {
+    pub fn expr_iter(&'n self, current: usize, make_row_leaf: bool) -> ExpressionIterator<'n> {
         ExpressionIterator {
             current,
             child: RefCell::new(0),
@@ -34,7 +34,7 @@ impl<'n> Nodes {
 }
 
 impl<'nodes> TreeIterator<'nodes> for ExpressionIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -54,17 +54,17 @@ impl<'nodes> ExpressionTreeIterator<'nodes> for ExpressionIterator<'nodes> {
 }
 
 impl<'n> Iterator for ExpressionIterator<'n> {
-    type Item = &'n usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        expression_next(self)
+        expression_next(self).copied()
     }
 }
 
 fn expression_next<'nodes>(
     iter: &mut impl ExpressionTreeIterator<'nodes>,
 ) -> Option<&'nodes usize> {
-    match iter.get_nodes().arena.get(*iter.get_current()) {
+    match iter.get_nodes().arena.get(iter.get_current()) {
         Some(Node::Expression(
             Expression::Alias { child, .. }
             | Expression::Cast { child, .. }
diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs
index 8bb0ff454c91607bd7d40c015d3748ce40ab64f5..543118d8179a86a966c522f944dc6d3f4f7cf0f1 100644
--- a/sbroad-core/src/ir/tree/relation.rs
+++ b/sbroad-core/src/ir/tree/relation.rs
@@ -11,14 +11,14 @@ trait RelationalTreeIterator<'nodes>: TreeIterator<'nodes> {}
 /// The iterator returns the next relational node in the plan tree.
 #[derive(Debug)]
 pub struct RelationalIterator<'n> {
-    current: &'n usize,
+    current: usize,
     child: RefCell<usize>,
     nodes: &'n Nodes,
 }
 
 impl<'n> Nodes {
     #[must_use]
-    pub fn rel_iter(&'n self, current: &'n usize) -> RelationalIterator<'n> {
+    pub fn rel_iter(&'n self, current: usize) -> RelationalIterator<'n> {
         RelationalIterator {
             current,
             child: RefCell::new(0),
@@ -28,7 +28,7 @@ impl<'n> Nodes {
 }
 
 impl<'nodes> TreeIterator<'nodes> for RelationalIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -44,17 +44,17 @@ impl<'nodes> TreeIterator<'nodes> for RelationalIterator<'nodes> {
 impl<'nodes> RelationalTreeIterator<'nodes> for RelationalIterator<'nodes> {}
 
 impl<'n> Iterator for RelationalIterator<'n> {
-    type Item = &'n usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        relational_next(self)
+        relational_next(self).copied()
     }
 }
 
 fn relational_next<'nodes>(
     iter: &mut impl RelationalTreeIterator<'nodes>,
 ) -> Option<&'nodes usize> {
-    match iter.get_nodes().arena.get(*iter.get_current()) {
+    match iter.get_nodes().arena.get(iter.get_current()) {
         Some(Node::Relational(
             Relational::Except { children, .. }
             | Relational::InnerJoin { children, .. }
diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs
index 05b79438d89c9d50b8687b3adf9969c2fba1b75c..672bf05be098873051b4d3371efaf7c5ec039366 100644
--- a/sbroad-core/src/ir/tree/subtree.rs
+++ b/sbroad-core/src/ir/tree/subtree.rs
@@ -15,13 +15,13 @@ trait SubtreePlanIterator<'plan>: PlanTreeIterator<'plan> {
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug)]
 pub struct SubtreeIterator<'plan> {
-    current: &'plan usize,
+    current: usize,
     child: RefCell<usize>,
     plan: &'plan Plan,
 }
 
 impl<'nodes> TreeIterator<'nodes> for SubtreeIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -51,16 +51,16 @@ impl<'plan> SubtreePlanIterator<'plan> for SubtreeIterator<'plan> {
 }
 
 impl<'plan> Iterator for SubtreeIterator<'plan> {
-    type Item = &'plan usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Latest)
+        subtree_next(self, &Snapshot::Latest).copied()
     }
 }
 
 impl<'plan> Plan {
     #[must_use]
-    pub fn subtree_iter(&'plan self, current: &'plan usize) -> SubtreeIterator<'plan> {
+    pub fn subtree_iter(&'plan self, current: usize) -> SubtreeIterator<'plan> {
         SubtreeIterator {
             current,
             child: RefCell::new(0),
@@ -75,13 +75,13 @@ impl<'plan> Plan {
 /// at the moment).
 #[derive(Debug)]
 pub struct FlashbackSubtreeIterator<'plan> {
-    current: &'plan usize,
+    current: usize,
     child: RefCell<usize>,
     plan: &'plan Plan,
 }
 
 impl<'nodes> TreeIterator<'nodes> for FlashbackSubtreeIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -111,19 +111,16 @@ impl<'plan> SubtreePlanIterator<'plan> for FlashbackSubtreeIterator<'plan> {
 }
 
 impl<'plan> Iterator for FlashbackSubtreeIterator<'plan> {
-    type Item = &'plan usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Oldest)
+        subtree_next(self, &Snapshot::Oldest).copied()
     }
 }
 
 impl<'plan> Plan {
     #[must_use]
-    pub fn flashback_subtree_iter(
-        &'plan self,
-        current: &'plan usize,
-    ) -> FlashbackSubtreeIterator<'plan> {
+    pub fn flashback_subtree_iter(&'plan self, current: usize) -> FlashbackSubtreeIterator<'plan> {
         FlashbackSubtreeIterator {
             current,
             child: RefCell::new(0),
@@ -135,13 +132,13 @@ impl<'plan> Plan {
 /// An iterator used while copying and execution plan subtree.
 #[derive(Debug)]
 pub struct ExecPlanSubtreeIterator<'plan> {
-    current: &'plan usize,
+    current: usize,
     child: RefCell<usize>,
     plan: &'plan Plan,
 }
 
 impl<'nodes> TreeIterator<'nodes> for ExecPlanSubtreeIterator<'nodes> {
-    fn get_current(&self) -> &'nodes usize {
+    fn get_current(&self) -> usize {
         self.current
     }
 
@@ -171,19 +168,16 @@ impl<'plan> SubtreePlanIterator<'plan> for ExecPlanSubtreeIterator<'plan> {
 }
 
 impl<'plan> Iterator for ExecPlanSubtreeIterator<'plan> {
-    type Item = &'plan usize;
+    type Item = usize;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Oldest)
+        subtree_next(self, &Snapshot::Oldest).copied()
     }
 }
 
 impl<'plan> Plan {
     #[must_use]
-    pub fn exec_plan_subtree_iter(
-        &'plan self,
-        current: &'plan usize,
-    ) -> ExecPlanSubtreeIterator<'plan> {
+    pub fn exec_plan_subtree_iter(&'plan self, current: usize) -> ExecPlanSubtreeIterator<'plan> {
         ExecPlanSubtreeIterator {
             current,
             child: RefCell::new(0),
@@ -197,7 +191,7 @@ fn subtree_next<'plan>(
     iter: &mut impl SubtreePlanIterator<'plan>,
     snapshot: &Snapshot,
 ) -> Option<&'plan usize> {
-    if let Some(child) = iter.get_nodes().arena.get(*iter.get_current()) {
+    if let Some(child) = iter.get_nodes().arena.get(iter.get_current()) {
         return match child {
             Node::Parameter => None,
             Node::Expression(exp) => match exp {
@@ -249,7 +243,7 @@ fn subtree_next<'plan>(
                         };
                         if let Ok(rel_id) = iter
                             .get_plan()
-                            .get_relational_from_reference_node(*iter.get_current())
+                            .get_relational_from_reference_node(iter.get_current())
                         {
                             match iter.get_plan().get_relation_node(*rel_id) {
                                 Ok(rel_node) if rel_node.is_subquery() || rel_node.is_motion() => {
diff --git a/sbroad-core/src/ir/tree/tests.rs b/sbroad-core/src/ir/tree/tests.rs
index f8ea53c900eb3d9e469a30cf7d264f660dd851c7..00295a4de0089a6216334138cbb8088d2c1d2ec9 100644
--- a/sbroad-core/src/ir/tree/tests.rs
+++ b/sbroad-core/src/ir/tree/tests.rs
@@ -1,9 +1,9 @@
 use crate::ir::operator::Bool;
 use crate::ir::relation::{Column, ColumnRole, Table, Type};
+use crate::ir::tree::traversal::{BreadthFirst, PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use crate::ir::value::Value;
 use crate::ir::{Expression, Plan};
 use pretty_assertions::assert_eq;
-use traversal::{Bft, DftPost, DftPre};
 
 #[test]
 fn expression_bft() {
@@ -25,55 +25,24 @@ fn expression_bft() {
         .add_bool(c1c2_and_c2c3, Bool::Or, c4_eq_c5)
         .unwrap();
 
-    let mut bft_tree = Bft::new(&top, |node| plan.nodes.expr_iter(node, true));
-    assert_eq!(bft_tree.next(), Some((0, &top)));
-    assert_eq!(bft_tree.next(), Some((1, &c1c2_and_c2c3)));
-    assert_eq!(bft_tree.next(), Some((1, &c4_eq_c5)));
-    assert_eq!(bft_tree.next(), Some((2, &c1_eq_c2)));
-    assert_eq!(bft_tree.next(), Some((2, &c2_eq_c3)));
-    assert_eq!(bft_tree.next(), Some((2, &c4)));
-    assert_eq!(bft_tree.next(), Some((2, &c5)));
-    assert_eq!(bft_tree.next(), Some((3, &c1)));
-    assert_eq!(bft_tree.next(), Some((3, &c2)));
-    assert_eq!(bft_tree.next(), Some((3, &c2)));
-    assert_eq!(bft_tree.next(), Some((3, &c3)));
-    assert_eq!(bft_tree.next(), None);
-}
-
-#[test]
-#[allow(clippy::similar_names)]
-fn and_chain_pre() {
-    // (((b1 or b2) and b3) and b4) and (b5 = (b6 = b7))
-
-    let mut plan = Plan::default();
-    let b1 = plan.nodes.add_const(Value::Boolean(true));
-    let b2 = plan.nodes.add_const(Value::Boolean(true));
-    let b3 = plan.nodes.add_const(Value::Boolean(true));
-    let b4 = plan.nodes.add_const(Value::Boolean(true));
-    let b5 = plan.nodes.add_const(Value::Boolean(true));
-    let b6 = plan.nodes.add_const(Value::Boolean(true));
-    let b7 = plan.nodes.add_const(Value::Boolean(true));
-
-    let b1_2 = plan.nodes.add_bool(b1, Bool::Or, b2).unwrap();
-    let b1_23 = plan.nodes.add_bool(b1_2, Bool::And, b3).unwrap();
-    let b1_234 = plan.nodes.add_bool(b1_23, Bool::And, b4).unwrap();
-    let b6b7 = plan.nodes.add_bool(b6, Bool::Eq, b7).unwrap();
-    let b5b6b7 = plan.nodes.add_bool(b5, Bool::Eq, b6b7).unwrap();
-    let top = plan.nodes.add_bool(b1_234, Bool::And, b5b6b7).unwrap();
-
-    let mut dft_pre = DftPre::new(&top, |node| plan.nodes.eq_iter(node));
-    assert_eq!(dft_pre.next(), Some((0, &top)));
-    assert_eq!(dft_pre.next(), Some((1, &b1_234)));
-    assert_eq!(dft_pre.next(), Some((2, &b1_23)));
-    assert_eq!(dft_pre.next(), Some((3, &b1_2)));
-    assert_eq!(dft_pre.next(), Some((3, &b3)));
-    assert_eq!(dft_pre.next(), Some((2, &b4)));
-    assert_eq!(dft_pre.next(), Some((1, &b5b6b7)));
-    assert_eq!(dft_pre.next(), Some((2, &b5)));
-    assert_eq!(dft_pre.next(), Some((2, &b6b7)));
-    assert_eq!(dft_pre.next(), Some((3, &b6)));
-    assert_eq!(dft_pre.next(), Some((3, &b7)));
-    assert_eq!(dft_pre.next(), None);
+    let mut bft_tree = BreadthFirst::with_capacity(
+        |node| plan.nodes.expr_iter(node, true),
+        EXPR_CAPACITY,
+        EXPR_CAPACITY,
+    );
+    let mut iter = bft_tree.iter(top);
+    assert_eq!(iter.next(), Some((0, top)));
+    assert_eq!(iter.next(), Some((1, c1c2_and_c2c3)));
+    assert_eq!(iter.next(), Some((1, c4_eq_c5)));
+    assert_eq!(iter.next(), Some((2, c1_eq_c2)));
+    assert_eq!(iter.next(), Some((2, c2_eq_c3)));
+    assert_eq!(iter.next(), Some((2, c4)));
+    assert_eq!(iter.next(), Some((2, c5)));
+    assert_eq!(iter.next(), Some((3, c1)));
+    assert_eq!(iter.next(), Some((3, c2)));
+    assert_eq!(iter.next(), Some((3, c2)));
+    assert_eq!(iter.next(), Some((3, c3)));
+    assert_eq!(iter.next(), None);
 }
 
 #[test]
@@ -112,12 +81,13 @@ fn relational_post() {
     let top = plan.get_top().unwrap();
 
     // Traverse the tree
-    let mut dft_post = DftPost::new(&top, |node| plan.nodes.rel_iter(node));
-    assert_eq!(dft_post.next(), Some((1, &scan_t1_id)));
-    assert_eq!(dft_post.next(), Some((2, &scan_t2_id)));
-    assert_eq!(dft_post.next(), Some((1, &selection_id)));
-    assert_eq!(dft_post.next(), Some((0, &union_id)));
-    assert_eq!(dft_post.next(), None);
+    let mut dft_post = PostOrder::with_capacity(|node| plan.nodes.rel_iter(node), REL_CAPACITY);
+    let mut iter = dft_post.iter(top);
+    assert_eq!(iter.next(), Some((1, scan_t1_id)));
+    assert_eq!(iter.next(), Some((2, scan_t2_id)));
+    assert_eq!(iter.next(), Some((1, selection_id)));
+    assert_eq!(iter.next(), Some((0, union_id)));
+    assert_eq!(iter.next(), None);
 }
 
 #[test]
@@ -180,28 +150,33 @@ fn selection_subquery_dfs_post() {
     let top = plan.get_top().unwrap();
 
     // Traverse relational nodes in the plan tree
-    let mut dft_post = DftPost::new(&top, |node| plan.nodes.rel_iter(node));
-    assert_eq!(dft_post.next(), Some((1, &scan_t1_id)));
-    assert_eq!(dft_post.next(), Some((4, &scan_t2_id)));
-    assert_eq!(dft_post.next(), Some((3, &selection_t2_id)));
-    assert_eq!(dft_post.next(), Some((2, &proj_id)));
-    assert_eq!(dft_post.next(), Some((1, &sq_id)));
-    assert_eq!(dft_post.next(), Some((0, &selection_t1_id)));
-    assert_eq!(dft_post.next(), None);
+    let mut dft_post = PostOrder::with_capacity(|node| plan.nodes.rel_iter(node), REL_CAPACITY);
+    let mut iter = dft_post.iter(top);
+    assert_eq!(iter.next(), Some((1, scan_t1_id)));
+    assert_eq!(iter.next(), Some((4, scan_t2_id)));
+    assert_eq!(iter.next(), Some((3, selection_t2_id)));
+    assert_eq!(iter.next(), Some((2, proj_id)));
+    assert_eq!(iter.next(), Some((1, sq_id)));
+    assert_eq!(iter.next(), Some((0, selection_t1_id)));
+    assert_eq!(iter.next(), None);
 
     // Traverse expression nodes in the selection t2 filter
-    let mut dft_post = DftPost::new(&eq_op, |node| plan.nodes.expr_iter(node, true));
-    assert_eq!(dft_post.next(), Some((1, &b)));
-    assert_eq!(dft_post.next(), Some((1, &const1)));
-    assert_eq!(dft_post.next(), Some((0, &eq_op)));
-    assert_eq!(dft_post.next(), None);
+    let mut dft_post =
+        PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, true), EXPR_CAPACITY);
+    let mut iter = dft_post.iter(eq_op);
+    assert_eq!(iter.next(), Some((1, b)));
+    assert_eq!(iter.next(), Some((1, const1)));
+    assert_eq!(iter.next(), Some((0, eq_op)));
+    assert_eq!(iter.next(), None);
 
     // Traverse expression nodes in the selection t1 filter
-    let mut dft_post = DftPost::new(&in_op, |node| plan.nodes.expr_iter(node, true));
-    assert_eq!(dft_post.next(), Some((1, &a)));
-    assert_eq!(dft_post.next(), Some((1, &c)));
-    assert_eq!(dft_post.next(), Some((0, &in_op)));
-    assert_eq!(dft_post.next(), None);
+    let mut dft_post =
+        PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, true), EXPR_CAPACITY);
+    let mut iter = dft_post.iter(in_op);
+    assert_eq!(iter.next(), Some((1, a)));
+    assert_eq!(iter.next(), Some((1, c)));
+    assert_eq!(iter.next(), Some((0, in_op)));
+    assert_eq!(iter.next(), None);
 }
 
 #[test]
@@ -257,16 +232,17 @@ fn subtree_dfs_post() {
         };
 
     // Traverse relational nodes in the plan tree
-    let mut dft_post = DftPost::new(&top, |node| plan.subtree_iter(node));
-    assert_eq!(dft_post.next(), Some((3, c_ref_id)));
-    assert_eq!(dft_post.next(), Some((2, alias_id)));
-    assert_eq!(dft_post.next(), Some((1, &proj_row_id)));
-    assert_eq!(dft_post.next(), Some((2, &scan_t1_id)));
-    assert_eq!(dft_post.next(), Some((4, &a_ref)));
-    assert_eq!(dft_post.next(), Some((3, &a)));
-    assert_eq!(dft_post.next(), Some((3, &const1)));
-    assert_eq!(dft_post.next(), Some((2, &eq_op)));
-    assert_eq!(dft_post.next(), Some((1, &selection_t1_id)));
-    assert_eq!(dft_post.next(), Some((0, &proj_id)));
-    assert_eq!(dft_post.next(), None);
+    let mut dft_post = PostOrder::with_capacity(|node| plan.subtree_iter(node), plan.next_id());
+    let mut iter = dft_post.iter(top);
+    assert_eq!(iter.next(), Some((3, *c_ref_id)));
+    assert_eq!(iter.next(), Some((2, *alias_id)));
+    assert_eq!(iter.next(), Some((1, proj_row_id)));
+    assert_eq!(iter.next(), Some((2, scan_t1_id)));
+    assert_eq!(iter.next(), Some((4, a_ref)));
+    assert_eq!(iter.next(), Some((3, a)));
+    assert_eq!(iter.next(), Some((3, const1)));
+    assert_eq!(iter.next(), Some((2, eq_op)));
+    assert_eq!(iter.next(), Some((1, selection_t1_id)));
+    assert_eq!(iter.next(), Some((0, proj_id)));
+    assert_eq!(iter.next(), None);
 }
diff --git a/sbroad-core/src/ir/tree/traversal.rs b/sbroad-core/src/ir/tree/traversal.rs
new file mode 100644
index 0000000000000000000000000000000000000000..19e67b9609f14f1bd29dc9cd57dec8c317b8a65d
--- /dev/null
+++ b/sbroad-core/src/ir/tree/traversal.rs
@@ -0,0 +1,106 @@
+use std::collections::VecDeque;
+
+pub const EXPR_CAPACITY: usize = 64;
+pub const REL_CAPACITY: usize = 32;
+
+pub type LevelNode = (usize, usize);
+
+pub struct PostOrder<F, I>
+where
+    F: FnMut(usize) -> I,
+    I: Iterator<Item = usize>,
+{
+    iter_children: F,
+    nodes: Vec<LevelNode>,
+}
+
+impl<F, I> PostOrder<F, I>
+where
+    F: FnMut(usize) -> I,
+    I: Iterator<Item = usize>,
+{
+    pub fn iter(&mut self, root: usize) -> impl Iterator<Item = LevelNode> {
+        self.populate_nodes(root);
+        self.take_nodes().into_iter()
+    }
+
+    pub fn new(iter_children: F, nodes: Vec<LevelNode>) -> Self {
+        Self {
+            iter_children,
+            nodes,
+        }
+    }
+
+    pub fn populate_nodes(&mut self, root: usize) {
+        self.traverse(root, 0);
+    }
+
+    pub fn take_nodes(&mut self) -> Vec<LevelNode> {
+        std::mem::take(&mut self.nodes)
+    }
+
+    fn traverse(&mut self, root: usize, level: usize) {
+        for child in (self.iter_children)(root) {
+            self.traverse(child, level + 1);
+        }
+        self.nodes.push((level, root));
+    }
+
+    pub fn with_capacity(iter_children: F, capacity: usize) -> Self {
+        Self {
+            iter_children,
+            nodes: Vec::with_capacity(capacity),
+        }
+    }
+}
+
+pub struct BreadthFirst<F, I>
+where
+    F: FnMut(usize) -> I,
+    I: Iterator<Item = usize>,
+{
+    iter_children: F,
+    queue: VecDeque<LevelNode>,
+    nodes: Vec<LevelNode>,
+}
+
+impl<F, I> BreadthFirst<F, I>
+where
+    F: FnMut(usize) -> I,
+    I: Iterator<Item = usize>,
+{
+    pub fn iter(&mut self, root: usize) -> impl Iterator<Item = LevelNode> {
+        self.populate_nodes(root);
+        self.take_nodes().into_iter()
+    }
+
+    pub fn new(iter_children: F, queue: VecDeque<LevelNode>, nodes: Vec<LevelNode>) -> Self {
+        Self {
+            iter_children,
+            queue,
+            nodes,
+        }
+    }
+
+    pub fn populate_nodes(&mut self, root: usize) {
+        self.queue.push_back((0, root));
+        while let Some((level, node)) = self.queue.pop_front() {
+            self.nodes.push((level, node));
+            for child in (self.iter_children)(node) {
+                self.queue.push_back((level + 1, child));
+            }
+        }
+    }
+
+    pub fn take_nodes(&mut self) -> Vec<LevelNode> {
+        std::mem::take(&mut self.nodes)
+    }
+
+    pub fn with_capacity(iter_children: F, node_capacity: usize, queue_capacity: usize) -> Self {
+        Self {
+            iter_children,
+            queue: VecDeque::with_capacity(queue_capacity),
+            nodes: Vec::with_capacity(node_capacity),
+        }
+    }
+}