Function for iterating out an unordered list of custom taxonomy terms
As pointed out, there is a lot of code repetition and this is a big problem when any changes get made.  It's so easy to quickly make a change to one piece of code and miss the 3 other versions of it which do the same thing.

Rather than factor out these parts of code into functions though, you can re-factor the code into a recursive function.  The idea being that you just use the same code to do each layer of the process rather than loops within loops calling helper functions to do the more common tasks.

One other thing I have changed is to build the output into a string rather than echo it out directly.  I would normally then return this string to the calling code and let that do whatever it wants with it.  This allows more control over the output generated, also allows the output to be re-used (although not sure how that would be necessary in this case) rather than having to re-generate the output each time.  But as I'm not sure how this is called, I just echo the combined string at the end.

function taxonomy_nav( string $category = null, array $settings = null  ) : string {
    if ( $category == null )    {
        $settings = [
            'taxonomy'     =&gt; 'kernal_category',
            'parent'        =&gt; 0,
            'number'        =&gt; 0,
            'hide_empty'    =&gt; false
        ];
        $parent_terms = get_terms( $settings );
    }
    else    {
        $parent_terms = get_terms( $category, $settings );
    }

    foreach ( $parent_terms as $parent_term ) {
        $list =  '&lt;li&gt;&lt;a href="'. get_term_link( $parent_term ) .'"&gt;'. $parent_term-&gt;name.'&lt;/a&gt;';

        $childrenCheck = get_term_children( $parent_term-&gt;term_id, $parent_term-&gt;taxonomy );

        if ( ! is_wp_error( $childrenCheck ) &amp;&amp; ! empty ( $childrenCheck ) ) {
            $list .= '&lt;ul&gt;'
                . taxonomy_nav ( 'kernal_category', array( 'hide_empty' =&gt; false, 'parent' =&gt; $parent_term-&gt;term_id )  ) 
                . '&lt;/ul&gt;';
        }

        $list .= '&lt;/li&gt;';
    }

    echo $list;
}


As you can see, in the if ( ! is_wp_error( $childrenCheck ) block, it just starts the sub list and then calls itself to do the processing for any child nodes.  

This also shows how the list item itself is only ever generated by the one line of code, not repetition and any changes are easily identified.