diff --git a/examples/math.funk b/examples/math.funk
index c2e5a326ef3618af56a1c7a1903c6e1c90422757..fa007cf248ae882dc9cf9b6edfe835b3cc2c43a7 100644
--- a/examples/math.funk
+++ b/examples/math.funk
@@ -1,3 +1,2 @@
-1+1;
+numb test = 10;
 2+2;
-3+3;
diff --git a/include/ast/declaration/DeclarationNode.h b/include/ast/declaration/DeclarationNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..199c3859bb501b28b4612136e721ec486876dcb1
--- /dev/null
+++ b/include/ast/declaration/DeclarationNode.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "ast/Node.h"
+#include "ast/declaration/VariableNode.h"
+#include "ast/expression/LiteralNode.h"
+#include "parser/Scope.h"
+#include "token/Token.h"
+
+namespace funk
+{
+class DeclarationNode : public Node
+{
+public:
+    DeclarationNode(const SourceLocation& location, bool is_mutable, TokenType type, const String& identifier,
+        ExpressionNode* initializer);
+
+    DeclarationNode(const SourceLocation& location, bool is_mutable, TokenType type, const String& identifier);
+
+    ~DeclarationNode() override;
+
+    String get_identifier() const;
+    Node* get_initializer() const;
+    String to_s() const override;
+
+    String get_type() const;
+
+    Node* evaluate() const override;
+
+private:
+    bool is_mutable;
+    TokenType type;
+    String identifier;
+    ExpressionNode* initializer;
+    bool has_initializer;
+};
+
+} // namespace funk
diff --git a/include/ast/declaration/VariableNode.h b/include/ast/declaration/VariableNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e5b899d473e5f226329af662435de197e7ef3b9
--- /dev/null
+++ b/include/ast/declaration/VariableNode.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "ast/Node.h"
+#include "ast/expression/ExpressionNode.h"
+#include "token/TokenType.h"
+
+namespace funk
+{
+
+class VariableNode : public ExpressionNode
+{
+public:
+    VariableNode(const SourceLocation& location, const String& identifier, bool is_mutable, TokenType type,
+        ExpressionNode* value);
+    ~VariableNode() override;
+
+    Node* evaluate() const override;
+    String to_s() const override;
+    NodeValue get_value() const override;
+
+    bool get_mutable() const;
+    TokenType get_type() const;
+    const String& get_identifier() const;
+    ExpressionNode* get_value_node() const;
+    void set_value(ExpressionNode* new_value);
+
+private:
+    String identifier;
+    bool is_mutable;
+    TokenType type;
+    ExpressionNode* value;
+};
+
+} // namespace funk
\ No newline at end of file
diff --git a/include/parser/Parser.h b/include/parser/Parser.h
index 48d6d316c24e6c344cd39ea554ac944b783a191e..0ff11c0d60b82e519659d71f65c149c13eebd66b 100644
--- a/include/parser/Parser.h
+++ b/include/parser/Parser.h
@@ -13,6 +13,7 @@
 
 #include "ast/BlockNode.h"
 #include "ast/Node.h"
+#include "ast/declaration/DeclarationNode.h"
 #include "ast/expression/BinaryOpNode.h"
 #include "ast/expression/LiteralNode.h"
 #include "ast/expression/UnaryOpNode.h"
@@ -108,6 +109,12 @@ private:
      */
     Node* parse_statement();
 
