{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreib72xeqeigdyickxpwltlmqvm2eywpkr5myywjwe6747hi3cbs3be",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpuvntgyjxl2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiayw4hngzhoojolffbh3hkumzgyzbafrbdbjdjqvss4em2wv4v7ui"
    },
    "mimeType": "image/webp",
    "size": 38122
  },
  "path": "/ameer_abdullah_68d48c8496/why-python-developers-fail-default-mutable-argument-questions-in-interviews-mhj",
  "publishedAt": "2026-07-05T05:00:00.000Z",
  "site": "https://dev.to",
  "tags": [
    "programming",
    "webdev",
    "python",
    "ai",
    "PyCodeIt",
    "pycodeit.com"
  ],
  "textContent": "#  Why Python Developers Fail Default Mutable Argument Questions in Interviews\n\nThere is a Python behavior that has ended more technical interviews than almost any other single concept. It appears in entry-level screens at startups and senior-level loops at FAANG companies alike.\n\nIt is the **default mutable argument trap**.\n\nHere is the classic version:\n\n\n\n    def add_item(item, items=[]):\n        items.append(item)\n        return items\n\n    print(add_item(\"a\"))\n    print(add_item(\"b\"))\n    print(add_item(\"c\"))\n\n\n\nBefore reading further, write down what you think this prints.\n\nMost people write:\n\n\n\n    ['a']\n    ['b']\n    ['c']\n\n\n\nThe actual output is:\n\n\n\n    ['a']\n    ['a', 'b']\n    ['a', 'b', 'c']\n\n\n\nIf you got it wrong, you are in good company. If you got it right, this article explains why it works this way and what variations interviewers use to trip up people who know the basic version.\n\n##  Why This Happens\n\nIn Python, default argument values are evaluated **once when the function is defined** , not each time the function is called.\n\nThat single sentence is the entire explanation. The list `[]` in `def add_item(item, items=[])` is created once when Python parses the function definition. It is stored as part of the function object itself. Every call to `add_item` that does not pass an explicit `items` argument shares that same list object.\n\nYou can see this yourself by checking the function's internal attributes:\n\n\n\n    print(add_item.__defaults__)\n    # After three calls, this outputs: (['a', 'b', 'c'],)\n\n\n\nThe default value lives inside the function and mutates with each call.\n\n##  The Trace Table for This Problem\n\nA trace table helps visualize how the state changes across executions.\n\nCall | item | items (default object) | After append | Returns\n---|---|---|---|---\n`add_item(\"a\")` | \"a\" | `[]` | `[\"a\"]` | `[\"a\"]`\n`add_item(\"b\")` | \"b\" | `[\"a\"]` | `[\"a\", \"b\"]` | `[\"a\", \"b\"]`\n`add_item(\"c\")` | \"c\" | `[\"a\", \"b\"]` | `[\"a\", \"b\", \"c\"]` | `[\"a\", \"b\", \"c\"]`\n\nThe key column is the third one. The default object does not reset between calls. It carries its state forward.\n\n##  How Interviewers Make It Harder\n\nOnce you know the basic trap, interviewers add layers. Here are the three most common variations:\n\n###  Variation 1: The dictionary default\n\nSame behavior, different data structure.\n\n\n\n    def update_config(key, value, config={}):\n        config[key] = value\n        return config\n\n    print(update_config(\"debug\", True))\n    print(update_config(\"verbose\", False))\n\n\n\n**Output:**\n\n\n\n    {'debug': True}\n    {'debug': True, 'verbose': False}\n\n\n\n###  Variation 2: The conditional write\n\nThe second call does not duplicate because of the conditional check, but the underlying list still persists.\n\n\n\n    def add_if_missing(item, items=[]):\n        if item not in items:\n            items.append(item)\n        return items\n\n    print(add_if_missing(\"x\"))\n    print(add_if_missing(\"x\"))\n    print(add_if_missing(\"y\"))\n\n\n\n**Output:**\n\n\n\n    ['x']\n    ['x']\n    ['x', 'y']\n\n\n\n###  Variation 3: The None pattern fix\n\nInterviewers sometimes show you the correct version and ask you to explain exactly why it works:\n\n\n\n    def add_item_fixed(item, items=None):\n        if items is None:\n            items = []\n        items.append(item)\n        return items\n\n\n\nHere, `None` is immutable. A new, clean list is created inside the function body on each call where `items` is not provided. The default reference itself never mutates.\n\n##  What the Interviewer Is Actually Testing\n\nWhen an interviewer asks a default mutable argument question, they are not testing whether you memorized this specific quirk. They are testing three deeper things:\n\n  1. **Interpreter Mechanics:** Whether you understand that Python evaluates defaults at definition time, not call time. This requires understanding how the Python interpreter parses and stores function objects.\n\n  2. **State Tracking:** Whether you can trace through the execution manually without running the code. The ability to predict output by reasoning about state is a fundamental skill that transfers to debugging, code review, and system design.\n\n  3. **Best Practices:** Whether you know the correct pattern to avoid the bug. The `None` sentinel pattern is the accepted industry solution. Knowing it exists and explaining why it works demonstrates depth beyond just knowing the trap exists.\n\n\n\n\n##  How to Practice This Type of Problem\n\nThe most effective practice for mutable default argument questions and similar Python traps is dry-run tracing. Reading the code line by line, tracking every variable state in a table, and predicting the output before running it.\n\nI built a free tool called PyCodeIt that generates unique Python tracing problems across this exact category of issues. Each problem is AI-generated so you never see the same question twice, and the explanation after each answer shows you the complete variable trace.\n\nYou can try it out at pycodeit.com without creating an account.\n\n##  Three More Mutable Trap Patterns to Study\n\nIf you want to master pythonic edge cases, make sure to look into these next:\n\n  * Class-level mutable attributes shared across instances\n\n  * The `+=` operator on lists inside functions (modifies in place versus rebinding)\n\n  * Generator expressions that close over loop variables\n\n\n\n\nEach of these works on the same underlying principle as the default mutable argument. Once you understand that Python names are references to objects rather than containers holding values, all of these patterns become completely predictable.\n\n_Written by the developer behind_ PyCodeIt_, a free AI-powered Python dry-run practice platform for technical interview preparation._",
  "title": "Why Python Developers Fail Default Mutable Argument Questions in Interviews"
}