diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000..6676a39
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,148 @@
+#include "list.h"
+#include "stdint.h"
+#include "stdlib.h"
+#include "string.h"
+#include "debug.h"
+
+#ifndef LIST_DEFAULT_RESERVE
+#define LIST_DEFAULT_RESERVE 4
+#endif
+
+List list_init(size_t element_size) {
+    List self = {
+        .element_size = element_size,
+        .cap = LIST_DEFAULT_RESERVE,
+        .len = 0,
+        .data = malloc(element_size * LIST_DEFAULT_RESERVE),
+    };
+
+    if(self.data == NULL) {
+        LOG_ERROR("Failed to allocate list with starting capacity of %d", LIST_DEFAULT_RESERVE);
+        self.cap = 0;
+    }
+
+    return self;
+}
+
+void list_empty(List* self) {
+    if(self->data == NULL || self->cap == 0)
+        return;
+    self->data = realloc(self->data, LIST_DEFAULT_RESERVE * self->element_size);
+    self->cap = LIST_DEFAULT_RESERVE;
+    self->len = 0;
+}
+
+void list_reserve(List* self, size_t at_least) {
+    if(at_least < self->cap)
+        return;
+
+    size_t new_cap = self->cap > 0 ? self->cap : LIST_DEFAULT_RESERVE;
+    while(at_least >= new_cap) {
+        new_cap *= 2;
+    }
+
+    void* new = realloc(self->data, new_cap * self->element_size);
+    ASSERT_RETURN(new != NULL,, "Failed to reserve space for %zu extra elements in list", new_cap);
+
+    self->data = new;
+    self->cap = new_cap;
+}
+
+static inline
+void* list_at_unchecked(List* self, size_t at) {
+    union {
+        uint8_t* as_byte;
+        void* as_void;
+    } data = {
+        .as_void = self->data
+    };
+
+    return data.as_byte + self->element_size * at;
+}
+
+void* list_at(List* self, size_t at) {
+    ASSERT_RETURN(at < self->len, NULL, "Index %zu out of bounds", at);
+    return list_at_unchecked(self, at);
+}
+
+size_t list_add(List* self, void* item) {
+    list_reserve(self, self->len + 1);
+    union {
+        uint8_t* as_byte;
+        void* as_void;
+    } data = {
+        .as_void = self->data
+    };
+
+    uint8_t* into = data.as_byte + self->element_size * self->len;
+
+    memcpy(into, item, self->element_size);
+    ++self->len;
+
+    return self->len - 1;
+}
+
+void list_insert(List* self, void* item, size_t at) {
+    ASSERT_RETURN(at < self->len,, "Index %zu out of bounds", at);
+
+    list_reserve(self, self->len + 1);
+
+    if(at == self->len - 1) {
+        list_add(self, item);
+        return;
+    }
+    
+    union {
+        uint8_t* as_byte;
+        void* as_void;
+    } data = {
+        .as_void = self->data
+    };
+    uint8_t* from = data.as_byte + self->element_size * at;
+    uint8_t* into = data.as_byte + self->element_size * (at + 1);
+    uint8_t* end = data.as_byte + self->element_size * self->len;
+    memmove(into, from, end - from);
+    memcpy(from, item, self->element_size);
+}
+
+void list_erase(List* self, size_t at) {
+    ASSERT_RETURN(at < self->len,, "Index %zu out of bounds", at);
+
+    union {
+        uint8_t* as_byte;
+        void* as_void;
+    } data = {
+        .as_void = self->data
+    };
+
+    uint8_t* into = data.as_byte + at * self->element_size;
+    uint8_t* from = data.as_byte + (at + 1) * self->element_size;
+
+    if(at < self->len - 1)
+        memmove(into, from, (self->len - at) * self->element_size);
+    --self->len;
+
+    size_t new_cap = self->cap;
+    while(new_cap > self->len) {
+        new_cap /= 2;
+    }
+    new_cap *= 2;
+
+
+    if(new_cap == self->cap)
+        return;
+
+    void* shrunk = realloc(self->data, new_cap * self->element_size);
+    ASSERT_RETURN(shrunk != NULL || new_cap == 0,, "Failed to shrink List to %zu", new_cap);
+
+    self->data = shrunk;
+    self->cap = new_cap;
+}
+
+void* list_iterator_begin(List* self) {
+    return list_at_unchecked(self, 0);
+}
+
+void* list_iterator_end(List* self) {
+    return list_at_unchecked(self, self->len);
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..4b2e37c
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,31 @@
+#ifndef _fencer_list_h
+#define _fencer_list_h
+
+#include "stddef.h"
+
+typedef struct List List;
+struct List {
+    void* data;
+    size_t cap;
+    size_t len;
+
+    size_t element_size;
+};
+
+extern List list_init(size_t element_size);
+extern void list_empty(List* list);
+
+extern void list_reserve(List* self, size_t at_least);
+extern void* list_at(List* list, size_t at);
+
+extern size_t list_add(List* self, void* item);
+extern void list_insert(List* self, void* item, size_t at);
+extern void list_erase(List* self, size_t at);
+
+extern void* list_iterator_begin(List* self);
+extern void* list_iterator_end(List* self);
+
+#define list_from_type(T) list_init(sizeof(T))
+#define list_foreach(T, iter, list) for(T* iter = list_iterator_begin(list); iter != (T*)list_iterator_end(list); ++iter)
+
+#endif // !_fencer_list_h