Python’s [:] Notation Explained with Examples!

In this article let us learn about the commonly used “[:]” notation in Python, and learn how we can wield the power of this notation in our programs.

For those of you in a hurry, here is the short version of the answer.

The Short Version of The Answer

Python’s “[:]” notation, officially known as Slice Notation is used to extract the desired portion/slice from a given sequence.

Example#1: Slice Notation and Lists

>>> x = ['a', 'b', 'c', 'd', 'e']
>>> x[2:5:1]
['c', 'd', 'e']

(On a side note, if you are looking for an example with the syntax x[1:] or similar syntax, scroll down to the section “The variants of using Slice Notation” below)

Let us see how the code in the above example works.

In line-1, x is initialized as a list object with 5 items: ‘a’, ‘b’, ‘c’, ‘d’ and ‘e’.

In line-2, a portion/slice of this list is extracted using the slice notation x[2:5:1].

In Slice Notation, the desired slice can be specified with the help of 3 parameters namely start_index, stop_index, and step_length. In this example, the values of these 3 parameters are,

start_index = 2,
stop_index = 5, and
step_length = 1

The image below illustrates this concept

Below are the 7 steps of code execution.

  1. python starts at the start_index, which is index-2,
  2. python collects the value at index-2, which is x[2] = ‘c’ (Python’s list index starts from 0 as shown in the illustration above)
  3. python takes a step with step_length of 1 and collects the value at the next index which is index-3, x[3] = ‘d’
  4. python takes another step with the step_length of 1 and collects the value at index-4, x[4] = ‘e’
  5. Then python takes the last step with step_length of 1, reaches index 5,
  6. Since stop_index = 5, python stops and returns the items collected thus far as a sequence
  7. The returned list is then printed out by the interpreter

In other words, python starts collecting values from the given sequence from start_index, moves through the list by incrementing the index using the step_length and stops once reaching the stop_index and returns the collected values!

Here is another example of the usage of Slice Notation ([:]), this time on strings

Example#2: Slice Notation ([:]) and Strings

>> string1 = 'ant-man'
>> string1[2::1]
't-man'

Here we have set the start_index to be 2 and step_length to be 1. Note how we have skipped the stop_index. If a required parameter is not provided by us, then Python will set the parameter with default values.

Since we did not provide stop_index, and the default value for stop index is the length of the sequence, its value is assigned to be 5 (the length of the string1 sequence can also be obtained using the expression len(string1))

The logic of execution is similar to that of Example#1.

Don’t worry if you haven’t understood everything just yet as this is just the short version of the answer.

Remember, our brain is just like any other muscle in our body. We need to train our brains with concepts to achieve mastery and understanding, just like we would train our muscles in the gym!

I have divided the article into 2 levels, Level#1 is all about practicing all possible variants of this notation, so that you will understand what each variant mean, and you will be able to read these notations intuitively, to a degree that even if someone wakes you up in the middle of the night and asks you to read some code, you would be able to understand the meaning of this notation with a single glance!

Level#2 is all about getting a deeper level of understanding, there we learn about how Python has implemented the [:]/slice notation internally and the common scenarios/use-cases where the power of this notation can be wielded.

I hope by the end of this article you will be able to master this concept of Slice Notations in Python!

Alright! Let’s begin!

The More Informative Version of The Answer

If you prefer videos to articles, here is a short video I have made for you on this topic! (I am a newbie YouTuber so go easy on me if the quality is not matching your expectations!)

Now let us get back to this article!

Coding in any programming language involves lots of iterations and looping, Python engineers were aware of this and they wanted to reduce the clutter produced by these for-loops and improve the readability of the code. This “[:]” notation or slice notation as it is officially called is one such measure to make the lives of us, the programmers, easier!

This “[:]” notation is very powerful and can be used with any kind of sequence in python.

A sequence is a collection where the insertion order is maintained. Examples of sequence data structures in python are are Strings, Lists, Tuples, Bytes, and Byte Arrays.

Using the slice notation we can extract any part of the sequence, be it

  • near the start,
  • near the end,
  • in the middle,
  • every other index,
  • every nth index, etc.

All of the above can be achieved with the help of 3 parameters namely

  • start_index
  • stop_index and
  • step_length

The above 3 parameters are to be used in combination with the sequence’s name and square brackets using the notation below.

sequence_name[start_index:stop_index:step_length]

Trick to remember this [:] Notation

You can always bookmark this page and take a quick peek whenever you need to remember the meaning of slice notation but, if you are interested in learning a simple trick to help you remember this concept, read on!

The C programming language (and several other programming languages like Java, C++, etc) use the following syntax for writing for-loops

for (i = 0; i < 10; i++)
{
  // do some cool stuff here
}

