diff --git a/src/query_engine/code_generator.hpp b/src/query_engine/code_generator.hpp
index b68e1cab5..3bf37f6a4 100644
--- a/src/query_engine/code_generator.hpp
+++ b/src/query_engine/code_generator.hpp
@@ -30,6 +30,9 @@ public:
                             {"stripped_hash", std::to_string(stripped_hash)},
                             {"query", query},
                             {"code", code_traverser.code}});
+        // TODO: ifndef
+        std::cout << generated << std::endl;
+
         utils::write_file(generated, path);
     }
 
diff --git a/src/query_engine/compiled/cpu/.gitignore b/src/query_engine/compiled/cpu/.gitignore
deleted file mode 100644
index 5e7d2734c..000000000
--- a/src/query_engine/compiled/cpu/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
diff --git a/src/query_engine/compiled/gpu/.gitignore b/src/query_engine/compiled/gpu/.gitignore
deleted file mode 100644
index 5e7d2734c..000000000
--- a/src/query_engine/compiled/gpu/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
diff --git a/src/query_engine/program_loader.hpp b/src/query_engine/program_loader.hpp
index 3a407ad63..54f26e004 100644
--- a/src/query_engine/program_loader.hpp
+++ b/src/query_engine/program_loader.hpp
@@ -61,6 +61,7 @@ public:
         code_libs.insert({{stripped.hash, code_lib}});
 
         // return instance of runnable code (ICodeCPU)
+        LOG_INFO("new code compiled and placed into cache");
         return QueryProgram(code_lib->instance(), std::move(stripped));
     }
 
diff --git a/src/query_engine/state_machine/cypher.hpp b/src/query_engine/state_machine/cypher.hpp
new file mode 100644
index 000000000..758ce0f86
--- /dev/null
+++ b/src/query_engine/state_machine/cypher.hpp
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <algorithm>
+#include <limits>
+#include <map>
+
+// TODO: remove
+#include "utils/underlying_cast.hpp"
+#include <iostream>
+
+// entities are nodes or relationship
+
+namespace entity_search
+{
+// returns maximum value for given template argument (for give type)
+template <typename T>
+constexpr T max()
+{
+    return std::numeric_limits<uint64_t>::max();
+}
+
+using cost_t = uint64_t;
+
+// TODO: rething
+// at least load hard coded values from somewhere
+constexpr cost_t internal_id_cost = 10;
+constexpr cost_t property_cost = 100;
+constexpr cost_t label_cost = 1000;
+constexpr cost_t max_cost = max<cost_t>();
+
+template <typename T>
+class SearchCost
+{
+public:
+    enum class SearchPlace : int
+    {
+        internal_id,
+        label_index,
+        property_index,
+        main_storage
+    };
+
+    using costs_t = std::map<SearchPlace, T>;
+    using cost_pair_t = std::pair<SearchPlace, T>;
+
+    SearchCost()
+    {
+        costs[SearchPlace::internal_id] = max<T>();
+        costs[SearchPlace::label_index] = max<T>();
+        costs[SearchPlace::property_index] = max<T>();
+        costs[SearchPlace::main_storage] = max<T>();
+    }
+
+    SearchCost(const SearchCost &other) = default;
+
+    SearchCost(SearchCost &&other) : costs(std::move(other.costs)) {}
+
+    void set(SearchPlace place, T cost) { costs[place] = cost; }
+
+    T get(SearchPlace place) const { return costs.at(place); }
+
+    SearchPlace min() const
+    {
+        auto min_pair = std::min_element(
+            costs.begin(), costs.end(),
+            [](const cost_pair_t &l, const cost_pair_t &r) -> bool {
+                return l.second < r.second;
+            });
+
+        if (min_pair->second == max_cost) return SearchPlace::main_storage;
+
+        return min_pair->first;
+    }
+
+private:
+    costs_t costs;
+};
+
+using search_cost_t = SearchCost<cost_t>;
+
+constexpr auto search_internal_id = search_cost_t::SearchPlace::internal_id;
+constexpr auto search_label_index = search_cost_t::SearchPlace::label_index;
+constexpr auto search_property_index =
+    search_cost_t::SearchPlace::property_index;
+constexpr auto search_main_storage = search_cost_t::SearchPlace::main_storage;
+}
+
+class CypherStateMachine
+{
+public:
+    void init_cost(const std::string &entity)
+    {
+        entity_search::search_cost_t search_cost;
+        _search_costs.emplace(entity, search_cost);
+    }
+
+    void search_cost(const std::string &entity,
+                     entity_search::search_cost_t::SearchPlace search_place,
+                     entity_search::cost_t cost)
+    {
+        if (_search_costs.find(entity) != _search_costs.end()) {
+            entity_search::search_cost_t search_cost;
+            _search_costs.emplace(entity, std::move(search_cost));
+        }
+
+        _search_costs[entity].set(search_place, cost);
+    }
+
+    entity_search::cost_t
+    search_cost(const std::string &entity,
+                entity_search::search_cost_t::SearchPlace search_place) const
+    {
+        return _search_costs.at(entity).get(search_place);
+    }
+
+    entity_search::search_cost_t::SearchPlace
+    min(const std::string &entity) const
+    {
+        return _search_costs.at(entity).min();
+    }
+
+private:
+    std::map<std::string, entity_search::search_cost_t> _search_costs;
+};
diff --git a/src/query_engine/traverser/code.hpp b/src/query_engine/traverser/code.hpp
index 5af6bfbc2..31a1c1d10 100644
--- a/src/query_engine/traverser/code.hpp
+++ b/src/query_engine/traverser/code.hpp
@@ -2,9 +2,54 @@
 
 #include <string>
 
