def quick_sort(arr, low=None, high=None):
    """Sort given items in place by partitioning items in range `[low...high]`
    around a pivot item and recursively sorting each remaining sublist range.
    Best case running time: O(nlogn) because your unsort list gets smaller and smaller with each recursive call
    Worst case running time: O(n^2) when you pick all high numbers then you need to traverse entire length of array and made only progress sorting on that highest number.
    Memory usage: O(n) because calling function recursively"""

    # base case in recursive call <=1 because last element is the pivot and poping it 
    if len(arr) <= 1:
        return arr
    else:  # not necessary cause when return is run the function stops but helpful to understand
        # pops and sets first item as pivot _value until len(arr) is 1 or less
        pivot_value = arr.pop(0)

        # 2 lists for items that are greater or less than pivot
        items_greater_pivot = []
        items_lower_pivot = []

        # loop through array to see if element is less or greater than pivot_value and add to proper list
        for num in arr:
            if num > pivot_value:
                items_greater_pivot.append(num)
            else:
                items_lower_pivot.append(num)

        # arr[:] = just so to get to pass sorting_test.py by mutating original array
        # recursively calls items_lower_pivot and items_greater_pivot to add to final sorted array. 
        # each call will have quick_sort(items_lower_pivot) + pivot_value and pivot_value + quick_sort(items_greater_pivot)
        arr[:] = quick_sort(items_lower_pivot) + [pivot_value] + quick_sort(items_greater_pivot)    
        return arr[:]