Here are resources for more information about Timsort:

CPython source code: objects/listobject.c Timsort: Wikipedia article Timsort: Original explanation Complete Analysis on How to Pick min_run for Timsort

Here are resources for more information about Timsort:

CPython source code: objects/listobject.c Timsort: Wikipedia article Timsort: Original explanation Complete Analysis on How to Pick min_run for Timsort

**00:00**
The final algorithm I’m going to talk about today is actually the native sorting algorithm for Python, which is called Timsort. It’s named for Tim Peters, who is the guy who implemented the algorithm in the first place. Now when you use in Python the `sorted()`

function, which is part of the standard library, Timsort is the sorting function that that uses.

**00:20**
And the reason for that is because Timsort is really fast. In the worst case, it actually is still just as fast as the best case of merge sort or quicksort.

**00:31**
So it’s a really, kind of, best-of-both-worlds algorithm, but it is a lot more complex to implement and that’s why people like Tim Peters are the ones to have implemented it.

**00:40**
So, let’s take a look at how it works. Timsort doesn’t lend itself super easily to diagram, so I’m just going to explain it as best I can and hopefully your experience with insertion sort and merge sort will help you understand this, because Timsort is kind of like a combination of those two kinds of sorts.

**00:57**
So the basic idea is you divide your list into super small sections—say, for example, smaller than 32 items. So small sublists, and then you use insertion sort to sort all of those sections because insertion sort is really fast on small lists because of its low overhead, it doesn’t have to allocate any new space, and it sorts in place, which is really nice.

**01:18**
Then you can merge those sections together using the merge sorted lists algorithm that I demonstrated earlier as part of merge sort. And you merge those together two at a time until the whole list is sorted.

**01:29**
Timsort is an example of what some call a block merge sorting algorithm, which is you divide the data into blocks, you sort those blocks with some other algorithm, and then you merge them using the ability to merge sorted lists in *O(n)* time.

**01:43**
So it leverages the strengths of insertion sort and the strengths of merge sort. Insertion sort is fast on small lists, it’s stable, and it has *O(n)* best-case performance.

**01:52**
So if the list is already sorted, insertion sort will see that really quickly and it won’t ever need to do any extra work. And then it also uses the strengths of merge sort, which is that merge sort is much faster on larger lists, it has a worst-case performance of *O(n log n)*, and the great thing is that it mitigates the weaknesses of both of these algorithms, right?

**02:14**
Merge sort can sometimes be a little slow in practice as an algorithm because it often needs to allocate extra space. In fact, it *always* needs to allocate extra space unless you do some serious extra work.

**02:26**
And this algorithm allows you to use the merging sorted lists part of merge sort without necessarily needing to use the recursive part of it. Whereas with insertion sort, which is pretty slow on large lists, you don’t actually have to use that part of insertion sort here.

**02:40**
So it combines these two algorithms in a really nice way. What I’m going to do now is I’m going to go through an implementation of this and it will require that I do some slight modifications to insertion sort, but then I’ll be able to use that insertion sort with some extra logic to implement Timsort.

**02:58**
So, the first thing to do in order to implement Timsort is you need to have an insertion sort that works only on specified subsections of a list.

**03:08**
One way to do that is you can define parameters `left`

and `right`

, which indicate the subsection of the list. And `right`

is going to default to `None`

, `left`

will default to `0`

. And `if right is None:`

**03:24**
then I’ll just set `right`

to equal the length of `items`

minus `1`

. And this is just kind of another way to simulate a default parameter that has a value based on the length of another parameter.

**03:35**
So, `left`

defaults to `0`

, `right`

defaults to the length of `items`

minus `1`

, and now I just want to go through—instead of going through the whole list, I want to go through from `left + 1`

to `right + 1`

, and it will only go up to but not including `right + 1`

, right?

**03:57**
So it’s just going essentially from the second element of the subsection all the way up to the end of the subsection. Now, there’s only one more thing that I need to do to make this work.

**04:07**
I need to change this from `0`

to `left`

, and then that’s actually all you need to do to have insertion sort work as it should work to work with Timsort.

**04:17**
And I’ll just delete this little timing thing just so that I can import this later and just use it as it is. So there’s the `insertion_sort()`

for Timsort.

**04:26**
This is just going to be `# sort subsection instead of full list`

. But of course this defaults. If you don’t pass in `left`

and `right`

, it will still sort the full list. So it works just fine.

**04:40**
Now let’s take a look at Timsort.

**04:43**
So, Timsort is going to start out just like the other sorting functions that I’ve used here. So I’m going to take in—and I’m not going to call it an `array`

, I’m going to call it `items`

—and I’ll say `timsort(items)`

, and there are a couple of different things to do here. The first is to define, let’s say, a `min_subsection_size`

, and I’ll define it to be `32`

.

**05:05**
And there are really actually in the implementation of Timsort that Python uses under the hood, there’s some complex logic to choose a `min_subsection_size`

that’s optimal.

**05:15**
I’m not going to worry too much about that right now because it’s just to show you how the general part of the algorithm works, but if you’re interested, I would encourage you to check out the Python source code, which is readily available online, and you can read the logic for how this actually works in the Python source code. Be warned, though, it is a little dense and it can be kind of obtuse, so there are a wealth of resources online as well that will explain this algorithm in minute detail. So go check those out for sure.

**05:42**
So now, first thing to do is you want to go through the whole list and say `for i in range(0, len(items))`

, so go through the whole array, and essentially what you’re trying to do is divide it into chunks based on the `min_subsection_size`

.

**06:02**
Then what you want to do there is you want to insertion sort each of these subsections. And as you recall, I can put in `items`

and then I can put in `i`

, and I can put in the minimum of, let’s see, it should be `i + min_subsection_size - 1`