+    /**
+     * @brief Parses a declaration
+     * @return Node* The AST node representing the declaration
+     */
+    Node* parse_declaration();
+
     /**
      * @brief Parses an expression
      * @return Node* The AST node representing the expression
diff --git a/source/ast/declaration/DeclarationNode.cc b/source/ast/declaration/DeclarationNode.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e553be171afe156714e0425cebf934e867cb191c
--- /dev/null
+++ b/source/ast/declaration/DeclarationNode.cc
@@ -0,0 +1,60 @@
+#include "ast/declaration/DeclarationNode.h"
+
+namespace funk
+{
+DeclarationNode::DeclarationNode(const SourceLocation& location, bool is_mutable, TokenType type,
+    const String& identifier, ExpressionNode* initializer) :
+    Node{location}, is_mutable{is_mutable}, type{type}, identifier{identifier}, initializer{initializer},
+    has_initializer{true}
+{
+}
+
+DeclarationNode::DeclarationNode(
+    const SourceLocation& location, bool is_mutable, TokenType type, const String& identifier) :
+    Node{location}, is_mutable{is_mutable}, type{type}, identifier{identifier}, initializer{nullptr},
+    has_initializer{false}
+{
+}
+
+DeclarationNode::~DeclarationNode()
+{
+    if (initializer) { delete initializer; }
+}
+
+String DeclarationNode::get_identifier() const
+{
+    return identifier;
+}
+
+Node* DeclarationNode::get_initializer() const
+{
+    return initializer;
+}
+
+String DeclarationNode::to_s() const
+{
+    if (has_initializer) { return "Declaration: " + identifier + " = " + initializer->to_s(); }
+    return "Declaration: " + identifier;
+}
+
+String DeclarationNode::get_type() const
+{
+    return token_type_to_s(type);
+}
+
+Node* DeclarationNode::evaluate() const
+{
+    Node* result = has_initializer ? initializer->evaluate() : new LiteralNode(get_location(), NodeValue{});
+
+    ExpressionNode* initial_value = dynamic_cast<ExpressionNode*>(result);
+    if (!initial_value)
+    {
+        if (result) { delete result; }
+        throw RuntimeError(get_location(), "Failed to evaluate initializer for '" + identifier + "'");
+    }
+
+    VariableNode* var = new VariableNode(get_location(), identifier, is_mutable, type, initial_value);
+    Scope::instance().add(identifier, var);
+    return var;
+}
+} // namespace funk
diff --git a/source/ast/declaration/VariableNode.cc b/source/ast/declaration/VariableNode.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0639e34998016008231c2c66f0a71eb0f6aec7d6
--- /dev/null
+++ b/source/ast/declaration/VariableNode.cc
@@ -0,0 +1,65 @@
+#include "ast/declaration/VariableNode.h"
+
+namespace funk
+{
+VariableNode::VariableNode(
+    const SourceLocation& location, const String& identifier, bool is_mutable, TokenType type, ExpressionNode* value) :
+    ExpressionNode{location}, identifier{identifier}, is_mutable{is_mutable}, type{type}, value{value}
+{
+}
+VariableNode::~VariableNode()
+{
+    if (value) { delete value; }
+    value = nullptr;
+}
+
+Node* VariableNode::evaluate() const
+{
+    return new VariableNode(get_location(), identifier, is_mutable, type, value);
+}
+
+String VariableNode::to_s() const
+{
+    return "Variable: " + identifier + " = " + value->to_s();
+}
+
+NodeValue VariableNode::get_value() const
+{
+    if (value)
+    {
+        NodeValue val = value->get_value();
+        return val;
+    }
+    return NodeValue{};
+}
+
+bool VariableNode::get_mutable() const
+{
+    return is_mutable;
+}
+
+TokenType VariableNode::get_type() const
+{
+    return type;
+}
+
+const String& VariableNode::get_identifier() const
+{
+    return identifier;
+}
+
+ExpressionNode* VariableNode::get_value_node() const
+{
+    return value;
+}
+
+void VariableNode::set_value(ExpressionNode* new_value)
+{
+    if (is_mutable)
+    {
+        if (value) { delete value; }
+        value = new_value;
+    }
+    else { throw RuntimeError(get_location(), "Cannot modify immutable variable '" + identifier + "'"); }
+}
+} // namespace funk
\ No newline at end of file
diff --git a/source/parser/Parser.cc b/source/parser/Parser.cc
index 2d3c518b50405f44a60b36f684ea0dad6b4e58ad..ac96590834acdd778014dcea107f43d82820bc35 100644
--- a/source/parser/Parser.cc
+++ b/source/parser/Parser.cc
@@ -63,6 +63,8 @@ bool Parser::match(TokenType expected)
 Node* Parser::parse_statement()
 {
     LOG_DEBUG("Parse statement");
+
+    Node* decl{parse_declaration()};
     Node* expr{parse_expression()};
 
     if (!match(TokenType::SEMICOLON))
@@ -73,9 +75,46 @@ Node* Parser::parse_statement()
         throw SyntaxError(error_loc, "Expected ';'");
     }
 
+    if (decl) { return decl; }
     return expr;
 }
 
+Node* Parser::parse_declaration()
+{
+    LOG_DEBUG("Parse declaration");
+
+    bool mut{false};
+    if (check(TokenType::MUT))
+    {
+        mut = true;
+        next();
+    }
+    if (check(TokenType::NUMB_TYPE) || check(TokenType::REAL_TYPE) || check(TokenType::BOOL_TYPE) ||
+        check(TokenType::CHAR_TYPE) || check(TokenType::TEXT_TYPE))
+    {
+        Token type{next()};
+        if (check(TokenType::IDENTIFIER))
+        {
+            Token identifier{next()};
+
+            if (match(TokenType::ASSIGN))
+            {
+                // needs to be parse_statement otherwise whines about semicolon dunno if I do it correctly
+                Node* expr{parse_statement()};
+                if (!expr) { throw SyntaxError(peek_prev().get_location(), "Expected expression after '='"); }
+
+                ExpressionNode* expr_node = dynamic_cast<ExpressionNode*>(expr);
+
+                return new DeclarationNode(
+                    type.get_location(), mut, type.get_type(), identifier.get_lexeme(), expr_node);
+            }
+            return new DeclarationNode(type.get_location(), mut, type.get_type(), identifier.get_lexeme());
+        }
+    }
+
+    return nullptr;
+}
+
 Node* Parser::parse_expression()
 {
     LOG_DEBUG("Parse expression");