If we rewrite the above code using the 3 parameters: start_index, stop_index, and step_length we get

for (i = start_index; i < stop_index; i+=step_length)
{
  // do some cool stuff here
}

As you can see, our 3 parameters fits right into the for loop!

In short, next time you feel lost using this notation, just try to remember the C for-loop and you can find your way around with the slice notation!

On python, probably on some lower level, this concept of “[:]” slice notation is implemented by plugging the parameters we provide into a for-loop. Below is some pseudocode logic to help illustrate this point.

def slice_list(source_list, start_index, stop_index, step_length):
    destination_list = []
    for (i = start_index; i < stop_index; i+=step_length)
    {
      destination_list.append(source_list[i])
    }
    return destination_list

Here we have a function named slice_list which takes in 4 parameters namely

  • source_list
  • start_index
  • stop_index and
  • step_length

The function then runs the list through a for-loop and collect the items which are at the required indices. Once the for-loop is over the collected items are returned in the form of another list.

Understanding the above pseudocode is enough for Level#1, in Level#2 of this article we will see the actual implementation!

Default Values for the 3 parameters in [:] Notation

We are also allowed to skip some or all of the 3 required parameters. If we do so, the Python interpreter will simply assume the default values for those parameters.

The default values can be given as follows.

  • start_index : 0 (Python’s list index starts from 0)
  • stop_index : length of the given sequence len(sequence_name)
  • step_length: 1

Next, let us see all the possible variants of this notation where we provide various combinations of the above 3 parameters and let python use the default values above for the ones we skipped providing.

Possible Variants in the Usage of the Slice Notation

Open your Python interpreter and follow along with the examples below to help maximize your understanding!

Also as you work through the examples, keep in your mind the following 2 important points

  1. list index starts from 0
  2. stop_index* is not included in the *destination_sequence

In all of the examples that follows we use the same list as shown below

>> x = ['a', 'b', 'c', 'd', 'e']
>> len(x)
5

Variant 1:

  • start_index: provided
  • stop_index: provided
  • step_length: provided
>>> x[1:4:2]
['b', 'd']

Here all the 3 parameters are provided by us and the result is as expected. (Again, remember, list index starts from 0, and stop_index is not included!)

Python starts from index-1, collects ‘b’, then takes next step to index-4 (since step_length = 2), collects ‘d’ and returns the sequence [‘b’, ‘d’]

Variant 2:

  • start_index: provided
  • stop_index: provided
  • step_length: skipped
>>> x[1:4]
['b', 'c', 'd']

Here python assumes the default value of 1 for the step_length parameter and returns the list from index-1 to index-3 (4 – 1 = 3)
See how instead of “x[1:4:]” we just use “x[1:4]” (there is one less “:”), python allows us to do this if we wish to skip the step_length parameter!

We can also write x[1:4:] and get the same result, I leave it up to you to verify that!

Variant 3:

  • start_index: provided
  • stop_index: skipped
  • step_length: provided
>>> x[2::3]
['c']

Here python assumes the default value of 5 (len(x) or length of the list x) for the stop_index parameter and starts collecting items from index-2, jumping 3 indices (step_length = 3) at a time!

Variant 4:

  • start_index: skipped
  • stop_index: provided
  • step_length: provided
>>> x[:4:2]
['a', 'c']

Here python assumes the default value of 0 for the start_index parameter and collects items from the list from index-0 to index-4 (5-1=4), jumping 2 indices (step_length = 2) at a time!

Variant 5:

  • start_index: provided
  • stop_index: skipped
  • step_length: skipped
>>> x[2:]
['c', 'd', 'e']

Here python assumes the default value of 5 (len(x) or length of the list x) for the stop_index parameter and the default value of 1 for the step_length parameter and starts collecting items from the list from index-2 to index-4 (5-1) jumping one index (step_length = 1) at a time!

Variant 6:

  • start_index: skipped
  • stop_index: provided
  • step_length: skipped
>>> x[:3]
['a', 'b', 'c']

Here python assumes the default value of 0 for the start_index parameter and the default value of 1 for the step_length parameter and starts collecting items from the list from index-0 to index-2 (3-1 = 2), jumping one index (step_length = 1) at a time!

Variant 7:

  • start_index: skipped
  • stop_index: skipped
  • step_length: provided
>>> x[::2]
['a', 'c', 'e']

Here python assumes the default value of 0 for the start_index parameter and the default value of 5 (len(x) or length of the list x) for the stop_index parameter and starts collecting items from the list from index-0 to index-4 taking 2 index-steps(step_length = 2) at a time!

Variant 8:

  • start_index: skipped
  • stop_index: skipped
  • step_length: skipped
