DEV Community

Ankur Marwaha
Ankur Marwaha

Posted on

Why Does My Python Function Remember Previous Calls?

Why Does My Python Function Remember Previous Calls?

You call a function twice, expecting each call to start with an empty list. Instead, the second result contains data from the first call.

The cause may be a mutable default argument: a list created when the function is defined and reused across calls.

Let’s see how this connects to references and copying.

1. The list that keeps growing

Consider a function that records completed lessons:

def complete_lesson(lesson, completed=[]):
    completed.append(lesson)
    return completed

print(complete_lesson("Variables"))
print(complete_lesson("Loops"))
Enter fullscreen mode Exit fullscreen mode

Output:

['Variables']
['Variables', 'Loops']
Enter fullscreen mode Exit fullscreen mode

Why does the second call include "Variables"?

Python evaluates default argument expressions when it executes the function definition. It does not create a new default list each time you call the function.

Both calls use and modify the same list.

2. Returned results can share that list too

The sharing becomes clearer when we keep both results:

def complete_lesson(lesson, completed=[]):
    completed.append(lesson)
    return completed

first = complete_lesson("Variables")
second = complete_lesson("Loops")

print(first)
print(second)
print(first is second)
Enter fullscreen mode Exit fullscreen mode

Output:

['Variables', 'Loops']
['Variables', 'Loops']
True
Enter fullscreen mode Exit fullscreen mode

The function returns its list without copying it.

Consequently, first and second refer to the same object. The second call changes what you see through first too.

3. Create a fresh list inside the function

If each call should start independently, use None as the default:

def complete_lesson(lesson, completed=None):
    if completed is None:
        completed = []

    completed.append(lesson)
    return completed

first = complete_lesson("Variables")
second = complete_lesson("Loops")

print(first)
print(second)
print(first is second)
Enter fullscreen mode Exit fullscreen mode

Output:

['Variables']
['Loops']
False
Enter fullscreen mode Exit fullscreen mode

The list expression [] now runs inside the function whenever the argument is omitted or explicitly set to None.

Each of these calls creates a separate list.

4. An explicitly supplied list can still change

The None pattern prevents accidental sharing of a default list. It does not prevent the function from modifying a list supplied by the caller.

def complete_lesson(lesson, completed=None):
    if completed is None:
        completed = []

    completed.append(lesson)
    return completed

my_lessons = ["Variables"]
result = complete_lesson("Loops", my_lessons)

print(my_lessons)
print(result is my_lessons)
Enter fullscreen mode Exit fullscreen mode

Output:

['Variables', 'Loops']
True
Enter fullscreen mode Exit fullscreen mode

The parameter completed refers to the caller’s list. Calling .append() modifies that object.

This behaviour can be useful, but it should be intentional.

5. Copy the input when you want a new result

If the function should preserve the supplied list, copy it before making changes:

def complete_lesson(lesson, completed=None):
    if completed is None:
        updated = []
    else:
        updated = completed.copy()

    updated.append(lesson)
    return updated

my_lessons = ["Variables"]
result = complete_lesson("Loops", my_lessons)

print(my_lessons)
print(result)
print(result is my_lessons)
Enter fullscreen mode Exit fullscreen mode

Output:

['Variables']
['Variables', 'Loops']
False
Enter fullscreen mode Exit fullscreen mode

For this list of strings, a shallow copy provides the separation we need.

For nested mutable data, you would also need to consider whether the inner objects should remain shared.

Three questions to ask when reading a function

When a function accepts or returns a list, check:

  1. When is the list created?
  2. Does the function modify an existing list?
  3. Does the returned value share an object with another variable?

These questions help explain both mutable default arguments and unexpected changes to function inputs.

For further reading, see the official Python tutorial on default argument values.

Free PCEP practice test

Preparing for PCEP? Access my AM Academy free practice test here: https://www.udemy.com/course/python-pcep-30-03-certification-practice-tests-2026/?couponCode=FREE-COURSE-PCEP. I’m the creator of this independent preparation resource; it is not affiliated with or endorsed by the Python Institute.

Top comments (0)