Is there any memory leak for the implementation of array of char arrays Basically, I declared a char array to hold a maximum size of 100 char arrays.

#include &lt;stdlib.h&gt;
#include &lt;memory.h&gt;
#include &lt;stdio.h&gt;

int add_to_urls(char **urls, char *url) {
    for (int i = 0; i &lt; 100; i++) {
        if (urls[i] == NULL) {
            urls[i] = malloc(sizeof(strlen(url) + 1));
            strcpy(urls[i], url);
            break;
        } else {
            //make sure we don't add same site twice
            if (strcmp(urls[i], url) == 0) {
                printf("%s already in list\n", urls[i]);
            }
        }
    }
    return 0;
}

void free_urls(char **urls) {
    for (int i = 0; i &lt; 100; i++) {
        if (urls[i] != NULL) {
            free(urls[i]);
        } else break;
    }
}

int main() {
    char *urls[100] = {NULL};

    char *url = "www.youtube.com";
    add_to_urls(urls, url);

    url = "www.reddit.com";
    add_to_urls(urls, url);

    url = "www.reddit.com";
    add_to_urls(urls, url);

    printf("URL: %s\n", urls[0]);
    printf("URL: %s\n", urls[1]);

    free_urls(urls);

    return 0;
}


I'm not sure if there's any conventions of making such an array container, as I am learning how to use c. The priority is to check if there's any memory leak, what I did is iteratively release the urls[i] and urls itself will be automatically handled (I suspect).

The second thing is about improvements, what do you think might be inappropriate causing any potential troubles?
You made an error here:
        urls[i] = malloc(sizeof(strlen(url) + 1));

strlen(url) + 1 is of type size_t, so you will allocate enough space for that, which could well be less than the amount you actually wanted (strlen(url) + 1).
If we fix that bug, and also change url to const char* (because it points at string literals, which are always const), then we get a clean Valgrind run from the test program.

What does the return value from add_to_urls indicate?  If you meant it to be true when the url was added, then the break should be replaced with return 1.
Always check the result of malloc() is not null.
Errors and warnings should be printed to the standard error stream, not to stdout.
Your main() doesn't check that we didn't add a duplicate.  In fact we did, but we never printed it.
Avoid writing constants (such as 100) in separate places if they need to agree with each other.
Instead of the break in free_urls(), we can make the test part of the loop's condition.

Improved code
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

int add_to_urls(char **urls, const char *url, size_t max_list)
{
    for (size_t i = 0;  i &lt; max_list;  i++) {
        if (!urls[i]) {
            urls[i] = malloc(strlen(url) + 1);
            if (!urls[i]) {
                fprintf(stderr, &quot;Failed to alloc for %s\n&quot;, url);
                return 0; /* failed allocation */
            }
            strcpy(urls[i], url);
            return 1;           /* we added it */
        }
        //make sure we don't add same site twice
        if (strcmp(urls[i], url) == 0) {
            fprintf(stderr, &quot;%s already in list\n&quot;, url);
            return 0;           /* already present */
        }
    }
    return 0; /* ran out of room */
}

void free_urls(char **urls, size_t max_list) {
    for (size_t i = 0;  i &lt; max_list &amp;&amp; urls[i];  i++) {
        free(urls[i]);
    }
}
    
int main() {
    char *urls[100] = {NULL};
    const size_t url_len = sizeof urls / sizeof *urls;

    add_to_urls(urls, &quot;www.youtube.com&quot;, url_len);
    add_to_urls(urls, &quot;www.reddit.com&quot;, url_len);
    add_to_urls(urls, &quot;www.reddit.com&quot;, url_len);

    for (size_t i = 0;  i &lt; url_len &amp;&amp; urls[i];  i++) {
        printf(&quot;URL[%zd]: %s\n&quot;, i, urls[i]);
    }

    free_urls(urls, url_len);
}