Bubblesort to Quicksort requires some cleverness, but is a quite natural progression.
First, you implement Bubblesort, and you find that for 100,000 random items it is indeed very slow. 100,000^2 operations. You'll find easily that if you split the array into two groups of 50,000, sort both and merge the results, you only need 50,000^2 * 2 + 100,000 or so operations which is almost twice as fast. And then obviously you sort a 50,000 item array by splitting it into two parts of 25,000. And if you follow this down and measure, you'll find that for some rather small n the bubble sort is actually faster. Now we've got merge sort.
Mergesort is actually already optimal in Big-O notation, but making an algorithm twice or three times as fast is still worthwhile. So we would analyse what's bad about merge sort, and it is mostly the fact that we need additional memory. Swapping elements doesn't require more than constant additional memory. Figuring out the Quicksort partition algorithm is difficult, but possible enough.
When you examine Quicksort more closely, two annoying things are the worst case which you fix by randomising the pivot, and the fact that it doesn't take advantage if the data is already sorted or mostly sorted.
Some implementations now check how many initial elements are in either ascending or descending order, and how many elements at the end are in either ascending or descending order. And if the numbers are significantly high compared to the total number of items (say large compared to n / log n) we can sort the unsorted items and then do one or two merges. This will be linear if we combine two sorted arrays, or if we take a sorted array with just one change, or a sorted array with O (n / log n) items appended, and usually be an improvement if we take a sorted array with two or three changes. (And the check is very fast if it doesn't gain anything).
Another implementation assumes that your array was created by starting with a sorted array, and adding / removing / appending / changing a few values, fewer than O (n / log n). Here we can remove all the items that are not in sorted order, sort them separately, then merge. This also works well if you take an array containing a million names, sorted correctly according to one of the official German sorting orders, and sorting it according to the official Swedish sorting order.
The last two changes are both quite natural.