diff --git a/CMakeLists.txt b/CMakeLists.txt
index da8d5d76..3db0d7b2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -214,6 +214,7 @@ set(XEUS_CPP_SRC
     src/xparser.cpp
     src/xutils.cpp
     src/xmagics/os.cpp
+    src/xmagics/execution.cpp
 )
 
 if(NOT EMSCRIPTEN)
diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp
index 85aa07b1..6cc2dab0 100644
--- a/src/xinterpreter.cpp
+++ b/src/xinterpreter.cpp
@@ -17,6 +17,7 @@
 
 #include "xinput.hpp"
 #include "xinspect.hpp"
+#include "xmagics/execution.hpp"
 #include "xmagics/os.hpp"
 #ifndef EMSCRIPTEN
 #include "xmagics/xassist.hpp"
@@ -26,47 +27,66 @@
 
 using Args = std::vector<const char*>;
 
-void* createInterpreter(const Args &ExtraArgs = {}) {
-  Args ClangArgs = {/*"-xc++"*/"-v"}; // ? {"-Xclang", "-emit-llvm-only", "-Xclang", "-diagnostic-log-file", "-Xclang", "-", "-xc++"};
+void* createInterpreter(const Args& ExtraArgs = {})
+{
+    Args ClangArgs = {/*"-xc++"*/ "-v"};  // ? {"-Xclang", "-emit-llvm-only", "-Xclang",
+                                          // "-diagnostic-log-file", "-Xclang", "-", "-xc++"};
 #ifdef EMSCRIPTEN
-  ClangArgs.push_back("-std=c++20");
+    ClangArgs.push_back("-std=c++20");
 #else
-  if (std::find_if(ExtraArgs.begin(), ExtraArgs.end(), [](const std::string& s) {
-    return s == "-resource-dir";}) == ExtraArgs.end()) {
-    std::string resource_dir = Cpp::DetectResourceDir();
-    if (resource_dir.empty())
-      std::cerr << "Failed to detect the resource-dir\n";
-    ClangArgs.push_back("-resource-dir");
-    ClangArgs.push_back(resource_dir.c_str());
-  }
-  std::vector<std::string> CxxSystemIncludes;
-  Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes);
-  for (const std::string& CxxInclude : CxxSystemIncludes) {
-    ClangArgs.push_back("-isystem");
-    ClangArgs.push_back(CxxInclude.c_str());
-  }
+    if (std::find_if(
+            ExtraArgs.begin(),
+            ExtraArgs.end(),
+            [](const std::string& s)
+            {
+                return s == "-resource-dir";
+            }
+        )
+        == ExtraArgs.end())
+    {
+        std::string resource_dir = Cpp::DetectResourceDir();
+        if (resource_dir.empty())
+        {
+            std::cerr << "Failed to detect the resource-dir\n";
+        }
+        ClangArgs.push_back("-resource-dir");
+        ClangArgs.push_back(resource_dir.c_str());
+    }
+    std::vector<std::string> CxxSystemIncludes;
+    Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes);
+    for (const std::string& CxxInclude : CxxSystemIncludes)
+    {
+        ClangArgs.push_back("-isystem");
+        ClangArgs.push_back(CxxInclude.c_str());
+    }
 #endif
