Concepts / Understanding Sequences and Lists

Understanding Sequences and Lists

Note that range() generates a sequence of numbers, but it will generate only one number at a time, when the for loop requests for the next item. If you want to see the full sequence of numbers immediately, use list(range()) . Lists are explained in the data structures chapter.

  • Programming

What Makes a Sequence

A sequence is an ordered collection of items. The key word is ordered: each item in a sequence has a position, and you can access items in a predictable order. In Python, sequences are not limited to numbers. You can have sequences of strings, sequences of mixed types, or sequences of any objects. The important characteristic is that they maintain order and can be iterated through, one item at a time.

The for..in loop works with any sequence. This is a powerful feature of Python: whether you are looping through numbers generated by range(), items in a list, characters in a string, or any other ordered collection, the same loop syntax applies.

Lists as a Specific Data Structure

A list is a data structure that holds an ordered collection of items. Think of it like a shopping list where you write down items to buy, except in Python you put commas between items instead of writing each on a separate line. A list is itself a sequence, which means it maintains order and can be iterated through.

Lists are sequences, but not all sequences are lists. For example, range() is a sequence, but it is not a list. Understanding this distinction is crucial for writing efficient Python code.

How range() Generates Numbers One at a Time

The range() function creates a sequence, but it does not immediately create all the numbers. Instead, it generates numbers lazily, meaning it produces one number at a time, only when the for loop asks for it. This is an important distinction. When you write a for loop like 'for i in range(1000000):', Python does not create a million numbers in memory all at once. It creates them on demand as the loop runs.

This lazy evaluation is efficient. If you only need to loop through the first 10 numbers out of a million, range() will only generate those 10. The range object remembers its start, stop, and step values, and calculates each number when the for loop requests it.

Materializing a Sequence into a List

If you want to see all the numbers from range() immediately, or if you need to store them in memory for repeated access, you can convert the range object into a list using the list() function. When you call list(range()), Python materializes the entire sequence at once, creating a list that contains all the numbers. This uses more memory than range() alone, but it gives you a concrete list object that you can inspect, modify, and reuse.

Use range() when you only need to loop through a sequence once. Use list(range()) when you need to see all values immediately, store them for later use, or perform list-specific operations like indexing or modification.

The Difference in Action

createscreatesrange(5)Lazy sequence objectMemory usedSmall (stores start, stop,step only)list(range(5))[0, 1, 2, 3, 4]Memory usedLarger (stores all 5numbers)
What's the difference between how range() generates numbers one-at-a-time versus how list(range()) creates them all at once?

When you use range(5), Python creates a range object that remembers the parameters. It does not store 0, 1, 2, 3, 4 in memory. Instead, when a for loop asks for the first number, it calculates and returns 0. When asked for the next, it calculates and returns 1, and so on. When you use list(range(5)), Python immediately calculates all five numbers and stores them in a list: [0, 1, 2, 3, 4]. Both approaches work with for loops, but they use memory differently.

How the For Loop Requests Items

When you write a for loop like 'for i in range(5):', the loop does not grab all five numbers at once. Instead, it follows a request-and-receive pattern. On the first iteration, the loop asks range(5) for the first item, and range responds with 0. On the second iteration, the loop asks for the next item, and range responds with 1. This continues until range has no more items to provide, at which point the loop stops. This mechanism works the same way whether you are looping through a range object or a list.

iteratesiteration 1range respondsiteration 2range respondsiteration 3range respondsfor loopRequest item 1Request item 2Request item 3range(5)Receive 0Receive 1Receive 2
How does the for loop pull the next number from range() each time it loops?

Sequences and Lists in Practice

Comparing range() and list(range()) in a Loop

You need to print the numbers 0 through 4. You can use either range(5) or list(range(5)) in your for loop. What is the practical difference between these two approaches?

Using range(5): When you write 'for i in range(5): print(i)', the loop asks range(5) for each number one at a time. The range object calculates 0, then 1, then 2, and so on. Memory usage is minimal because range only stores the parameters (start=0, stop=5, step=1), not the actual numbers.

Using list(range(5)): When you write 'for i in list(range(5)): print(i)', Python first converts range(5) into [0, 1, 2, 3, 4]. Then the loop iterates through this list. Memory usage is higher because the list stores all five numbers.

Which to use: For a simple loop that prints numbers, both work identically from the learner's perspective. However, if you are looping through a million numbers, range(5) is more efficient. If you need to access the numbers multiple times or modify them, list(range(5)) is more appropriate.

Both produce the same output (0, 1, 2, 3, 4 on separate lines), but range(5) uses less memory during the loop.

Lists as Sequences

A list is a sequence, which means you can iterate through it with a for loop just like you iterate through range(). The difference is that a list is a concrete data structure: it stores all its items in memory, and you can access them by index, modify them, add new items, or remove items. When you loop through a list, the for loop requests each item from the list one at a time, just as it does with range().

The for..in loop works for any sequence. You can use the same loop syntax for range(), lists, strings, and other sequence types. This consistency is one of Python's strengths.

Common Misconceptions

  • Thinking range() creates all numbers immediately

    range() is lazy. It only generates numbers when asked. It stores only the start, stop, and step values, not the actual numbers.

    Fix: Use range() for loops when you only need to iterate once. Use list(range()) only if you need to store or repeatedly access all the numbers.

  • Confusing sequences with lists

    A sequence is a general concept: any ordered collection that can be iterated through. A list is a specific data structure. range() is a sequence but not a list.

    Fix: Remember: all lists are sequences, but not all sequences are lists. range(), strings, and tuples are also sequences.

  • Assuming you must convert range() to a list to use it in a loop

    The for loop works directly with range() objects. Converting to a list is unnecessary overhead for simple iteration.

    Fix: Use range() directly in for loops. Only convert to a list if you have a specific reason (e.g., you need to access items by index, modify the sequence, or use it multiple times).

When to Use range() vs list(range())

ScenarioUse range()Use list(range())
Simple for loop iterationYes — efficient and cleanNo — unnecessary conversion
Need to access items by index multiple timesNo — range() recalculates each timeYes — list stores values for fast access
Need to modify the sequenceNo — range() is immutableYes — lists can be modified
Working with very large rangesYes — minimal memory usageNo — uses significant memory
Need to see all values immediatelyNo — values are generated on demandYes — all values are materialized

Practice: Recognizing Sequences

MEDIUM

For each of the following, identify whether it is a sequence, a list, both, or neither. Explain your reasoning. (1) range(10), (2) [1, 2, 3, 4, 5], (3) 'hello', (4) a single integer like 42, (5) a dictionary like {'a': 1, 'b': 2}.

Hints
  • A sequence is an ordered collection that can be iterated through.
  • A list is a specific data structure that stores items and allows modification.
  • Strings are sequences of characters.
  • Dictionaries are not ordered collections in the way sequences are.

Key Takeaways

  • A sequence is an ordered collection of items that can be iterated through. Lists are a specific type of sequence, but not all sequences are lists.
  • range() generates numbers lazily, one at a time, only when the for loop requests them. This is memory-efficient for large ranges.
  • list(range()) materializes all numbers at once into a concrete list. Use this when you need to store, modify, or repeatedly access the numbers.
  • The for loop requests items from any sequence one at a time using the same syntax, whether the sequence is a range object, a list, or another iterable.
  • Use range() for simple iteration. Use list(range()) when you need list-specific features like indexing, modification, or multiple passes through the data.