diff --git a/include/ast/control/ControlNode.h b/include/ast/control/ControlNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..e959921ce0b4dae6d9c7979407262a590b503354
--- /dev/null
+++ b/include/ast/control/ControlNode.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ast/Node.h"
+
+namespace funk
+{
+class ControlNode : public Node
+{
+public:
+    ControlNode(const SourceLocation& loc);
+    ~ControlNode() override;
+};
+} // namespace funk
diff --git a/include/ast/control/IfNode.h b/include/ast/control/IfNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..b692f5ff3b6c98792d2c0172a94c2f9cacdfc857
--- /dev/null
+++ b/include/ast/control/IfNode.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "ast/BlockNode.h"
+#include "ast/control/ControlNode.h"
+#include "ast/expression/ExpressionNode.h"
+
+namespace funk
+{
+class IfNode : public ControlNode
+{
+public:
+    IfNode(ExpressionNode* condition, BlockNode* body, Node* else_branch = nullptr);
+    ~IfNode() override;
+    Node* evaluate() const override;
+    String to_s() const override;
+
+private:
+    ExpressionNode* condition;
+    BlockNode* body;
+    Node* else_branch;
+};
+} // namespace funk
diff --git a/include/ast/control/WhileNode.h b/include/ast/control/WhileNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e4a3b905fd75fb662a633e65907fbbbf497691d
--- /dev/null
+++ b/include/ast/control/WhileNode.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "ast/BlockNode.h"
+#include "ast/control/ControlNode.h"
+#include "ast/expression/ExpressionNode.h"
+
+namespace funk
+{
+class WhileNode : public ControlNode
+{
+public:
+    WhileNode(ExpressionNode* condition, BlockNode* body);
+    ~WhileNode() override;
+    Node* evaluate() const override;
+    String to_s() const override;
+
+private:
+    ExpressionNode* condition;
+    BlockNode* body;
+};
+} // namespace funk
diff --git a/include/parser/Parser.h b/include/parser/Parser.h
index 1f60ce4e5337c646d3885ac5b0f70323c473cc0d..f08c405cfeb126866e9cd803818d3869a9a77534 100644
--- a/include/parser/Parser.h
+++ b/include/parser/Parser.h
@@ -6,18 +6,20 @@
  */
 #pragma once
 
-#include "lexer/Lexer.h"
-#include "parser/Scope.h"
-#include "token/Token.h"
-#include "utils/Common.h"
-
 #include "ast/BlockNode.h"
 #include "ast/Node.h"
+#include "ast/control/IfNode.h"
+#include "ast/control/WhileNode.h"
 #include "ast/declaration/DeclarationNode.h"
 #include "ast/expression/BinaryOpNode.h"
 #include "ast/expression/CallNode.h"
 #include "ast/expression/LiteralNode.h"
 #include "ast/expression/UnaryOpNode.h"