+#include "query_engine/util.hpp"
+
 struct Code
 {
     void reset() { code = ""; }
 
     std::string code;
 };
+
+namespace code
+{
+
+// TODO: UNIT tests
+
+const std::string transaction_begin = "auto& t = db.tx_engine.begin();";
+
+const std::string transaction_commit = "t.commit();";
+
+const std::string vertex_accessor =
+    "auto vertex_accessor = db.graph.vertices.insert(t);";
+
+const std::string args_id = "auto id = args[{}]->as<Int32>();";
+
+const std::string vertex_accessor_args_id =
+    "auto vertex_accessor = db.graph.vertices.find(t, id.value);";
+
+const std::string vertex_set_property =
+    "vertex_accessor.property(\"{}\", args[{}]);";
+
+const std::string return_empty_result =
+    "return std::make_shared<QueryResult>();";
+
+const std::string create_label =
+    "auto &{} = db.graph.label_store.find_or_create(\"{}\");";
+
+const std::string add_label = "vertex_accessor.add_label({});";
+
+const std::string todo = "// TODO: {}";
+
+std::string debug_print_vertex_labels()
+{
+#ifdef DEBUG
+    return LINE("PRINT_PROPS(vertex_accessor.properties());") +
+           LINE("cout << \"LABELS:\" << endl;") +
+           LINE("for (auto label_ref : vertex_accessor.labels()) {") +
+           LINE("cout << label_ref.get() << endl;") + LINE("}");
+#else
+    return "";
+#endif
+}
+}
diff --git a/src/query_engine/traverser/read_traverser.hpp b/src/query_engine/traverser/read_traverser.hpp
index 8afb123f2..518b596ae 100644
--- a/src/query_engine/traverser/read_traverser.hpp
+++ b/src/query_engine/traverser/read_traverser.hpp
@@ -1,48 +1,99 @@
 #pragma once
 
+// TODO: wrap fmt format
+#include <fmt/format.h>
+
 #include "code.hpp"
 #include "cypher/visitor/traverser.hpp"
+#include "query_engine/state_machine/cypher.hpp"
 #include "query_engine/util.hpp"
+#include "utils/underlying_cast.hpp"
+
+using namespace entity_search;
 
 class ReadTraverser : public Traverser, public Code
 {
 private:
+    // TODO: change int to something bigger (int64_t)
+    std::map<std::string, int> internal_ids;
+
+    std::set<std::string> entities;
+    CypherStateMachine csm;
     uint32_t index{0};
+    std::map<std::string, uint32_t> property_indices;
 
 public:
     std::string code;
 
-    void visit(ast::Match& match) override
+    void update_entities(const std::string &name)
     {
-        code += line("auto& t = db.tx_engine.begin();");
+        std::cout << name << std::endl; 
+        if (entities.find(name) == entities.end()) {
+            csm.init_cost(name);
+            property_indices[name] = index++;
+            entities.insert(std::move(name));
+        }
+    }
 
+    void visit(ast::Match &match) override
+    {
+        code += LINE(code::transaction_begin);
         Traverser::visit(match);
     };
 
-    void visit(ast::Property& property) override
+    void visit(ast::Node &node) override
     {
-        code += line("auto id = args[" + std::to_string(index) + "]->as<Int32>();");
-        code += line("auto vertex_accessor = db.graph.vertices.find(t, id.value);");
+        auto identifier = node.idn;
+        if (identifier == nullptr) return;
 
-        ++index;
+        auto name = identifier->name;
+        update_entities(name);
+
+        if (node.labels != nullptr && node.labels->value != nullptr) 
+            csm.search_cost(name, search_label_index, label_cost);
     }
 
-    void visit(ast::InternalIdExpr& internal_id) override
+    void visit(ast::InternalIdExpr &internal_id_expr) override
     {
+        if (internal_id_expr.identifier == nullptr) return;
+        auto name = internal_id_expr.identifier->name;
+
+        // value has to exist
+        if (internal_id_expr.value == nullptr) {
+            // TODO: raise exception
+        }
+
+        update_entities(name);
+        csm.search_cost(name, search_internal_id, internal_id_cost);
     }
 
-    void visit(ast::Return& ret) override
+    void visit(ast::Return &ret) override
     {
-#ifdef DEBUG
-        // TODO: remove from here
-        code += line("PRINT_PROPS(vertex_accessor.properties());");
-        code += line("cout << \"LABELS:\" << endl;");
-        code += line("for (auto label_ref : vertex_accessor.labels()) {");
-        code += line("cout << label_ref.get() << endl;");
-        code += line("}");
-#endif
+        for (auto const &entity : entities) {
+            std::cout << entity << std::endl;
+            std::cout << underlying_cast(csm.min(entity)) << std::endl;
+            if (csm.min(entity) == search_internal_id) {
+                auto property_index = property_indices.at(entity);
+                code += LINE(
+                    fmt::format(code::args_id, std::to_string(property_index)));
+                code += LINE(code::vertex_accessor_args_id);
+                continue;
+            }
 
-        code += line("t.commit();");
-        code += line("return std::make_shared<QueryResult>();");
+            if (csm.min(entity) == search_label_index) {
+                code += LINE(fmt::format(code::todo, "search label index"));
+                continue;
+            }
+
+            if (csm.min(entity) == search_main_storage) {
+                code += LINE(fmt::format(code::todo, "return all vertices"));
+                continue;
+            }
+        }
+
+        code::debug_print_vertex_labels();
+
+        code += LINE(code::transaction_commit);
+        code += LINE(code::return_empty_result);
     }
 };
