**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()`.