>>> x[:]
['a', 'b', 'c', 'd', 'e']

Here python assumes the default value for all 3 parameters and returns the entire list!
We can also use x[::] and get the same result as above!

Before we take a look at the next Variant, let us take a quick detour and learn/refresh our memories on the concept of “Negative Indexing in Python”

Negative Indexing in Python

We can also use negative indices to refer to items in our sequences. For instance, no matter what the length of the given sequence is, we can always get the last item on the sequence by using the index of -1.

In our example, x[-1] will give ‘e’ and x[-3] will give us ‘c’ as ‘c’ is the 3rd item from the end of the list!

The picture below illustrates this concept.

Python engineers have made it possible so that we can use even the negative indices with our Slice notation. Let us see an example of how to do this!

Variant 9:

  • start_index: provided
  • stop_index: skipped
  • step_length: skipped
>>> x[-2:]
['d', 'e']

As you can see in the image above, start_index = –2, which points to ‘d’, stop_index, and step_length got their default values of 5 and 1 respectively and hence we got the above result!

Variants to traverse the sequence in Reverse Direction

We can also use the slice notation to traverse in reverse direction from the end of the sequence to the beginning of the sequence if needed by choosing step_length < 0

In other words, we must use the decrement/negative value for the “step_length” parameter to iterate through the list in the reverse direction.

Defaults for negative step_lengths:

If we use negative step_length the defaults for start_index and stop_index get different values as shown below.

  • start_index: -1
  • stop_index: – (len(list_name) + 1)

The pseudo-code below shows the C-equivalent for-loop for negative step sizes

for (i = start_index; i > stop_index; i-=step_length)
{
  // do some cool stuff here
}

Since a negative step_length parameter is a must for doing reverse iterations, we only have 4 variants with reverse traversing as shown below.

Variant#10:

  • start_index: provided
  • stop_index: provided
  • step_length: provided (must for negative step_lengths)
>>> x[-1:-5:-1]
['e', 'd', 'c', 'b']

As you can see in the illustration above, start_index = -1, stop_index = -5 and we step through the list in reverse direction, 1 item at a time as step_length = -1 and get the above output!

Variant#11:

  • start_index: provided
  • stop_index: skipped(default)
  • step_length: provided (must for negative step_lengths)
>>> x[-2::-1]
['d', 'c', 'b', 'a']

Here start_index = -2, stop_index = -6 (the default value of – (len(list_name) + 1) )and we step through the list 1 item at a time as *step_length = *-1* and get the above output!

Variant#12:

  • start_index: skipped(default)
  • stop_index: provided
  • step_length: provided (must for negative step_lengths)
>>> x[:-3:-1]
['e', 'd']

Here start_index = -1 (the default value), stop_index = -3 and we step through the list 1 item at a time as step_length = -1 and get the above output!

Variant#13:

  • start_index: skipped(default)
  • stop_index: skipped(default)
  • step_length: provided (must for negative step_lengths)
>>> x[::-2]
Out[48]: ['e', 'c', 'a']

Here start_index = -1 (the default value), stop_index = -6 (the default value of – (len(list_name) + 1)) and we step through the list 2 indices at a time as step_length = -2 and get the above output!

This ends the variants section of this article!

To understand the real power of this Slice notation better, let us have a bit of fun with a 2-minute coding challenge!

Coding Challenge:

Reverse the list [‘a’, ‘b’, ‘c’, ‘d’, ‘e’] using a single line of code.

Clue: use the negative list index in python!

Take a couple of minutes to think this through and then scroll down for the answer!

.
.
.
.
.

If you came up with the correct answer as shown below, then congratulations! You have successfully completed Level#1!

Answer:

>>> x =  ['a', 'b', 'c', 'd', 'e']
>>> x[::-1]
['e', 'd', 'c', 'b', 'a']

As you can see, this is a very powerful notation, by “powerful” I mean, with very little code you can get so much accomplished!


If your thirst for knowledge has not been quenched yet, read on for Level#2, where we try and understand how the slice notation is actually implemented in Python and the situations where we can use them in!

How does the “[:]” slice notation work?

If you have made it till here, I am sure you have a bright future ahead of you as a Python developer! Let’s continue our quest in Level#2, I promise this one won’t be as hard as Level#1!

Consider the code below.

>>> x[1:4:2]

The Python interpreter takes the above code that we entered and will translate it into something like this

x[slice(1, 4, 2)]

where slice() is an inbuilt Python function that works with Sequence datatypes. This function when invoked with a sequence object, accepts our 3 parameters (start_index, stop_index, and step_length) as input and returns another sequence as output.

As shown in the example above, the slice() function should be invoked only inside the square brackets ‘[]’

