Difficulty: Beginner to Intermediate | Prerequisites: Basic Python syntax (variables, operators, print statements)
This material covers the bread and butter of writing Python functions that operate on lists: checking conditions across elements, building lists iteratively, and returning results. These patterns appear in nearly every data science workflow, from cleaning datasets to generating sequences for modelling. If you can write a function that loops over a list and applies a condition, you can handle the majority of early ECE 20875 assignments. You should already be comfortable with if/else, for loops, and basic list indexing before tackling this.
Functions that validate or generate sequences (like the Fibonacci sequence) rely on base-case checks, loop iteration, and element-wise comparison. The core skill is combining conditionals with list indexing inside a loop, then returning the right value at the right time.
List
An ordered, mutable collection of items in Python, written with square brackets. Think of it as a numbered row of boxes you can add to, remove from, or change at will.
Function (def)
A reusable block of code that takes inputs (parameters), does something, and optionally hands back a result via return. In simple terms, it is a named recipe you call whenever you need that operation.
return
The statement that immediately exits a function and sends a value back to the caller. If you forget it, Python returns None by default.
range(start, stop)
A built-in that produces a sequence of integers from start up to (but not including) stop. In simple terms, it gives you a counter for your loop.
len()
Returns the number of elements in a list (or other sequence). If seq = [0, 1, 1], then len(seq) is 3.
append()
Adds a single element to the end of a list, modifying it in place. The list grows by one each time you call it.
Recurrence relation
A formula that defines each term of a sequence using one or more previous terms. The Fibonacci sequence is the classic example: each number is the sum of the two before it.
Boolean expression
An expression that evaluates to True or False. Comparisons like seq[0] != 0 and logical connectors like or produce Booleans.
List slicing (seq[start:stop:step])
A way to extract a sub-list from a list by specifying a start index, a stop index, and an optional step. seq[0:1] gives the first element wrapped in a list; seq[i::7] gives every 7th element starting from index i.
The function is_fibonacci_sequence(seq) checks whether an entire list follows the Fibonacci rule.
Guard clause for length: if the list has fewer than 2 elements, return False immediately. A valid Fibonacci sequence needs at least [0, 1].
Check the starting values: the first element must be 0 and the second must be 1. If either fails, return False.
Loop from index 2 onward: for every remaining element, check that seq[i] == seq[i-1] + seq[i-2]. If any element violates this, return False.
Default return: if the loop completes without finding a violation, the sequence is valid, so return True.
The key insight is that the function returns False early at the first failure and only returns True after checking everything. This early-return pattern is extremely common in validation functions.
The function fib(n) builds a list of Fibonacci numbers of length n + 1.
Initialise with [0, 1]: these are always the first two terms.
Handle edge cases first: if n == 0, return just [0] (using slicing: fib_seq[0:1]). If n == 1, return [0, 1].
Build iteratively: loop from 2 to n (inclusive, so use range(2, n+1)), appending fib_seq[i-1] + fib_seq[i-2] each time.
Return the completed list.
Notice the difference between range(2, len(seq)) in the validator (which iterates over existing elements) and range(2, n+1) in the generator (which creates new elements). The +1 in the generator is because range excludes its upper bound, and the spec says the output should have n + 1 elements.
Indexing: seq[0] is the first element, seq[-1] is the last.
Slicing: seq[0:1] returns a new list containing only the first element. This is different from seq[0], which returns the element itself (not wrapped in a list).
Append: fib_seq.append(value) adds value to the end. It modifies the list in place and returns None, so never write fib_seq = fib_seq.append(value).
len(): returns the count of elements. Used in range(2, len(seq)) to iterate up to the last index.
Early return: checking for invalid input at the top of a function and returning immediately keeps the main logic clean and un-nested.
Loop-and-check: iterating over elements and returning False at the first failure, with return True after the loop, is the standard pattern for "does every element satisfy a condition?" problems.
Guard, then build: handling n == 0 and n == 1 before entering the loop avoids index errors and off-by-one mistakes.
Fibonacci recurrence
F₀ = 0, F₁ = 1, Fᵢ = Fᵢ₋₁ + Fᵢ₋₂ for 2 ≤ i < n
Validation template
def is_fibonacci_sequence(seq):
if len(seq) < 2:
return False
if seq[0] != 0 or seq[1] != 1:
return False
for i in range(2, len(seq)):
if seq[i] != seq[i-1] + seq[i-2]:
return False
return TrueGeneration template
def fib(n):
fib_seq = [0, 1]
if n == 0:
return fib_seq[0:1]
if n == 1:
return fib_seq
for i in range(2, n+1):
fib_seq.append(fib_seq[i-1] + fib_seq[i-2])
return fib_seqStarting the loop at index 0 instead of 2. The first two elements are base cases, not computed by the recurrence. Checking seq[0] == seq[-1] + seq[-2] is nonsensical and will produce wrong results.
Using range(2, n) instead of range(2, n+1) when generating. If fib(6) should return 7 elements (indices 0 through 6), the loop must run from 2 to 6 inclusive, which means range(2, 7) or equivalently range(2, n+1).
Confusing seq[0:1] with seq[0]. The slice returns [0] (a list), while the index returns 0 (an integer). The function spec asks for a list, so slicing is correct.
Forgetting that append() returns None. Writing fib_seq = fib_seq.append(x) destroys the list and sets fib_seq to None. Just call fib_seq.append(x) on its own line.
⚠️ The exam gives you a function skeleton with blanks to fill. You need to know where the loop starts, what condition to check, and where to place return True vs return False.
⚠️ Off-by-one errors in range() are the most common point-loser on list-based questions. Always ask: does the upper bound need a +1?
⚠️ The exam tests whether you understand that return inside a loop exits the entire function, not just the current iteration.
⚠️ Edge cases (empty list, single element, n == 0) are tested explicitly. Never skip them.
True or False: is_fibonacci_sequence([0, 1]) returns True.
What does fib(0) return?
True or False: range(2, 5) produces the values 2, 3, 4, 5.
Fill in the blank: fib_seq._____(fib_seq[i-1] + fib_seq[i-2]) adds the next Fibonacci number to the list.
True or False: placing return True inside the for-loop (instead of after it) would make the validator work correctly.
Answers: 1. True (it starts with 0, 1 and has no further elements to violate the rule). 2. [0]. 3. False (it produces 2, 3, 4 only). 4. append. 5. False (it would return True after checking only the first element).
Q: Write a function that checks whether a given list is a valid Fibonacci sequence starting from 0, 1. What should it return for [0, 1, 1, 2, 3, 5]?
A: It should return True. The function checks that the first two elements are 0 and 1, then verifies seq[i] == seq[i-1] + seq[i-2] for all remaining elements.
Q: Why does is_fibonacci_sequence([1, 1, 2, 3, 5, 8]) return False?
A: Because the sequence does not start with 0, 1. The guard clause if seq[0] != 0 or seq[1] != 1 catches this.
Q: Given the function fib(n), what is the length of the returned list when n = 6?
A: 7 elements. The function returns a list of length n + 1, covering indices 0 through n.
Q: In the fib(n) function, why does the loop use range(2, n+1) rather than range(2, n)?
A: Because range excludes its upper bound. To generate terms up to and including index n, the stop value must be n + 1.
Q: What happens if you write fib_seq = fib_seq.append(value) instead of just fib_seq.append(value)?
A: fib_seq becomes None because append() modifies the list in place and returns None. The original list is lost.
This connects to dictionaries and enumerate() because many exam questions ask you to loop over a list while tracking both the index and the value, then store results in a dictionary. The early-return pattern here also appears in filtering with lambda and higher-order functions (covered in the next set of notes). Sequence generation is a stepping stone to understanding how iterative algorithms build datasets for statistical analysis later in the course.
Python list, for loop, range function, append method, list slicing, Fibonacci sequence, recurrence relation, function definition, return statement, early return pattern, guard clause, off-by-one error, len function, Boolean expression, ECE 20875, Python for Data Science, Purdue