Improving an itoa function I wrote an itoa function yesterday, which based on feedback I am now working on improving. How does the below look? Are there any ways in which I can improve it?
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;limits.h&gt;

char* itoa(int number, size_t sz, char buffer[sz])
{
    // do error handling and edge cases
    if (buffer == NULL || sz == 0)
        return NULL;
    else if (number == 0) 
        return strcpy(buffer, &quot;0&quot;);

    char tmp[sz];
    int idx = 0;
    bool is_negative = (number &lt; 0);
    if (!is_negative) number = -number; // so we can handle INT_MIN

    for (; number != 0; number/=10, idx++) {
        if (sz-- == 0) return NULL;
        tmp[idx] = '0' - (number % 10); // negative version of number + '0'
    }
    if (is_negative) {
        if (sz-- == 0) return NULL;
        tmp[idx++] = '-';
    }

    // reverse
    for (int i=0; i&lt;idx; i++)
        buffer[idx-i-1] = tmp[i];
    buffer[idx] = '\0';
    return buffer;
}


int main(void)
{
    char buffer[100];
    int test[] = {123, 234, 0, 17, -4, -7, -17, -27, INT_MAX, INT_MIN};
    size_t len = sizeof(test) / sizeof(*test);
    
    // give it a small buffer and see how it works
    for (int i=0, num; i &lt; len; i++) {
        num = test[i];
        char* str = itoa(num, 2, buffer); 
        printf(&quot;%-10d &lt;%s&gt;\n&quot;, num, str);
    }
    
    // now give it the correctly-sized buffer
    for (int i=0, num; i &lt; len; i++) {
        num = test[i];
        char* str = itoa(num, sizeof buffer / sizeof *buffer, buffer);
        printf(&quot;%-10d &lt;%s&gt;\n&quot;, num, str);
    }
}

Working code here: https://onlinegdb.com/H1xKCemNu. A few questions about this:

It seems like there are just so many edge cases to consider for the most trivial of functions to write in C. For example, the above function took me about two hours to write (and was after some very helpful feedback from @chux). Is that a common issue in writing C code? Or is this more a beginner issue that will go away with experience?
With the for loop I often find myself being bitten by an off-by-one error, for example, not realizing the idx is incremented after the previous loop but before the next loop is checked. Are there particular ways to look out for that?
Any other areas to improve here?
Incorrect size check
itoa(num, 2, buffer); forms &quot;17&quot;, which needs 3 char, even though only 2 provided.
Perhaps OP meant 2 to be the string length allowed and not the string size needed.  In which case sizeof buffer / sizeof *buffer is not the length allowed.
Missing header
Add #include &lt;stdbool.h&gt;
Missing size check
Corner case: strcpy(buffer, &quot;0&quot;); occurs even if sz == 1.

A simple alternative to buffer overflow concerns is to form the initial characters in a local buffer that is right-sized for worst case (INT_MIN) like char local_buf[12].  Check idx against sz.  Then reverse into the true destination.
// Re-write end-of-code.
// ....
// Prior code does not check `sz` nor change sz as characters are saved in ample sized local_buf

if (idx + 1 &gt; sz) {
  if (sz &gt; 0) buffer[0] = 0;  // Set buffer[] to &quot;&quot; if possible
  return NULL;
}

for (int i=0; i&lt;idx; i++)
    buffer[idx-i-1] = local_buf[i];
buffer[idx] = '\0';
return buffer;


Rather than hard-code 12 use code to scale to the size needed.  E.g.
// Scale by log10(2)
#define INT_STR_SZ (sizeof(int)*CHAR_BIT*301030/1000000 + 3)
char local_buf[INT_STR_SZ];