**No documentation* .f file deserve overall documentation. Consider users should be able to understand what these functions do without access to the .c file. **Bug: `al_contains()`** `al_contains()` returns 0 when `data` is not found or if `data != 0`. Did OP want `al_contains()` to return the index when found? **Unclear return** `al_find()` returns -1 when `data` not found, yet `-1` is a valid `data`. Consider a different approach. **Use `const`** For functions that do not modify the state of the list: Example: //int al_is_valid_index(struct array_list *list, int index); int al_is_valid_index(const struct array_list *list, int index); **Include first** In `array_list.c`,code `#include "array_list.h"` first to test that it does not rely on any `<.h>` files that it does not include itself. **Why 10?** Zero is a better choice for `AL_INITIAL_CAPACITY`. Often the list are used, there are many empty ones. Zero is a natural choice. If concerned about a lot of initial re-allocations, simply jump use to 10 when first needed. **Name space** Code uses `al_...` and `array_list...`. Use one, **`static`??** `static const int AL_INITIAL_CAPACITY = 16;` serves no purpose in the .h file. **Order?** With so many functions, consider alphabetizing the order in both .v and .h/ **Mixed indexing types** Code using `int` and `size_t` for the array indexing and sizing type. Suggest `size_t` throughout. **Pedantic growth** Insure `capacity + capacity / 2` does not overflow. **More functions** Consider: A right-size function to reduce the allocation to the needed size. With `al_find()` and `al_find_last()`, perhaps a `find_next(... index)` to pickup after `al_find()`.