chapter
    Function Scope, Closures and Default Arguments Notes for GATE DA

    Function Scope, Closures and Default Arguments notes for GATE DA: 16 study cards covering concepts, formulas, shortcuts and exam traps, plus solved practice q

    function scope closures and default arguments notes

    Chapter Roadmap: Function Scope, Closures and Default Arguments

    Chapter Roadmap

    Chapter Journey

    1
    Mutable Default Arguments (Current Topic)
    • Why default arguments are evaluated only once
    • The shared state trap with lists and dictionaries
    • The None pattern for safe defaults
    2
    Closures and Enclosed State
    • How inner functions capture variables from outer scopes
    • The nonlocal keyword and state retention
    • Factory functions and decorators

    Chapter Mastery

    • Predict the exact state of mutable default arguments across multiple function calls.
    • Distinguish between variable mutation and reassignment in default arguments.
    • Trace closure state and identify enclosed variables in nested functions.
    Weight hint: Both topics are highly conceptual. Expect code-tracing questions where a small misunderstanding of evaluation time or scope leads to the wrong output.

    Topic Hero: The Mutable Default Argument Trap

    Topic Hero: The Mutable Default Argument Trap

    The Core Intuition

    In Python, default arguments are evaluated exactly once — when the def statement is executed, not when the function is called.

    If the default value is a mutable object (like a list, dictionary, or set), that single object is shared across all function calls that use the default.

    Why this happens

    When Python reads a function definition, it evaluates the default expressions and stores them in the function object. Every time you call the function without providing that argument, Python passes the exact same object reference.

    Argument Type Evaluated When Behavior
    Immutable (int, str, tuple) Definition time Safe. Reassignment creates a new local object.
    Mutable (list, dict, set) Definition time Trap. Mutations affect the shared default object.

    Definition Time vs Call Time Evaluation

    Definition Time vs Call Time Evaluation

    1. Definition Time

    The def keyword is executed. Python creates the function object and evaluates the default arguments.

    2. Call Time

    The function is invoked. Python binds the arguments to the parameter names and executes the body.

    Inspecting the Defaults

    You can actually see the stored default objects using the __defaults__ attribute:

    def my_func(a, b=[]):
        pass
    print(my_func.__defaults__)

    This prints ([],). The list is created once and stored inside the function object. Every call that uses the default b gets a reference to this exact same list.

    13 more cards in this chapter

    Try a question

    Answer it here to see how it works. Nothing is recorded until you sign in.

    Question 1
    Level 1: Warm-up

    Which of the following statements about the None pattern for default arguments in Python is true?

    Question 2
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=None):

    if lst is None:

    lst = []

    lst.append(x)

    return lst</pre>

    Which of the following statements about the behavior of this function is true?

    Question 3
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    f(1)

    f(2, [])

    f(3)</pre>

    What is the minimum number of additional calls using the default argument needed to make the default list contain exactly 5 elements?

    Question 4
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst = [x]

    return lst

    result1 = f(1)

    result2 = f(2)</pre>

    Assertion: The default list object remains empty after both function calls.

    Reason: The assignment lst = [x] creates a new local list object without modifying the shared default.

    Which option is correct?

    Question 5
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(k, v, d={}):

    d[k] = v

    f('x', 1)

    f('y', 2)

    f('z', 3, {})

    f('x', 4)

    What is the minimum number of additional calls using the default argument needed to make the default dictionary contain exactly 5 key-value pairs?</pre>

    Question 6
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst = lst + [x]

    return lst

    result1 = f(1)

    result2 = f(2)</pre>

    Assertion: The default list object remains empty after both function calls.

    Reason: The expression <code>lst + [x]</code> creates a new list object and reassigns the local variable <code>lst</code> to it, leaving the original default list unmodified.

    Which option is correct?

    Question 7
    Level 1: Warm-up

    Consider the following Python code:

    def make_list():

    lst = []

    def add(x):

    lst.append(x)

    return lst

    return add

    f1 = make_list()

    f2 = make_list()

    f1(1)

    f2(2)

    f1(3)

    What is the minimum number of distinct list objects in memory after executing this code?

    Question 8
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    return sum(lst)

    f(-5)

    print(f(-2))</pre>

    What is the output of this program?

    Question 9
    Level 1: Warm-up

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    return min(lst)

    f(-10)

    f(-20)

    print(f(-5))</pre>

    What is the output of this program?

    Question 10
    Level 1: Warm-up

    Consider the following Python code snippets:

    (P) def outer():

    x = 10

    def inner():

    print(x)

    (Q) def outer():

    x = 10

    def inner():

    return x

    return inner

    (R) def outer():

    x = 10

    def inner(y):

    return y + 5

    return inner

    (S) def outer():

    x = 10

    def inner():

    x = 20

    return x

    return inner

    How many of these snippets create a valid Python closure?

    Free preview ends here

    Login to view the complete notes

    Creating an account is free. You get the rest of this chapter, step-by-step solutions, and a study plan built around the topics you are actually weak at.

    Why MastersUp

    Personalised first. High quality throughout.

    Most platforms hand everyone the same content. Here the content moves with your performance, topic by topic.

    Built around you, not around a syllabus PDF

    Every answer you give moves your topic-level intelligence rate. The next question, the next revision card and tomorrow's plan all change with it.

    Revision that hits your weak spots

    We only revise topics you have actually attempted and are still below the safe bar on — never the same chapter on repeat.

    Questions calibrated to the real exam

    Each question carries a measured toughness. You are served a rung above your current level, so practice keeps stretching you.

    Notes written for recall, not for volume

    Full lesson cards for first study, curated short-note cards for the last mile — with derivations, traps and exam patterns marked.

    One place for everything

    Notes, chapter practice, previous-year questions, test series and full-length papers — all feeding one picture of your preparation.

    Honest progress

    No vanity streaks. Progress here means chapters mastered and accuracy that held up on harder questions.

    Unlock the whole course

    Full notes and short notes, the complete question bank with worked solutions, mock tests, full-length papers, and an adaptive plan that rebuilds itself as you improve.

    Function Scope, Closures and Default Arguments Notes for GATE DA

    Function Scope, Closures and Default Arguments notes for GATE DA: 16 study cards covering concepts, formulas, shortcuts and exam traps, plus solved practice questions.

    Chapter Roadmap: Function Scope, Closures and Default Arguments

    Chapter Roadmap

    Chapter Journey

    1
    Mutable Default Arguments (Current Topic)
    • Why default arguments are evaluated only once
    • The shared state trap with lists and dictionaries
    • The None pattern for safe defaults
    2
    Closures and Enclosed State
    • How inner functions capture variables from outer scopes
    • The nonlocal keyword and state retention
    • Factory functions and decorators

    Chapter Mastery

    • Predict the exact state of mutable default arguments across multiple function calls.
    • Distinguish between variable mutation and reassignment in default arguments.
    • Trace closure state and identify enclosed variables in nested functions.
    Weight hint: Both topics are highly conceptual. Expect code-tracing questions where a small misunderstanding of evaluation time or scope leads to the wrong output.

    Topic Hero: The Mutable Default Argument Trap

    Topic Hero: The Mutable Default Argument Trap

    The Core Intuition

    In Python, default arguments are evaluated exactly once — when the def statement is executed, not when the function is called.

    If the default value is a mutable object (like a list, dictionary, or set), that single object is shared across all function calls that use the default.

    Why this happens

    When Python reads a function definition, it evaluates the default expressions and stores them in the function object. Every time you call the function without providing that argument, Python passes the exact same object reference.

    Argument Type Evaluated When Behavior
    Immutable (int, str, tuple) Definition time Safe. Reassignment creates a new local object.
    Mutable (list, dict, set) Definition time Trap. Mutations affect the shared default object.

    Definition Time vs Call Time Evaluation

    Definition Time vs Call Time Evaluation

    1. Definition Time

    The def keyword is executed. Python creates the function object and evaluates the default arguments.

    2. Call Time

    The function is invoked. Python binds the arguments to the parameter names and executes the body.

    Inspecting the Defaults

    You can actually see the stored default objects using the __defaults__ attribute:

    def my_func(a, b=[]):
        pass
    print(my_func.__defaults__)

    This prints ([],). The list is created once and stored inside the function object. Every call that uses the default b gets a reference to this exact same list.

    Tracing the Classic append_to_lst Example

    Tracing the Classic append_to_lst Example

    The Code

    def append_to_lst(val, lst=[]):
        lst.append(val)
        return lst
    
    print(append_to_lst(1))
    print(append_to_lst(2))
    print(append_to_lst(3, []))
    Default List Object
    Explicit List Object (Call 3)
    Current Call: 0

    Step-by-Step Trace

    • Call 1: append_to_lst(1)
      val is 1. lst binds to default []. Mutates default to [1]. Returns [1].
    • Call 2: append_to_lst(2)
      val is 2. lst binds to default (now [1]). Mutates to [1, 2]. Returns [1, 2].
    • Call 3: append_to_lst(3, [])
      val is 3. lst is explicitly provided as new []. Mutates explicit list to [3]. Returns [3]. Default remains [1, 2].

    Final Output

    [1]
    [1, 2]
    [3]

    Function Scope, Closures and Default Arguments: Solved Questions with Step-by-Step Explanations (10 Problems)

    Question 1 · Programming, Data Structures and Algorithms MCQ

    Which of the following statements about the None pattern for default arguments in Python is true?

    1. A.

      None is evaluated at call time, bounding the state to each individual call.

    2. B.

      None is immutable, bounding the default to a safe, unchangeable reference.

    3. C.

      The None pattern requires the function to return None if the argument is missing.

    4. D.

      Using None as a default automatically creates a new list for each call without explicit checks.

    Correct Answer:

    B

    Step-by-Step Solution

    Key idea: This is a statement truth question about the None pattern, recognizable by evaluating the properties of None and the pattern's mechanics.

    Step 1: Evaluate option A: "None is evaluated at call time" - False. All default arguments, including None, are evaluated at definition time.

    Step 2: Evaluate option B: "None is immutable, bounding the default to a safe, unchangeable reference" - True. None cannot be mutated in place, so it safely avoids the shared state trap.

    Step 3: Evaluate option C: "requires the function to return None" - False. The pattern dictates the default argument, not the return value.

    Step 4: Evaluate option D: "automatically creates a new list... without explicit checks" - False. Python does not automatically create objects; the programmer must explicitly write the <code>if lst is None: lst = []</code> check.

    Answer: B

    Question 2 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=None):

    if lst is None:

    lst = []

    lst.append(x)

    return lst</pre>

    Which of the following statements about the behavior of this function is true?

    1. A.

      If f(10, None) is called, it raises a TypeError because None cannot be appended to.

    2. B.

      If f(10) is called twice, the second call returns [10, 10] because the default list is shared.

    3. C.

      If f(10, None) is called, it creates a new list, appends 10, and returns [10].

    4. D.

      The lst = [] assignment modifies the shared default argument for all future calls.

    Correct Answer:

    C

    Step-by-Step Solution

    Key idea: This is a statement truth question about the None pattern, recognizable by evaluating the properties of None and the pattern's mechanics.

    Step 1: Evaluate option A: "raises a TypeError" - False. The code explicitly checks if lst is None: and assigns a new list before calling append().

    Step 2: Evaluate option B: "second call returns [10, 10]" - False. The None pattern ensures that each call that uses the default gets its own fresh list, isolating the state.

    Step 3: Evaluate option C: "creates a new list, appends 10, and returns [10]" - True. Passing None explicitly triggers the if block, creating a fresh list for that specific call.

    Step 4: Evaluate option D: "modifies the shared default" - False. The assignment lst = [] reassigns the local variable lst to a new object; it does not mutate the None default.

    Answer: C

    Question 3 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    f(1)

    f(2, [])

    f(3)</pre>

    What is the minimum number of additional calls using the default argument needed to make the default list contain exactly 5 elements?

    1. A.

      3

    2. B.

      2

    3. C.

      5

    4. D.

      4

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: This is a contradiction tracing question where explicit arguments bypass the default, recognizable by the mix of default and explicit calls.

    Step 1: Trace the state of the default list after each call.

    Step 2: Call f(1): uses default []. Appends 1. Default becomes [1].

    Step 3: Call f(2, []): provides an explicit list. Does NOT use the default. The default remains [1].

    Step 4: Call f(3): uses default [1]. Appends 3. Default becomes [1, 3].

    Step 5: After these calls, the default list has exactly 2 elements.

    Step 6: To reach exactly 5 elements, we need 5 - 2 = 3 more elements.

    Step 7: Each additional call using the default appends exactly 1 element, so we need 3 more calls.

    Answer: A

    Question 4 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst = [x]

    return lst

    result1 = f(1)

    result2 = f(2)</pre>

    Assertion: The default list object remains empty after both function calls.

    Reason: The assignment lst = [x] creates a new local list object without modifying the shared default.

    Which option is correct?

    1. A.

      Both assertion and reason are true, and reason is the correct explanation of assertion.

    2. B.

      Both assertion and reason are true, but reason is NOT the correct explanation of assertion.

    3. C.

      Assertion is true, but reason is false.

    4. D.

      Assertion is false, but reason is true.

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: This is an assertion-reason question about reassignment vs mutation, recognizable by the <code>lst = [x]</code> syntax and the assertion-reason format.

    Step 1: Evaluate the assertion: "The default list object remains empty after both function calls."

    Step 2: Trace the code: In f(1), lst binds to the default []. Then lst = [x] reassigns the local variable lst to a new list [1]. The default [] is untouched.

    Step 3: In f(2), lst binds to the default [] again (still empty). Then lst = [x] reassigns lst to [2]. The default [] is still untouched.

    Step 4: The assertion is TRUE.

    Step 5: Evaluate the reason: "The assignment lst = [x] creates a new local list object without modifying the shared default."

    Step 6: This is TRUE. The equals sign (=) performs reassignment, creating a new object and pointing the local name to it. It does not mutate the original default object.

    Step 7: Does the reason explain the assertion? YES. The reason correctly identifies that reassignment creates a new local object, which is exactly why the default remains empty.

    Answer: A

    Question 5 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(k, v, d={}):

    d[k] = v

    f('x', 1)

    f('y', 2)

    f('z', 3, {})

    f('x', 4)

    What is the minimum number of additional calls using the default argument needed to make the default dictionary contain exactly 5 key-value pairs?</pre>

    1. A.

      4

    2. B.

      2

    3. C.

      1

    4. D.

      3

    Correct Answer:

    D

    Step-by-Step Solution

    Key idea: This is a contradiction tracing question where explicit arguments bypass the default, recognizable by the mix of default and explicit calls.

    Step 1: Trace the state of the default dictionary after each call.

    Step 2: Call f('x', 1): uses default {}. d['x'] = 1. Default becomes {'x': 1}.

    Step 3: Call f('y', 2): uses default {'x': 1}. d['y'] = 2. Default becomes {'x': 1, 'y': 2}.

    Step 4: Call f('z', 3, {}): provides an explicit dictionary. Does NOT use the default. The default remains {'x': 1, 'y': 2}.

    Step 5: Call f('x', 4): uses default {'x': 1, 'y': 2}. d['x'] = 4. Default becomes {'x': 4, 'y': 2}.

    Step 6: After these calls, the default dictionary has exactly 2 key-value pairs.

    Step 7: To reach exactly 5 key-value pairs, we need 5 - 2 = 3 more pairs.

    Step 8: Each additional call using the default adds exactly 1 new key, so we need 3 more calls.

    Answer: D

    Question 6 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst = lst + [x]

    return lst

    result1 = f(1)

    result2 = f(2)</pre>

    Assertion: The default list object remains empty after both function calls.

    Reason: The expression <code>lst + [x]</code> creates a new list object and reassigns the local variable <code>lst</code> to it, leaving the original default list unmodified.

    Which option is correct?

    1. A.

      Both assertion and reason are true, and reason is the correct explanation of assertion.

    2. B.

      Both assertion and reason are true, but reason is NOT the correct explanation of assertion.

    3. C.

      Assertion is true, but reason is false.

    4. D.

      Assertion is false, but reason is true.

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: This is an assertion-reason question about reassignment vs mutation, recognizable by the <code>lst = lst + [x]</code> syntax and the assertion-reason format.

    Step 1: Evaluate the assertion: "The default list object remains empty after both function calls."

    Step 2: Trace the code: In f(1), lst binds to the default []. Then lst = lst + [x] evaluates to [] + [1] = [1], and reassigns the local variable lst to this new list. The default [] is untouched.

    Step 3: In f(2), lst binds to the default [] again (still empty). Then lst = lst + [x] evaluates to [] + [2] = [2], and reassigns lst. The default [] is still untouched.

    Step 4: The assertion is TRUE.

    Step 5: Evaluate the reason: "The expression lst + [x] creates a new list object and reassigns the local variable lst to it, leaving the original default list unmodified."

    Step 6: This is TRUE. The plus operator (+) on lists creates a new list object. The equals sign (=) performs reassignment, pointing the local name to this new object. It does not mutate the original default object.

    Step 7: Does the reason explain the assertion? YES. The reason correctly identifies that concatenation creates a new object and reassignment preserves the default, which is exactly why the assertion is true.

    Answer: A

    Question 7 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    def make_list():

    lst = []

    def add(x):

    lst.append(x)

    return lst

    return add

    f1 = make_list()

    f2 = make_list()

    f1(1)

    f2(2)

    f1(3)

    What is the minimum number of distinct list objects in memory after executing this code?

    1. A.

      2

    2. B.

      1

    3. C.

      3

    4. D.

      5

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: Each call to the outer function creates a new closure instance with its own isolated state.

    Step 1: f1 = make_list() creates a new closure. A new list object lst1 is created and enclosed in f1's cell.

    Step 2: f2 = make_list() creates another closure. A new list object lst2 is created and enclosed in f2's cell. lst1 and lst2 are distinct objects.

    Step 3: f1(1) appends 1 to lst1. lst1 is now [1].

    Step 4: f2(2) appends 2 to lst2. lst2 is now [2].

    Step 5: f1(3) appends 3 to lst1. lst1 is now [1, 3].

    Step 6: Total distinct list objects: lst1 and lst2. Count = 2.

    Answer: A

    Question 8 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    return sum(lst)

    f(-5)

    print(f(-2))</pre>

    What is the output of this program?

    1. A.

      -7

    2. B.

      -2

    3. C.

      -5

    4. D.

      7

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: This is a casework tracing question for shared mutable state, recognizable by multiple calls modifying a numeric aggregate.

    Step 1: The default lst=[] is created once at definition time.

    Step 2: First call f(-5): lst binds to []. lst.append(-5) makes it [-5]. The function returns sum([-5]) = -5. (This return value is not printed).

    Step 3: Second call f(-2): lst binds to the same default list, which is now [-5]. lst.append(-2) makes it [-5, -2].

    Step 4: The function returns sum([-5, -2]) = -7. This value is printed.

    Answer: A

    Question 9 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code:

    <pre>def f(x, lst=[]):

    lst.append(x)

    return min(lst)

    f(-10)

    f(-20)

    print(f(-5))</pre>

    What is the output of this program?

    1. A.

      -10

    2. B.

      -20

    3. C.

      -5

    4. D.

      5

    Correct Answer:

    B

    Step-by-Step Solution

    Key idea: This is a casework tracing question for shared mutable state, recognizable by multiple calls modifying a numeric aggregate.

    Step 1: The default lst=[] is created once at definition time.

    Step 2: First call f(-10): lst binds to []. lst.append(-10) makes it [-10]. The function returns min([-10]) = -10.

    Step 3: Second call f(-20): lst binds to the same default list, which is now [-10]. lst.append(-20) makes it [-10, -20]. The function returns min([-10, -20]) = -20.

    Step 4: Third call f(-5): lst binds to the default list, which is now [-10, -20]. lst.append(-5) makes it [-10, -20, -5].

    Step 5: The function returns min([-10, -20, -5]) = -20. This value is printed.

    Answer: B

    Question 10 · Programming, Data Structures and Algorithms MCQ

    Consider the following Python code snippets:

    (P) def outer():

    x = 10

    def inner():

    print(x)

    (Q) def outer():

    x = 10

    def inner():

    return x

    return inner

    (R) def outer():

    x = 10

    def inner(y):

    return y + 5

    return inner

    (S) def outer():

    x = 10

    def inner():

    x = 20

    return x

    return inner

    How many of these snippets create a valid Python closure?

    1. A.

      1

    2. B.

      2

    3. C.

      3

    4. D.

      4

    Correct Answer:

    A

    Step-by-Step Solution

    Key idea: A closure requires 3 strict criteria: nested function, enclosed reference, and returned by outer function.

    Step 1: Analyze (P). Nested function: yes. Enclosed reference: yes (prints x). Returned: no (outer doesn't return inner). Not a closure.

    Step 2: Analyze (Q). Nested function: yes. Enclosed reference: yes (returns x). Returned: yes (returns inner). Valid closure.

    Step 3: Analyze (R). Nested function: yes. Enclosed reference: no (inner doesn't reference x). Returned: yes. Not a closure.

    Step 4: Analyze (S). Nested function: yes. Enclosed reference: no (x = 20 creates a local variable, doesn't reference outer x). Returned: yes. Not a closure.

    Count: Only (Q) is a valid closure. Count = 1.

    Answer: A

    More notes in this unit