+#include "lexer/Lexer.h"
+#include "logging/LogMacros.h"
+#include "parser/Scope.h"
+#include "token/Token.h"
+#include "utils/Common.h"
 
 namespace funk
 {
@@ -116,6 +118,30 @@ private:
      */
     Node* parse_declaration();
 
+    /**
+     * @brief Parses a block of statements
+     * @return Node* The AST node representing the block
+     */
+    Node* parse_block();
+
+    /**
+     * @brief Parses a control flow statement
+     * @return Node* The AST node representing the control flow statement
+     */
+    Node* parse_control();
+
+    /**
+     * @brief Parses an if statement
+     * @return Node* The AST node representing the if statement
+     */
+    Node* parse_if();
+
+    /**
+     * @brief Parses a while loop
+     * @return Node* The AST node representing the while loop
+     */
+    Node* parse_while();
+
     /**
      * @brief Parses an expression
      * @return Node* The AST node representing the expression
diff --git a/source/ast/control/ControlNode.cc b/source/ast/control/ControlNode.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7b952df11561a07cbb3999391e6c12f1eebddeaa
--- /dev/null
+++ b/source/ast/control/ControlNode.cc
@@ -0,0 +1,8 @@
+#include "ast/control/ControlNode.h"
+
+namespace funk
+{
+ControlNode::ControlNode(const SourceLocation& loc) : Node(loc) {}
+
+ControlNode::~ControlNode() = default;
+} // namespace funk
diff --git a/source/ast/control/IfNode.cc b/source/ast/control/IfNode.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c1badfb5dd8a002fea21ee5b6911a48c54136bbd
--- /dev/null
+++ b/source/ast/control/IfNode.cc
@@ -0,0 +1,32 @@
+#include "ast/control/IfNode.h"
+
+namespace funk
+{
+IfNode::IfNode(ExpressionNode* condition, BlockNode* body, Node* else_branch) :
+    ControlNode(condition->get_location()), condition(condition), body(body), else_branch(else_branch)
+{
+}
+
+IfNode::~IfNode()
+{
+    delete condition;
+    delete body;
+
+    if (else_branch) { delete else_branch; }
+}
+
+Node* IfNode::evaluate() const
+{
+    LOG_DEBUG("Evaluating if statement");
+    if (dynamic_cast<ExpressionNode*>(condition->evaluate())->get_value().cast<bool>()) { return body->evaluate(); }
+    else if (else_branch) { return else_branch->evaluate(); }
+    return nullptr;
+}
+
+String IfNode::to_s() const
+{
+    String result{"if ( " + condition->to_s() + " ) {\n" + body->to_s() + "}"};
+    if (else_branch) { result += "\n} else {\n" + else_branch->to_s() + "}"; }
+    return result;
+}
+} // namespace funk
diff --git a/source/ast/control/WhileNode.cc b/source/ast/control/WhileNode.cc
new file mode 100644
index 0000000000000000000000000000000000000000..66cc08249bf69c9e9dd0d76cc2f6bddbd4cc47cc
--- /dev/null
+++ b/source/ast/control/WhileNode.cc
@@ -0,0 +1,27 @@
+#include "ast/control/WhileNode.h"
+
+namespace funk
+{
+WhileNode::WhileNode(ExpressionNode* condition, BlockNode* body) :
+    ControlNode(condition->get_location()), condition(condition), body(body)
+{
+}
+
+WhileNode::~WhileNode()
+{
+    delete condition;
+    delete body;
+}
+
+Node* WhileNode::evaluate() const
+{
+    LOG_DEBUG("Evaluating while loop");
+    while (dynamic_cast<ExpressionNode*>(condition->evaluate())->get_value().cast<bool>()) { body->evaluate(); }
+    return nullptr;
+}
+
+String WhileNode::to_s() const
+{
+    return "while ( " + condition->to_s() + " ) {\n" + body->to_s() + "}";
+}
+} // namespace funk
diff --git a/source/main.cc b/source/main.cc
index d8c959c59b28cefc38aeac312497fe33ea37f425..458f95049f83271f288b99e601b14db0e78356b6 100644
--- a/source/main.cc
+++ b/source/main.cc
@@ -122,7 +122,8 @@ void process_file(const String& file, const Config& config)
         LOG_DEBUG("Evaluating AST...");
         Node* res{ast->evaluate()};
         LOG_DEBUG("AST evaluated!");
-        LOG_INFO("Result: " + res->to_s());
+        if (!res) { LOG_INFO("Result: nullptr"); }
+        else { LOG_INFO("Result: " + res->to_s()); }
     }
     catch (const FunkError& e)
     {
diff --git a/source/parser/Parser.cc b/source/parser/Parser.cc
index 877c7dfb55e146e4c5949fe88e9ba44bc63d3038..6cb9c26acfd130b967412016effbabe4f420de98 100644
--- a/source/parser/Parser.cc
+++ b/source/parser/Parser.cc
@@ -1,5 +1,4 @@
 #include "parser/Parser.h"
-#include "logging/LogMacros.h"
 
 namespace funk
 {
@@ -64,8 +63,14 @@ Node* Parser::parse_statement()
 {
     LOG_DEBUG("Parse statement");
 
+    Node* control{parse_control()};
+    if (control) { return control; }
+
     Node* decl{parse_declaration()};
+    if (decl) { return decl; }
+
     Node* expr{parse_expression()};
+    if (!expr) { throw SyntaxError(peek().get_location(), "Expected statement"); }
 
     if (!match(TokenType::SEMICOLON))
     {
@@ -75,7 +80,6 @@ Node* Parser::parse_statement()
         throw SyntaxError(error_loc, "Expected ';'");
     }
 
-    if (decl) { return decl; }
     return expr;
 }
 
@@ -115,6 +119,55 @@ Node* Parser::parse_declaration()
     return nullptr;
 }
 
+Node* Parser::parse_block()
+{
+    LOG_DEBUG("Parse block");
+    if (!match(TokenType::L_BRACE)) { throw SyntaxError(peek().get_location(), "Expected '{'"); }
+
+    Vector<Node*> statements{};
+    while (!check(TokenType::R_BRACE) && !done()) { statements.push_back(parse_statement()); }
+
+    if (!match(TokenType::R_BRACE)) { throw SyntaxError(peek().get_location(), "Expected '}'"); }
+
+    return new BlockNode(statements.at(0)->get_location(), statements);
+}
+
+Node* Parser::parse_control()
+{
+    LOG_DEBUG("Parse control flow");
+    if (match(TokenType::IF)) { return parse_if(); }
+    if (match(TokenType::WHILE)) { return parse_while(); }
+    return nullptr;
+}
+
+Node* Parser::parse_if()
+{
+    LOG_DEBUG("Parse if");
+    if (!match(TokenType::L_PAR)) { throw SyntaxError(peek().get_location(), "Expected '('"); }
+
+    ExpressionNode* condition{dynamic_cast<ExpressionNode*>(parse_expression())};
+    if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')'"); }
+
+    BlockNode* body{dynamic_cast<BlockNode*>(parse_block())};
+    Node* else_branch{nullptr};
+    if (match(TokenType::ELSE))
+    {
+        if (match(TokenType::IF)) { else_branch = parse_if(); }
+        else { else_branch = parse_block(); }
+    }
+    return new IfNode(condition, body, else_branch);
+}
+
+Node* Parser::parse_while()
+{
+    LOG_DEBUG("Parse while loop");
+    if (!match(TokenType::L_PAR)) { throw SyntaxError(peek().get_location(), "Expected '('"); }
+    ExpressionNode* condition{dynamic_cast<ExpressionNode*>(parse_expression())};
+    if (!match(TokenType::R_PAR)) { throw SyntaxError(peek().get_location(), "Expected ')'"); }
+    BlockNode* body{dynamic_cast<BlockNode*>(parse_block())};
+    return new WhileNode(condition, body);
+}
+
 Node* Parser::parse_expression()
 {
     LOG_DEBUG("Parse expression");