Hey developers! 👋
Have you ever wondered:
- Why arrays, lists, and strings start counting at 0 instead of 1?
- Why Python can magically reverse a list using
[::-1]? - Why strings, tuples, lists, and ranges all behave so similarly?
The answer lies in one of the most fundamental concepts in programming:
Sequences
Sequences are everywhere.
Every time you loop through a list, access a character in a string, slice an array, or iterate over a range, you're working with a sequence.
Understanding sequences deeply helps you write cleaner code, understand language internals, and become much more comfortable with data structures.
Today we'll explore:
- What sequences really are
- Why indexing starts at 0
- How slicing works behind the scenes
- Shallow vs deep copying
- Building custom sequence classes
- Using Python's Sequence abstract base class
- Practical examples and best practices
Ready?
Let's dive in. 🚀
What Is a Sequence?
At its core, a sequence is simply:
An ordered collection of elements that can be accessed by position.
A sequence guarantees:
- Order
- Index-based access
- Iteration support
Think of a train.
🚂 🚃 🚃 🚃 🚃
Each carriage occupies a specific position.
You can access:
Position 0
Position 1
Position 2
Position 3
in a predictable order.
That's exactly how sequences behave.
Common Sequence Types in Python
Python includes several built-in sequence types.
Lists
Mutable sequences.
numbers = [1, 2, 3, 4]
You can modify them:
numbers.append(5)
Tuples
Immutable sequences.
coordinates = (10, 20)
Once created, they cannot be changed.
Strings
Sequences of characters.
name = "Python"
Access individual characters:
print(name[0])
Output:
P
Range Objects
Efficient numeric sequences.
range(5)
Produces:
0, 1, 2, 3, 4
without storing every value in memory.
Sequences in Other Languages
The concept exists everywhere.
JavaScript
const numbers = [1, 2, 3];
const text = "hello";
Arrays and strings behave as sequences.
Java
int[] numbers = {1, 2, 3};
ArrayList<Integer> list = new ArrayList<>();
C and C++
int numbers[3] = {1, 2, 3};
Arrays are the most fundamental sequence structures.
The idea remains the same across languages:
Ordered elements
+ Position-based access
Why Does Indexing Start at 0?
This question has confused generations of developers.
At first glance:
1, 2, 3, 4...
feels more natural than:
0, 1, 2, 3...
But computers have a very practical reason.
The Low-Level Explanation
Consider an array in C:
int arr[] = {10, 20, 30};
Internally, the array name is essentially a pointer to the first element.
When you write:
arr[0]
the compiler interprets it as:
*(arr + 0)
Meaning:
Value at memory address
+ offset 0
Similarly:
arr[1]
becomes:
*(arr + 1)
and:
arr[2]
becomes:
*(arr + 2)
No extra calculations required.
The index directly represents the offset from the starting address.
Why Not Start at 1?
Suppose indexing started at 1.
Then:
First element → index 1
Internally the computer would need:
actual_position = index - 1
for every lookup.
That extra adjustment would happen constantly.
Programming languages adopted 0-based indexing because it aligns perfectly with memory addressing.
Benefits of 0-Based Indexing
Faster Calculations
Offsets map directly to memory locations.
Simpler Slicing
Consider:
numbers = [10, 20, 30, 40]
Length:
len(numbers)
returns:
4
Last valid index:
4 - 1 = 3
which matches:
numbers[3]
perfectly.
Consistency
Many data structures use similar offset-based calculations:
- Arrays
- Heaps
- Buffers
- Strings
Everything remains mathematically consistent.
A Real-World Analogy
Imagine standing at the entrance of a movie theater.
Your current position is:
0 steps away
The first seat is right there.
No movement required.
Computers think similarly.
The first element exists:
0 positions away
from the beginning.
Simple.
Efficient.
Logical.
Understanding Python Indexing
Basic indexing:
letters = ["A", "B", "C", "D"]
print(letters[0])
Output:
A
Negative indexing works too:
print(letters[-1])
Output:
D
Python counts backward from the end.
Internally:
-1 → last element
-2 → second-to-last
and so on.
Python Slicing: A Developer Superpower
One of Python's most elegant features is slicing.
Example:
numbers = [10, 20, 30, 40, 50]
Basic Slice
numbers[1:4]
Output:
[20, 30, 40]
Omitted Start
numbers[:3]
Output:
[10, 20, 30]
Steps
numbers[::2]
Output:
[10, 30, 50]
Reverse
numbers[::-1]
Output:
[50, 40, 30, 20, 10]
One of Python's most beloved tricks.
How Slicing Works
General syntax:
sequence[start:stop:step]
Start
Where iteration begins.
Stop
Where iteration ends.
Not included.
Step
How much to move each iteration.
Example:
numbers[0:5:2]
Meaning:
Start at 0
Stop before 5
Move by 2
Result:
[10, 30, 50]
Why the Stop Value Is Exclusive
Many beginners ask:
Why doesn't Python include the stop value?
Because exclusive ranges simplify calculations.
Example:
numbers[0:3]
contains:
3 elements
because:
3 - 0 = 3
No special rules required.
This design appears throughout Python.
Copying Sequences Correctly
A common task:
original = [1, 2, 3]
Create a copy.
Using Slicing
copy1 = original[:]
Using Constructor
copy2 = list(original)
Using Copy Method
copy3 = original.copy()
All create shallow copies.
What Is a Shallow Copy?
Consider:
nested = [[1, 2], [3, 4]]
copy_list = nested.copy()
Modify:
nested[0][0] = 999
Now:
print(copy_list)
Output:
[[999, 2], [3, 4]]
Why?
Because only the outer list was copied.
The inner lists are still shared.
Deep Copies
For completely independent copies:
import copy
nested = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(nested)
Modify:
nested[0][0] = 999
Now:
print(deep_copy)
Output:
[[1, 2], [3, 4]]
The nested structures remain independent.
Building Your Own Sequence
One of Python's coolest features is that you can create custom sequence types.
Example:
class RepeatedSequence:
def __init__(self, value, count):
self.value = value
self.count = count
def __len__(self):
return self.count
def __getitem__(self, index):
if isinstance(index, slice):
start, stop, step = index.indices(self.count)
return [
self.value
for _ in range(start, stop, step)
]
if index < 0:
index += self.count
if 0 <= index < self.count:
return self.value
raise IndexError("Index out of range")
def __repr__(self):
return str([self.value] * self.count)
Trying It Out
seq = RepeatedSequence("🍕", 5)
Indexing:
print(seq[2])
Output:
🍕
Slicing:
print(seq[:3])
Output:
['🍕', '🍕', '🍕']
Length:
print(len(seq))
Output:
5
The object behaves like a real sequence.
Using collections.abc.Sequence
Python provides a professional shortcut.
Instead of implementing everything yourself:
from collections.abc import Sequence
Create:
class MySeq(Sequence):
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __len__(self):
return len(self.data)
That's it.
What Do You Get for Free?
By implementing only:
__getitem__()
__len__()
Python automatically provides:
__contains__()
__iter__()
count()
index()
and other sequence behaviors.
This is a great example of Python's powerful abstract base classes.
Sequence Thinking: A Useful Mental Model
Think of a sequence as:
Ordered Data + Position
If position matters:
Sequence
If position doesn't matter:
Set
Understanding this distinction helps you choose the right data structure.
TL;DR Quick Recap
- Sequences are ordered collections of elements.
- Lists, tuples, strings, and ranges are sequences.
- Indexing starts at 0 because it matches memory offsets.
- Negative indexes count backward from the end.
- Python slicing uses
start:stop:step. - The stop value is exclusive.
- Shallow copies duplicate containers but share nested objects.
- Deep copies recursively duplicate everything.
- Custom sequences can be built using
__getitem__and__len__. collections.abc.Sequenceprovides many sequence features automatically.
Final Thoughts: Sequences Are Everywhere 🧠
Sequences may seem like a beginner topic.
They're not.
They sit at the foundation of countless programming concepts:
- Arrays
- Strings
- Buffers
- Database rows
- Collections
- Iterators
- Data pipelines
Understanding how indexing, slicing, and sequence behavior work gives you a deeper appreciation for how programming languages are designed.
And once you truly understand why indexing starts at 0, you'll stop fighting it and start seeing the elegance behind it.
A Little Python Joke to End On 😄
Why did the sequence start counting from zero?
Because it didn't want to waste an index on unnecessary introductions.
Frequently Asked Questions
What is a sequence in programming?
A sequence is an ordered collection of elements that supports indexing and iteration.
Examples include:
- Lists
- Tuples
- Strings
- Arrays
- Ranges
Why does indexing start at 0?
Because indexes represent offsets from the starting memory address.
The first element is located:
0 positions away
from the beginning.
What are examples of Python sequences?
Python sequence types include:
list
tuple
str
range
bytes
bytearray
Each stores elements in a specific order.
What is slicing?
Slicing extracts part of a sequence.
Syntax:
sequence[start:stop:step]
Example:
numbers[1:4]
returns elements from index 1 up to (but not including) index 4.
Why is the stop index excluded?
Because exclusive boundaries make calculations simpler.
The slice length becomes:
stop - start
without special adjustments.
What is the difference between shallow and deep copy?
Shallow copy:
copy.copy(obj)
copies only the outer container.
Deep copy:
copy.deepcopy(obj)
recursively copies nested objects as well.
What methods are required for a custom sequence?
Typically:
__getitem__()
__len__()
These allow indexing and length operations.
What is collections.abc.Sequence?
It is an abstract base class that provides many sequence behaviors automatically when you implement:
__getitem__()
__len__()
Why does [::-1] reverse a sequence?
Because:
step = -1
tells Python to iterate backward through the sequence.
When should I use a sequence?
Use a sequence whenever:
- Order matters
- Position matters
- Iteration is required
- Elements need index-based access
Key Takeaways
- Sequences are ordered collections with positional access.
- Python provides lists, tuples, strings, and ranges as built-in sequences.
- Zero-based indexing comes directly from memory addressing principles.
- Slicing is a powerful mechanism for extracting and transforming data.
- Shallow and deep copies behave very differently with nested structures.
- Custom sequence classes are easy to build.
collections.abc.Sequenceoffers many sequence features automatically.- Understanding sequences improves your grasp of programming fundamentals and data structures.
If you found this helpful, share it with another developer and follow for more Python deep dives, language internals, software architecture discussions, and programming fundamentals. 🚀
About the Author
Anik Sikder is a Software Engineer specializing in Backend Development, Python, Django, FastAPI, DevOps, Cloud Infrastructure, and Software Architecture.
He writes about Python internals, software engineering fundamentals, system design, scalable backend systems, and modern software architecture.