diff --git a/src/query_engine/traverser/write_traverser.hpp b/src/query_engine/traverser/write_traverser.hpp
index 9436ddba2..aadf7f23a 100644
--- a/src/query_engine/traverser/write_traverser.hpp
+++ b/src/query_engine/traverser/write_traverser.hpp
@@ -3,6 +3,7 @@
 #include <iostream>
 #include <map>
 #include <typeinfo>
+#include <fmt/format.h>
 
 #include "cypher/visitor/traverser.hpp"
 #include "query_engine/util.hpp"
@@ -19,8 +20,8 @@ public:
 
     void visit(ast::Create &create) override
     {
-        code += line("auto& t = db.tx_engine.begin();");
-        code += line("auto vertex_accessor = db.graph.vertices.insert(t);");
+        code += LINE(code::transaction_begin);
+        code += LINE(code::vertex_accessor);
 
         Traverser::visit(create);
     };
@@ -34,10 +35,8 @@ public:
 
         if (collecting_labels) {
             for (auto label : labels) {
-                code += line("auto &" + label +
-                             " = db.graph.label_store.find_or_create(\"" +
-                             label + "\");");
-                code += line("vertex_accessor.add_label(" + label + ");");
+                code += LINE(fmt::format(code::create_label, label, label));
+                code += LINE(fmt::format(code::add_label, label));
             }
             labels.clear();
             collecting_labels = false;
@@ -52,22 +51,13 @@ public:
     void visit(ast::Property &property) override
     {
         auto key = property.idn->name;
-
-        code += line("vertex_accessor.property(");
-        code += line("\t\"" + key + "\", args[" + std::to_string(index) + "]");
-        code += line(");");
-
+        code += LINE(fmt::format(code::vertex_set_property, key, index));
         ++index;
     }
 
     void visit(ast::Return &ret) override
     {
-#ifdef DEBUG
-        // TODO: remove from here
-        code += line("PRINT_PROPS(vertex_accessor.properties());");
-#endif
-
-        code += line("t.commit();");
-        code += line("return std::make_shared<QueryResult>();");
+        code += LINE(code::transaction_commit);
+        code += LINE(code::return_empty_result);
     }
 };
diff --git a/src/query_engine/util.hpp b/src/query_engine/util.hpp
index b84448298..f773dbd11 100644
--- a/src/query_engine/util.hpp
+++ b/src/query_engine/util.hpp
@@ -2,4 +2,4 @@
 
 #include <string>
 
-std::string line(std::string line) { return "\t\t" + line + "\n"; }
+std::string LINE(std::string line) { return "\t" + line + "\n"; }
diff --git a/tests/unit/cypher_state_machine.cpp b/tests/unit/cypher_state_machine.cpp
new file mode 100644
index 000000000..0ebdafb35
--- /dev/null
+++ b/tests/unit/cypher_state_machine.cpp
@@ -0,0 +1,36 @@
+#include <iostream>
+
+#include "query_engine/state_machine/cypher.hpp"
+#include "utils/assert.hpp"
+#include "utils/underlying_cast.hpp"
+
+using std::cout;
+using std::endl;
+
+int main()
+{
+    // initialize cypher state machine
+    CypherStateMachine csm;
+
+    // set cost for label index
+    auto test_cost = static_cast<uint64_t>(30);
+    csm.search_cost("n", entity_search::search_label_index, test_cost);
+
+    // check all costs
+    auto max_cost = entity_search::max<uint64_t>();
+    permanent_assert(csm.search_cost("n", entity_search::search_internal_id) ==
+                         max_cost,
+                     "Search internal id cost should be max cost value");
+    permanent_assert(csm.search_cost("n", entity_search::search_label_index) ==
+                         test_cost,
+                     "Search label index cost should be test cost value");
+    permanent_assert(
+        csm.search_cost("n", entity_search::search_property_index) == max_cost,
+        "Search property index should be max cost value");
+
+    // check minimum cost
+    permanent_assert(csm.min("n") == entity_search::search_label_index,
+                     "Search place should be label index");
+
+    return 0;
+}