-  ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end());
-  // FIXME: We should process the kernel input options and conditionally pass
-  // the gpu args here.
-  return Cpp::CreateInterpreter(ClangArgs/*, {"-cuda"}*/);
+    ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end());
+    // FIXME: We should process the kernel input options and conditionally pass
+    // the gpu args here.
+    return Cpp::CreateInterpreter(ClangArgs /*, {"-cuda"}*/);
 }
 
 using namespace std::placeholders;
 
 namespace xcpp
 {
-    struct StreamRedirectRAII {
-      std::string &err;
-      StreamRedirectRAII(std::string &e) : err(e) {
-        Cpp::BeginStdStreamCapture(Cpp::kStdErr);
-        Cpp::BeginStdStreamCapture(Cpp::kStdOut);
-      }
-      ~StreamRedirectRAII() {
-        std::string out = Cpp::EndStdStreamCapture();
-        err = Cpp::EndStdStreamCapture();
-        std::cout << out;
-      }
+    struct StreamRedirectRAII
+    {
+        std::string& err;
+
+        StreamRedirectRAII(std::string& e)
+            : err(e)
+        {
+            Cpp::BeginStdStreamCapture(Cpp::kStdErr);
+            Cpp::BeginStdStreamCapture(Cpp::kStdOut);
+        }
+
+        ~StreamRedirectRAII()
+        {
+            std::string out = Cpp::EndStdStreamCapture();
+            err = Cpp::EndStdStreamCapture();
+            std::cout << out;
+        }
     };
 
     void interpreter::configure_impl()
@@ -101,14 +121,14 @@ __get_cxx_version ()
         return std::to_string(cxx_version);
     }
 
-    interpreter::interpreter(int argc, const char* const* argv) :
-        xmagics()
+    interpreter::interpreter(int argc, const char* const* argv)
+        : xmagics()
         , p_cout_strbuf(nullptr)
         , p_cerr_strbuf(nullptr)
         , m_cout_buffer(std::bind(&interpreter::publish_stdout, this, _1))
         , m_cerr_buffer(std::bind(&interpreter::publish_stderr, this, _1))
     {
-        //NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic)
+        // NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic)
         createInterpreter(Args(argv ? argv + 1 : argv, argv + argc));
         m_version = get_stdopt();
         redirect_output();
@@ -211,10 +231,11 @@ __get_cxx_version ()
             //
             // JupyterLab displays the "{ename}: {evalue}" if the traceback is
             // empty.
-            if (evalue.size() < 4) {
+            if (evalue.size() < 4)
+            {
                 ename = " ";
             }
-            std::vector<std::string> traceback({ename  + evalue});
+            std::vector<std::string> traceback({ename + evalue});
             if (!config.silent)
             {
                 publish_execution_error(ename, evalue, traceback);
@@ -257,7 +278,8 @@ __get_cxx_version ()
 
         Cpp::CodeComplete(results, code.c_str(), 1, _cursor_pos + 1);
 
-        return xeus::create_complete_reply(results /*matches*/,
+        return xeus::create_complete_reply(
+            results /*matches*/,
             cursor_pos - to_complete.length() /*cursor_start*/,
             cursor_pos /*cursor_end*/
         );
@@ -278,13 +300,17 @@ __get_cxx_version ()
 
     nl::json interpreter::is_complete_request_impl(const std::string& code)
     {
-        if (!code.empty() && code[code.size() - 1] == '\\') {
+        if (!code.empty() && code[code.size() - 1] == '\\')
+        {
             auto found = code.rfind('\n');
             if (found == std::string::npos)
+            {
                 found = -1;
+            }
             auto found1 = found++;
-            while (isspace(code[++found1])) ;
-            return xeus::create_is_complete_reply("incomplete", code.substr(found, found1-found));
+            while (isspace(code[++found1]))
+                ;
+            return xeus::create_is_complete_reply("incomplete", code.substr(found, found1 - found));
         }
 
         return xeus::create_is_complete_reply("complete");
@@ -358,11 +384,11 @@ __get_cxx_version ()
 
     void interpreter::init_preamble()
     {
-        //NOLINTBEGIN(cppcoreguidelines-owning-memory)
+        // NOLINTBEGIN(cppcoreguidelines-owning-memory)
         preamble_manager.register_preamble("introspection", std::make_unique<xintrospection>());
         preamble_manager.register_preamble("magics", std::make_unique<xmagics_manager>());
         preamble_manager.register_preamble("shell", std::make_unique<xsystem>());
-        //NOLINTEND(cppcoreguidelines-owning-memory)
+        // NOLINTEND(cppcoreguidelines-owning-memory)
     }
 
     void interpreter::init_magic()
@@ -373,6 +399,7 @@ __get_cxx_version ()
         // timeit(&m_interpreter));
         // preamble_manager["magics"].get_cast<xmagics_manager>().register_magic("python", pythonexec());
         preamble_manager["magics"].get_cast<xmagics_manager>().register_magic("file", writefile());
+        preamble_manager["magics"].get_cast<xmagics_manager>().register_magic("timeit", timeit());
 #ifndef EMSCRIPTEN
         preamble_manager["magics"].get_cast<xmagics_manager>().register_magic("xassist", xassist());
 #endif
diff --git a/src/xmagics/execution.cpp b/src/xmagics/execution.cpp
new file mode 100644
index 00000000..c586399a
--- /dev/null
+++ b/src/xmagics/execution.cpp
@@ -0,0 +1,262 @@
+/************************************************************************************
+ * Copyright (c) 2023, xeus-cpp contributors                                        *
+ *                                                                                  *
+ * Distributed under the terms of the BSD 3-Clause License.                         *
+ *                                                                                  *
+ * The full license is in the file LICENSE, distributed with this software.         *
+ ************************************************************************************/
+
+#include "execution.hpp"
+#include "xeus-cpp/xinterpreter.hpp"
+
+#include "../xparser.hpp"
+#include "clang/Interpreter/CppInterOp.h"
+
+namespace xcpp
+{
+    struct StreamRedirectRAII
+    {
+        std::string& err;
+
+        StreamRedirectRAII(std::string& e)
+            : err(e)
+        {
+            Cpp::BeginStdStreamCapture(Cpp::kStdErr);
+            Cpp::BeginStdStreamCapture(Cpp::kStdOut);
+        }
+
+        ~StreamRedirectRAII()
+        {
+            std::string out = Cpp::EndStdStreamCapture();
+            err = Cpp::EndStdStreamCapture();
+            std::cout << out;
+        }
+    };
+
+    int timeit::exec_counter = 0;
+
+    void timeit::operator()(const std::string& line)
+    {
+        std::string cline = line;
+        std::string ccell = "";
+        execute(cline, ccell);
+    }
+
+    void timeit::operator()(const std::string& line, const std::string& cell)
+    {
+        std::string cline = line;
+        std::string ccell = cell;
+        execute(cline, ccell);
+    }
+
+    void timeit::get_options(argparser& argpars)
+    {
+        argpars.add_description("Time execution of a C++ statement or expression");
+        argpars.add_argument("-n", "--number")
+            .help("execute the given statement n times in a loop. If this value is not given, a fitting value is chosen"
+            )
+            .default_value(0)
+            .scan<'i', int>();
+        argpars.add_argument("-r", "--repeat")
+            .help("repeat the loop iteration r times and take the best result")
+            .default_value(7)
+            .scan<'i', int>();
+        argpars.add_argument("-p", "--precision")
+            .help("use a precision of p digits to display the timing result")
+            .default_value(3)
+            .scan<'i', int>();
+        argpars.add_argument("expression").help("expression to be evaluated").remaining();
+        argpars.add_argument("-h", "--help")
+            .action(
+                [&](const std::string& /*unused*/)
+                {
+                    std::cout << argpars.help().str();
+                }
+            )
+            .default_value(false)
+            .help("shows help message")
+            .implicit_value(true)
+            .nargs(0);
+    }
+
+    std::string timeit::inner(std::size_t number, const std::string& code, int exec_counter) const
+    {
+        static std::size_t counter = 0;  // Ensure unique lambda names
+        std::string unique_id = std::to_string(counter++);
+        std::string timeit_code = "";
+        timeit_code += "auto user_code_" + unique_id + " = []() {\n";
+        timeit_code += "   " + code + "\n";
+        timeit_code += "};\n";
+        timeit_code += "get_elapsed_time_" + std::to_string(exec_counter) + "(" + std::to_string(number)
+                       + ", user_code_" + unique_id + ")\n";
+        return timeit_code;
+    }
+
+    std::string timeit::_format_time(double timespan, std::size_t precision) const
+    {
+        std::vector<std::string> units{"s", "ms", "us", "ns"};
+        std::vector<double> scaling{1, 1e3, 1e6, 1e9};
+        std::ostringstream output;
+        int order;
+
+        if (timespan > 0.0)
+        {
+            order = std::min(-static_cast<int>(std::floor(std::floor(std::log10(timespan)) / 3)), 3);
+        }
+        else
+        {
+            order = 3;
+        }
+        output.precision(precision);
+        output << timespan * scaling[order] << " " << units[order];
+        return output.str();
+    }
+
+    void timeit::execute(std::string& line, std::string& cell)
+    {
+        exec_counter++;
+        argparser argpars("timeit", XEUS_CPP_VERSION, argparse::default_arguments::none);
+        get_options(argpars);
+        argpars.parse(line);
+
+        int number = argpars.get<int>("-n");
+        int repeat = argpars.get<int>("-r");
+        int precision = argpars.get<int>("-p");
+
+        std::string code;
+        try
+        {
+            const auto& v = argpars.get<std::vector<std::string>>("expression");
+            for (const auto& s : v)
+            {
+                code += " " + s;
+            }
+        }
+        catch (std::logic_error& e)
+        {
+            if (trim(cell).empty() && (argpars["-h"] == false))
+            {
+                std::cerr << "No expression given to evaluate" << std::endl;
+            }
+        }
+
+        code += cell;
+        if (trim(code).empty())
+        {
+            return;
+        }
+
+        auto errorlevel = 0;
+        std::string ename;
+        std::string evalue;
+        std::string output;
+        std::string err;
+        bool hadError = false;
+
+        bool compilation_result = true;
+        compilation_result = Cpp::Process("#include <chrono>\n");
+        // Define the reusable timing function once
+        std::string timing_function = R"(
+                double get_elapsed_time_)"
+                                      + std::to_string(exec_counter)
+                                      + R"( (std::size_t num_iterations, void (*func)()) {
+                    auto _t2 = std::chrono::high_resolution_clock::now();
+                    for (std::size_t _i = 0; _i < num_iterations; ++_i) {
+                        func();
+                    }
+                    auto _t3 = std::chrono::high_resolution_clock::now();
+                    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(_t3 - _t2).count();
+                    return duration < 0 ? 0.0 : static_cast<double>(duration);
+                }
+            )";
+
+        compilation_result = Cpp::Process(timing_function.c_str());
+
+        try
+        {
+            StreamRedirectRAII R(err);
+            std::ostringstream buffer_out, buffer_err;
+            std::streambuf* old_cout = std::cout.rdbuf(buffer_out.rdbuf());
+            std::streambuf* old_cerr = std::cerr.rdbuf(buffer_err.rdbuf());
+            compilation_result = Cpp::Declare(code.c_str());
+            std::cout.rdbuf(old_cout);
+            std::cerr.rdbuf(old_cerr);
+        }
+        catch (std::exception& e)
+        {
+            errorlevel = 1;
+            ename = "Standard Exception: ";
+            evalue = e.what();
+            return;
+        }
+        catch (...)
+        {
+            errorlevel = 1;
+            ename = "Error: ";
+            return;
+        }
+
+        if (compilation_result)
+        {
+            errorlevel = 1;
+            ename = "Error: ";
+            evalue = "Compilation error! " + err;
+            std::cerr << err;
+            return;
+        }
+
+
+        if (number == 0)
+        {
+            for (std::size_t n = 0; n < 10; ++n)
+            {
+                number = std::pow(10, n);
+                std::string timeit_code = inner(number, code, exec_counter);
+                std::ostringstream buffer_out, buffer_err;
+                std::streambuf* old_cout = std::cout.rdbuf(buffer_out.rdbuf());
+                std::streambuf* old_cerr = std::cerr.rdbuf(buffer_err.rdbuf());
+                StreamRedirectRAII R(err);
+                auto res_ptr = Cpp::Evaluate(timeit_code.c_str(), &hadError);
+                std::cout.rdbuf(old_cout);
+                std::cerr.rdbuf(old_cerr);
+                output = std::to_string(res_ptr);
+                err += buffer_err.str();
+                double elapsed_time = std::stod(output) * 1e-6;
+                if (elapsed_time >= 0.2)
+                {
+                    break;
+                }
+            }
+        }
+
+        std::vector<double> all_runs;
+        double mean = 0;
+        double stdev = 0;
+        for (std::size_t r = 0; r < static_cast<std::size_t>(repeat); ++r)
+        {
+            std::string timeit_code = inner(number, code, exec_counter);
+            std::ostringstream buffer_out, buffer_err;
+            std::streambuf* old_cout = std::cout.rdbuf(buffer_out.rdbuf());
+            std::streambuf* old_cerr = std::cerr.rdbuf(buffer_err.rdbuf());
+            StreamRedirectRAII R(err);
+            auto res_ptr = Cpp::Evaluate(timeit_code.c_str(), &hadError);
+            std::cout.rdbuf(old_cout);
+            std::cerr.rdbuf(old_cerr);
+            output = std::to_string(res_ptr);
+            err += buffer_err.str();
+            double elapsed_time = std::stod(output) * 1e-6;
+            all_runs.push_back(elapsed_time / number);
+            mean += all_runs.back();
+        }
+        mean /= repeat;
+        for (std::size_t r = 0; r < static_cast<std::size_t>(repeat); ++r)
+        {
+            stdev += (all_runs[r] - mean) * (all_runs[r] - mean);
+        }
+        stdev = std::sqrt(stdev / repeat);
+
+        std::cout << _format_time(mean, precision) << " +- " << _format_time(stdev, precision);
+        std::cout << " per loop (mean +- std. dev. of " << repeat << " run" << ((repeat == 1) ? ", " : "s ");
+        std::cout << number << " loop" << ((number == 1) ? "" : "s") << " each)" << std::endl;
+    }
+}
\ No newline at end of file
diff --git a/src/xmagics/execution.hpp b/src/xmagics/execution.hpp
new file mode 100644
index 00000000..f2708f75
--- /dev/null
+++ b/src/xmagics/execution.hpp
@@ -0,0 +1,42 @@
+/************************************************************************************
+ * Copyright (c) 2023, xeus-cpp contributors                                        *
+ *                                                                                  *
+ * Distributed under the terms of the BSD 3-Clause License.                         *
+ *                                                                                  *
+ * The full license is in the file LICENSE, distributed with this software.         *
+ ************************************************************************************/
+
+#ifndef XMAGICS_EXECUTION_HPP
+#define XMAGICS_EXECUTION_HPP
+
+#include <cstddef>
+#include <string>
+
+#include "xeus-cpp/xmagics.hpp"
+#include "xeus-cpp/xoptions.hpp"
+
+namespace xcpp
+{
+    class timeit : public xmagic_line_cell
+    {
+    public:
+
+        XEUS_CPP_API
+        virtual void operator()(const std::string& line) override;
+
+        XEUS_CPP_API
+        virtual void operator()(const std::string& line, const std::string& cell) override;
+
+    public:
+
+        static int exec_counter;
+
+    private:
+
+        void get_options(argparser& argpars);
+        std::string inner(std::size_t number, const std::string& code, int exec_counter) const;
+        std::string _format_time(double timespan, std::size_t precision) const;
+        void execute(std::string& line, std::string& cell);
+    };
+}
+#endif
\ No newline at end of file
diff --git a/test/test_interpreter.cpp b/test/test_interpreter.cpp
index fd18eb35..5327ce1e 100644
--- a/test/test_interpreter.cpp
+++ b/test/test_interpreter.cpp
@@ -16,11 +16,12 @@
 #include "xeus-cpp/xoptions.hpp"
 #include "xeus-cpp/xeus_cpp_config.hpp"
 
-#include "../src/xparser.hpp"
-#include "../src/xsystem.hpp"
+#include "../src/xinspect.hpp"
+#include "../src/xmagics/execution.hpp"
 #include "../src/xmagics/os.hpp"
 #include "../src/xmagics/xassist.hpp"
-#include "../src/xinspect.hpp"
+#include "../src/xparser.hpp"
+#include "../src/xsystem.hpp"
 
 
 #include <iostream>
@@ -1053,3 +1054,48 @@ TEST_SUITE("file") {
         infile.close();
     }
 }
+
+TEST_SUITE("timeit")
+{
+    TEST_CASE("cell_check")
+    {
+        std::string line = "timeit";
+        std::string cell = "std::cout << 1 << std::endl;";
+        StreamRedirectRAII redirect(std::cout);
+        xcpp::timeit ti;
+        ti(line, cell);
+        std::string output = redirect.getCaptured();
+        REQUIRE(output.find("mean +- std. dev. of") != std::string::npos);
+    }
+
+    TEST_CASE("line_check")
+    {
+        std::string line = "timeit std::cout << 1 << std::endl;";
+        StreamRedirectRAII redirect(std::cout);
+        xcpp::timeit ti;
+        ti(line);
+        std::string output = redirect.getCaptured();
+        REQUIRE(output.find("mean +- std. dev. of") != std::string::npos);
+    }
+
+    TEST_CASE("arg_check")
+    {
+        std::string line = "timeit -n 10 -r 1 -p 6 std::cout << 1 << std::endl;";
+        StreamRedirectRAII redirect(std::cout);
+        xcpp::timeit ti;
+        ti(line);
+        std::string output = redirect.getCaptured();
+        REQUIRE(output.find("mean +- std. dev. of 1 run, 10 loops each") != std::string::npos);
+    }
+
+    TEST_CASE("fail_check")
+    {
+        std::string line = "timeit";
+        std::string cell = "int x = ";
+        StreamRedirectRAII redirect(std::cerr);
+        xcpp::timeit ti;
+        ti(line, cell);
+        std::string output = redirect.getCaptured();
+        REQUIRE(output.find("expected expression") != std::string::npos);
+    }
+}