because this is just to make the arithmetic work out, and then `len(items)`

minus `1`

. And this is a super long line, but I hope you’ll forgive me for that.

**06:35**
Essentially, this is just saying “Insertion sort `items`

from `i`

to the minimum of `i`

plus this subsection size minus `1`

or length of `items`

minus 1.” So if the last subsection goes over the end of the array, then you want to just use the length of the `items`

minus `1`

, instead of adding the next `min_subsection_size`

.

**06:56**
So that’s all just arithmetic to make this work out. And before I forget, let me say this. I want to `from insertion_sort import insertion_sort`

.

**07:11**
So, that’s good, I have an `insertion_sort()`

to actually work with here. And now this is where you’re going to go through and merge the sorted slices.

**07:21**
I’ll start out here and I’ll say the size of the lists that I’m working with is going to be the `min_subsection_size`

. That’s the initial size of each array that I’m working with. And I’ll say `while size < n`

, so of course, when the size of the full array becomes the size of `items`

—and I wrote `n`

just because I originally had that written as the size of the array, but I’ll just say `len(items)`

here. And now that I’m looking at this, it occurs to me that I wrote `len(items - 1)`

, which is not an expression with a length. This is what it should be.

**07:56**
So, `while size < len(items):`

I need to first determine which subarrays are going to be merged together. So I’ll say `for start in range(0, len(items))`

, and going up by steps of `size * 2`

, because I’m trying to merge each array together, I’ll calculate the `midpoint`

. So essentially what that is is just the end of the first subarray that I’m trying to merge here.

**08:26**
So essentially all I’m doing is I’m just saying go through in chunks of the size times `2`

, get the midpoint of that chunk because I know that that’s where the second subarray starts.

**08:41**
And then I’ll say the `end`

is— again, this is some index arithmetic, so the minimum of `start + size * 2 - 1`

.

**08:52**
And this, of course, probably seems a little bit like gibberish but again, what’s happening here is I just want to make sure that the last slice gets calculated correctly, right?

**09:04**
So the `end`

is either the `start + size * 2 - 1`

, so the end of this big chunk, or it’s just the end of the list of `items`

, if that’s where we’re at.

**09:15**
So now I have a `midpoint`

and the `end`

, so I know where the two subarrays start, so I can say `merged_array = merge_sorted_lists()`

where the left sorted list is `items`

from `start`

to `midpoint + 1`

, and then the next subarray is `items`

from `midpoint + 1`

to `end + 1`

.

**09:49**
And again, we want to add that `+ 1`

in there to make this so that it goes up to but not including the actual end. And if you’re a stickler for Python style, I’m going to put these like this so that it’s kind of more clear what’s happening here.

**10:03**
So now there’s a merged array that is the `size * 2`

, and now all I need to do is I just need to say `items`

at `start`

to `start + len(merged_array)`

equals `merged_array`

.

**10:22**
So, this is kind of a complicated implementation, as I’m sure you’re becoming aware here. But really, it’s doing simple things. It just requires some logic to make sure that your indices are all right.

**10:35**
So now I just need to multiply the `size`

by `2`

, because now I’ve merged all of these arrays into chunks that are twice the size of the previous chunks, and you’ll just keep doubling the size of those chunks.

**10:47**
And then, finally, you’ll just return `items`

at the end of it. So, that is Timsort! And I need to make sure I remember to say `from merge_sort import merge_sorted_lists`

, and that should be good because it’ll allow me to actually merge these together.

**11:08**
Let’s take a look. Since this is not a recursive algorithm, I can say `from timing import timed_func`

and I can mark this as a `@timed_func`

.

**11:19**
And I can just run it and I’ll use the same tests that I’ve been using this whole time. I’ll say `items = random.randint()`

from `1`

to `1000`

**11:34**
And then I’ll just say `timsort(items)`

and the timer will tell me how long it took. As you can see, it takes about five thousandths of a second to run for a list of a thousand elements, which is pretty darn good. Let’s crank it up to `10000`

, see how that goes. And so, as you can see, this has very fast performance on the order of merge and quicksort. For `100000`

, it takes a little bit longer. It takes about a second.

**12:03**
And then I’ll just bump it up to `1000000`

and we’ll see what happens. As you can see, that one took a little bit longer to run—about 13 seconds—but for a million elements in a pure Python sorting algorithm, that’s pretty darn good. And the real Timsort, of course—the one that’s actually used in Python—is much, much faster because it’s written purely in C on the backend and then Python simply calls those C functions.

**12:26**
So that’s an important performance optimization that’s done under the hood in the actual version. And as I said, you’re welcome to check out that algorithm as it’s actually implemented in the Python source code because that’s all online.

**12:39**
There are many great things about Timsort. The first is that it’s stable, unlike quicksort, and its worst-case performance is *O(n log n)*, so it doesn’t have any really terrible cases, any pathological cases where the complexity soars.

**12:55**
And finally, its best-case performance when the list is already sorted is actually order of *n*, so it’s super fast with almost-sorted lists. And in reality, it turns out, for general purpose programming languages most of the things that people try to sort have some elements of sorted data already, have some subsections.

**13:12**
And this algorithm really feeds on that because whenever a subsection is sorted, insertion sort can sort it incredibly quickly. So, Timsort—I mean, I wouldn’t go so far as to say it’s a perfect sorting algorithm because we’re always improving sorting algorithms, but it is pretty darn good and it’s about the best that you can find out there.

**13:30**
Luckily, it comes standard in Python, so hopefully you won’t ever have to implement it on your own. But hey, if you write your own programming language at some point, which is something that is really fun and interesting to do, you might very well want to write a Timsort implementation for it, and that would be super cool. Next up is the summary.

Become a Member to join the conversation.