Python lists can be reversed using built-in methods reverse(), reversed() or by [::-1] list slicing technique. The reverse() built-in method reverses the list in place while the slicing technique creates a copy of the original list. The reversed() method simply returns a list iterator that returns elements in reverse order.

Below are the three built-in, common method used for reversing Python lists.

Bash

1 2 3 4 5 6 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>> type(nums.reverse())
<type 'NoneType'>
>>> nums
[8, 7, 6, 5, 4, 3, 2, 1]
>>>
``` |

Bash

1 2 3 4 5 6 7 8 9 10 11 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>>
>>> nums_reversed = nums[::-1]
>>> nums_reversed
[8, 7, 6, 5, 4, 3, 2, 1]
>>> type(nums_reversed)
<type 'list'>
>>>
>>> nums
[1, 2, 3, 4, 5, 6, 7, 8]
>>>
``` |

Bash

1 2 3 4 5 6 7 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>> reversed(nums)
<listreverseiterator object at 0x10fced990>
>>>
>>> [n for n in reversed(nums)]
[8, 7, 6, 5, 4, 3, 2, 1]
>>>
``` |

Let us look at each in detail to understand pros, cons and when to use a particular method.

Bash

1 2 3 4 5 6 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>> type(nums.reverse())
<type 'NoneType'>
>>> nums
[8, 7, 6, 5, 4, 3, 2, 1]
>>>
``` |

The reverse() method works in O(n) time complexity and with O(1) space. Internally, when reverse() is called it operates by swapping i-th element with (n-i)th element. Therefore, the first element is replaced with the last element, the second element is replaced with the second last element and so on. Thus, a total of N/2 swap operations are required for list reversal. That makes the overall time complexity as O(N/2) which is same as O(N)

- In-Place
- Intuitive and easy to understand, it upholds code readability.

- The order of elements in the original list is changed.

Scenarios where order of elements in the original list can be altered and keeping a low memory footprint is desired .

Python lists can be reversed using the [::-1] slicing suffix. It creates and returns a new copy of the list without altering the actual list.

Bash

1 2 3 4 5 6 7 8 9 10 11 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>>
>>> nums_reversed = nums[::-1]
>>> nums_reversed
[8, 7, 6, 5, 4, 3, 2, 1]
>>> type(nums_reversed)
<type 'list'>
>>>
>>> nums
[1, 2, 3, 4, 5, 6, 7, 8]
>>>
``` |

What does [::-1] notation mean? It means to select elements starting from the first element till the last element with a stride of negative one, i.e, in reverse order. The list slicing notation is [start:end:step], so here start=end=None means the defaults (0 and n-1) and step=-1 implies reverse order.

What are the pros, cons of using slicing for list reversal, and when should we prefer slicing over reverse() or reversed() ?

- The original list is not altered. The order of elements in the original arrays is maintained before and after the slicing operation.

- Takes extra space by creating a list of the same size.
- While [::-1] notation is shorter, it is cryptic and requires more attention to understand as compared to english words syntax reverse() or reversed(). In short not the best for code readability.

- If it is a requirement to preserve the order of elements in the original list.
- It is fine to allocate extra memory for the copy of the list.

Python lists can also be reversed using the built-in reversed() method. The reversed() method neither reverses the list in-place nor it creates a copy of the full list. It instead returns a list iterator(*listreverseiterator*) that generates elements of the list in reverse order.

Bash

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ```
>>> nums = [1,2,3,4,5,6,7,8]
>>> reversed(nums)
<listreverseiterator object at 0x10fced990>
>>>
>>> [n for n in reversed(nums)]
[8, 7, 6, 5, 4, 3, 2, 1]
>>>
>>>
>>> def reverse_python_list(nums):
... for num in reversed(nums):
... yield num
...
>>>
>>> list(reverse_python_list([1,2,3,4,5,6,7,8]))
[8, 7, 6, 5, 4, 3, 2, 1]
>>>
``` |

Note that calling reversed(*nums*) simply returns an iterator object. We can see in the following example that the *reverse_python_list* method, which simply wraps the reversed() method, does not modify the original list or create a copy of the list.

- No extra space is required
- The original list remains unchanged
- The syntax aids to code readability

- None really. Just that extra caution needs to be exercised with iterators.The returned iterator can be used only once(it gets exhausted on looping over once). So, if it is required to access the reversed list multiple times, we need to create a copy of the list or call the reversed() function multiple times.

Let us take a look at a few other common Python Lists reversal related problems.

To reverse a list of size n using for loop, iterate on the list from (n-1)th element to the first element and yield each element.

Bash

1 2 3 4 5 6 7 8 9 | ```
>>> def reverse_list_using_for(nums):
... # Traverse [n-1, -1) , in the opposite direction.
... for i in range(len(nums)-1, -1, -1):
... yield nums[i]
...
>>>
>>> print list(reverse_list_using_for([1,2,3,4,5,6,7]))
[7, 6, 5, 4, 3, 2, 1]
>>>
``` |

To reverse a list of using recursion, we define a method that returns sum of two lists, the first being the last element (selected by -1 index) and the second one being reverse of the entire list upto the last element (selected by :-1). The base condition is met when all the elements are exhausted and the array is empty, upon which we return an empty array. Below is the functional code.

Bash

1 2 3 4 5 6 7 8 9 | ```
>>> def reverse_list_using_recursion(nums):
... if not nums:
... return []
... return [nums[-1]] + reverse_list_using_recursion(nums[:-1])
...
>>>
>>> print reverse_list_using_recursion([1,2,3,4,5,6,7])
[7, 6, 5, 4, 3, 2, 1]
>>>
``` |

To reverse a part of a list, the built-in reverse, reversed or slicing methods can be used on the subset identified by slicing. The following code shows all the three approaches:

Bash

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ```
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>>
# Method: 1 Using Slicing
>>> nums[3:8][::-1]
[8, 7, 6, 5, 4]
>>>
>>>
# Method: 2 Using reverse()
>>> nums_subset = nums[3:8]
>>> nums_subset.reverse()
>>> nums_subet
[8, 7, 6, 5, 4]
>>>
>>>
# Method: 3 Using reversed()
>>> list(reversed(nums[3:8]))
[8, 7, 6, 5, 4]
>>>
``` |

The numpy arrays can be reversed using the slicing technique (using [::-1] slice descriptor) or by using numpy’s flipud method. The following code shows the usage of both:

Bash

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ```
>>> np_array = np.array([1, 2, 3, 4, 5, 6])
>>>
# Method 1: Using slicing
>>> np_array[::-1]
array([6, 5, 4, 3, 2, 1])
>>> type(np_array[::-1])
<type 'numpy.ndarray'>
>>>
>>>
# Method 1: Using flipud
>>> np.flipud(np_array)
array([6, 5, 4, 3, 2, 1])
>>> type(np.flipud(np_array))
<type 'numpy.ndarray'>
>>>
``` |

In this tutorial we learnt the three techniques for Python list reversal, viz reverse(), reversed() and slicing. We also looked at the pros and cons of each method.

The answer depends on the requirements. If the requirement is to maintain the order of original elements then reversed() or slicing technique should be used. If the requirement is to have minimal memory footprint, reverse() or reversed() are more suited. If it is required to have a minimal memory footprint along with maintaining order of elements in the original list, reversed() should be used. In general, if there is no such preference, reverse() or reversed() can be preferred over slicing technique as it aids to code readability.