This function is designed to accept a variable number of parameters so that if you skip a parameter, python will assign the default value.

The default value of each parameter, as we have seen above, depends upon the sign and availability of the step_length parameter

Internally, as we have seen earlier in this article the for-loop logic is used to get us the sequence returned after slicing!

Let us take some examples and play around with the slice() function!

slice() function Example#1

>>> x[1:4:2]
['b', 'd']
>>> x[slice(1,4,2)]
['b', 'd']

As you can see, slice() function gave us the same results as the Slice Notation!

Let us see another example where we invoke the slice() function with 2 arguments, instead of 3

slice() function Example#2

>>> x[2:4]
['c', 'd']
>>> x[slice(2,4)]
['c', 'd']

Again we got the same results!

For the next example let us see what happens when we invoke slice with just one argument

>>> x[slice(3)]
['a', 'b', 'c']

Looks like slice() uses the argument supplied as stop_index and uses the defaults for the other 2!

Let us verify this theory by having a quick look at the documentation of the slice() function by typing in the following line on the python interpreter

>>> help(slice)

The first few lines of the output will be something like below

Help on class slice in module builtins:

class slice(object)
 |  slice(stop)
 |  slice(start, stop[, step])
 |  
 |  Create a slice object.  This is used for extended slicing (e.g. a[0:10:2]).
 |  
 |  Methods defined here:
 .
 .
 .

As you can see, in lines 4 and 5, if you just give one argument slice() uses that as stop_index, so our theory was indeed correct!

But say we wish to specify only the start_index or the step_length parameter, how can we do that?

Simple, we can use the 3 argument version of the slice() function (which is slice(start, stop[, step])) and provide None to fill in the values where we wish to use the default values!

The example below illustrates this.

>>> x[2::]
['c', 'd', 'e']
>>> x[slice(2,None,None)]
['c', 'd', 'e']

Yay! We managed to get things working just the way we wanted to! If we can do something with the Slice Notation, we can do the same with the slice() function!

Where can we use the slice notation?

We can use this notation in a variety of situations, let us have a look at 3 most-common use-cases!

Use-case#1: Copy Portion of a sequence

Say we have a string and we wish to extract a sub-string, we can do so using Slice Notation as shown below.

>> my_fav_quote = 'I love Python!'
>> lang_i_love = my_fav_quote[-7:]
>> print('What do I love?\n', lang_i_love)
What do I love?
 Python!

Use-case#2: Iterate through portion of a sequence

Say you wish to iterate through a portion of a list, we can do so using Slice Notation as shown below.

for item in x[1:5:2]:
  // do your magic here!

Use-case#3: Create a copy to play with

List methods like pop() will alter our list, but since slice notation creates a copy. We can play with that slice in any way we want without worrying about altering the contents of the original sequence.

>>> x = ['o','r','i','g','i','n','a','l']
>>> y = x[1:]
>>> y[2] = 'z'
>>> x
['o', 'r', 'i', 'g', 'i', 'n', 'a', 'l']
>>> y
['r', 'i', 'z', 'i', 'n', 'a', 'l']

We can use the copy() method of Sequences to do the same, but that will copy over the entire list, which we might not want in situations where the list has tens of thousands of entries and we are only interested in a portion of those.

The above 3 are only the most common use-cases. Just use your imagination while you code, and I am sure you will come up with lots of scenarios where this notation will prove to be useful in your code!

And with that, we have reached the end of Level#2!

As I promised, Level-#2 was not so hard right!

Hopefully, now you have some good insight into the inner working of the slice notation and scenarios where this notation might prove to be useful!

To summarize this article let us have a one last look at the important points covered in this article.

Points to Remember

  • The official name of this “[:]” notation is slice notation
  • The 3 important parameters are: [start_index:stop_index:step_length]
  • stop_index is not included and python starts counting from index-0
  • We can choose to skip providing some of these 3 parameters and the python interpreter will use the default values for the skipped ones.
  • The default values of these parameters (for step_length > 0) are
    start_index = 0
    stop_index = length of the list, len(list_name)
    step_length = 1
  • The default values of these parameters for step_length < 0 are start_index = -1 and stop_index = -(seq_length + 1)
  • you can use slice notation with any sequence in Python like strings, lists, tuples, bytes, and byte arrays.
  • the inbuilt slice() function does the same job when invoked within the square brackets []
  • slice notation can be used to extract slices, iterate through portions, and for playing with lists in a safe manner without making any changes to the original.

Alright, with that I will stop!
Hope you enjoyed reading this article as much I did writing it!
Feel free to share this article with your friends and colleagues!

References

Head First Python (Affiliate link to amazon)
Official Python slice notation documentation
Official Python Sequence documentation
Stack overflow question

Comments are closed.