Mergesort through recursion
Slicing a list makes a copy of it, e.g.:

&gt;&gt;&gt; a = list(range(10))
&gt;&gt;&gt; a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
&gt;&gt;&gt; b = a[:5]
&gt;&gt;&gt; b
[0, 1, 2, 3, 4]
&gt;&gt;&gt; b[2] = 0
&gt;&gt;&gt; b
[0, 1, 0, 3, 4]
&gt;&gt;&gt; a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


So each of your recursive calls is creating a copy of half of the list before passing it down, which is not the best use of time. I think it is a much better approach to pass around the whole list, which simply passes a reference to it around, no copies involved, and a couple of indices marking the range you want to work on:

def mergesort(alist, lo=0, hi=None):
    if hi is None:
        hi = len(alist)
    if hi - lo &lt;= 1:
        return
    mid = lo + (hi - lo) // 2  # mid = (lo + hi) // 2 can overflow
    mergesort(alist, lo=lo, hi=mid)
    mergesort(alist, lo=mid, hi=hi)
    # Copy out the left side, which may be overwritten by the merge
    temp = alist[lo:mid]
    read_left = 0  # index into temp, copy of left subarray
    read_right = mid  # index into alist's right subarray
    write = lo  # index into alist
    while read_left &lt; len(temp) and read_right &lt; hi:
        if alist[read_right] &lt; temp[read_left]:
            alist[write] = alist[read_right]
            read_right += 1
        else:
            alist[write] = temp[read_left]
            read_left += 1
        write += 1
    # Copy any unprocessed items in temp back to alist
    alist[write:write+len(temp)-read_left] = temp[read_left:]
    # Any unprocessed items in right subarray are already in place!
    # Nothing returned: the list gets sorted in-place