[{"data":1,"prerenderedAt":186},["ShallowReactive",2],{"qa-\u002Fpython\u002Ffundamentals\u002Fmutability":3},{"page":4,"siblings":166,"blog":183},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":157,"seo":158,"seoDescription":159,"stem":160,"subtopic":161,"topic":162,"topicSlug":163,"updated":164,"__hash__":165},"qa\u002Fpython\u002Ffundamentals\u002Fmutability.md","Mutability",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","Python","python",{},true,1,"\u002Fpython\u002Ffundamentals\u002Fmutability",[23,28,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153],{"id":24,"difficulty":25,"q":26,"a":27},"mutable-immutable","easy","Which Python types are mutable and which are immutable?","**Immutable** (the value can never change in place — any \"change\" makes a new\nobject): `int`, `float`, `complex`, `bool`, `str`, `tuple`, `frozenset`,\n`bytes`, and `None`.\n\n**Mutable** (can be modified in place): `list`, `dict`, `set`, `bytearray`,\nand most custom class instances.\n\n```python\ns = \"hello\"\nprint(id(s))\ns += \" world\"        # looks like mutation...\nprint(id(s))         # ...but id() changed — a NEW string was created\n\nnums = [1, 2, 3]\nprint(id(nums))\nnums.append(4)       # genuine in-place mutation\nprint(id(nums))      # same id — same object\n```\n\nWhy it matters: mutability drives how **assignment, function arguments, and\n`==`\u002F`is` behave**. Immutable objects are also **hashable** (usable as dict\nkeys \u002F set members), while mutable ones generally aren't.\n",{"id":29,"difficulty":30,"q":31,"a":32},"default-arg","hard","What is the mutable default argument trap?","A function's default argument is **evaluated once, when the `def` statement\nruns** — not on each call. So a mutable default (like `[]` or `{}`) is created\na single time and **shared across every call**, accumulating state between\ninvocations.\n\n```python\ndef add(item, bucket=[]):   # the same list object on every call\n    bucket.append(item)\n    return bucket\n\nadd(1)   # [1]\nadd(2)   # [1, 2]  \u003C- surprise! the list persisted\nadd(3)   # [1, 2, 3]\n```\n\nThe fix is the standard `None` sentinel — use `None` as the default and create\na fresh object inside the body:\n\n```python\ndef add(item, bucket=None):\n    if bucket is None:      # new list per call\n        bucket = []\n    bucket.append(item)\n    return bucket\n```\n",{"id":34,"difficulty":14,"q":35,"a":36},"is-vs-eq","What is the difference between `is` and `==`?","`==` tests **value equality** — \"do these represent the same data?\" — by\ncalling the object's `__eq__`. `is` tests **identity** — \"are these the *exact\nsame object* in memory?\" — comparing `id()`s. They often agree, but not always.\n\n```python\na = [1, 2, 3]\nb = [1, 2, 3]\na == b   # True  — equal contents\na is b   # False — two distinct list objects\n\nc = a\nc is a   # True  — same object\n```\n\n**Rule: use `is` only for singletons** — `None`, `True`, `False` (e.g.\n`if x is None:`). Don't use `is` for numbers or strings: small ints and some\nstrings *appear* to work because CPython caches\u002Finterns them\n(`256 is 256` -> `True`, but `1000 is 1000` can be `False`), which is an\nimplementation detail you shouldn't rely on.\n",{"id":38,"difficulty":14,"q":39,"a":40},"shallow-deep-copy","What is the difference between a shallow and a deep copy?","A **shallow copy** (`copy.copy`, `list(x)`, `x[:]`, `dict(x)`) creates a new\nouter container but **copies references** to the nested objects — so the inner\nobjects are still **shared**. A **deep copy** (`copy.deepcopy`) recursively\ncopies everything, producing a fully **independent** structure.\n\n```python\nimport copy\noriginal = [[1, 2], [3, 4]]\n\nshallow = copy.copy(original)\nshallow[0].append(99)\nprint(original)   # [[1, 2, 99], [3, 4]]  \u003C- inner list was shared!\n\ndeep = copy.deepcopy(original)\ndeep[0].append(99)\nprint(original)   # unchanged — fully independent\n```\n\nUse a shallow copy when the elements are immutable (or sharing is fine); reach\nfor `deepcopy` when you have nested mutable structures and need true isolation\n(note it's slower and handles cycles).\n",{"id":42,"difficulty":14,"q":43,"a":44},"tuple-mutable","Can a tuple contain mutable objects?","Yes. A tuple's **immutability is shallow**: you can't reassign or resize its\nslots, but each slot is just a reference, and if that reference points to a\nmutable object, that object can still be changed in place.\n\n```python\nt = (1, [2, 3])\nt[1].append(4)     # allowed — mutating the list inside\nprint(t)           # (1, [2, 3, 4])\n\nt[1] = [9]         # TypeError — can't reassign a tuple slot\n```\n\nA consequence interviewers love: a tuple containing a list is **not\nhashable**, because hashability requires all contents to be immutable — so\n`hash((1, [2]))` raises `TypeError`, and such a tuple can't be a dict key.\n",{"id":46,"difficulty":30,"q":47,"a":48},"hashable","Why can't you use a list as a dictionary key?","Dictionary keys (and set members) must be **hashable**. Hashing requires that an\nobject's hash value **stays constant for its lifetime**, which in practice means\nit must be **immutable**. Lists are mutable, so Python deliberately makes them\n**unhashable** — they have no `__hash__`.\n\n```python\nd = {}\nd[[1, 2]] = 'x'   # TypeError: unhashable type: 'list'\nd[(1, 2)] = 'x'   # tuples are immutable -> hashable\n```\n\nThe reason is correctness: a dict places a key in a bucket based on its hash. If\na key could mutate after insertion, its hash would change and you'd never be\nable to find it again. Use an immutable equivalent — a **tuple** instead of a\nlist, a `frozenset` instead of a set.\n",{"id":50,"difficulty":14,"q":51,"a":52},"id-function","What does the id() function tell you?","`id(obj)` returns a unique integer **identity** for an object — in CPython, its\nmemory address. Two names with the same `id` refer to the **same object**; `is`\nis essentially an `id` comparison.\n\n```python\na = [1, 2]\nb = a\nid(a) == id(b)   # True  — same object\na is b           # True\nc = [1, 2]\nid(a) == id(c)   # False — equal value, different object\n```\n\n`id` is useful for understanding aliasing and why mutation through one name is\nvisible through another. The actual value is implementation-specific (don't rely\non it being an address).\n",{"id":54,"difficulty":30,"q":55,"a":56},"small-int-cache","Why does `is` sometimes work for equal integers?","CPython **pre-caches small integers** from **−5 to 256** as singletons, so equal\nvalues in that range share one object and `is` returns `True`. Outside that range,\nequal integers are usually distinct objects.\n\n```python\na = 256\nb = 256\na is b      # True  — cached\n\nc = 257\nd = 257\nc is d      # False — separate objects (in a REPL)\nc == d      # True  — always compare values with ==\n```\n\nThis is a CPython implementation detail, **not** a language guarantee. Never use\n`is` to compare numbers — use `==`. `is` is only for singletons like `None`.\n",{"id":58,"difficulty":30,"q":59,"a":60},"string-interning","What is string interning in Python?","CPython **interns** some strings — storing one shared copy — so identical\nstring literals can be the same object. Short, identifier-like strings are\ninterned automatically; others may not be.\n\n```python\na = \"hello\"\nb = \"hello\"\na is b           # True  — interned literal\n\nc = \"hello world!\"\nd = \"hello world!\"\nc is d           # often False (not auto-interned)\n\nimport sys\ne = sys.intern(\"hello world!\")  # force interning\n```\n\nLike int caching, this is an optimization detail. Always compare string **values**\nwith `==`, not identity with `is`.\n",{"id":62,"difficulty":30,"q":63,"a":64},"augmented-tuple","What happens when you use += on a list inside a tuple?","You get a surprising result: the list **is** mutated, **and** a `TypeError` is\nraised. `t[0] += [3]` does `t[0] = t[0] + [3]` — the `+=` extends the list in place\n(succeeds), then tries to reassign the tuple slot (fails, since tuples are\nimmutable).\n\n```python\nt = ([1, 2], 'x')\nt[0] += [3]      # TypeError: 'tuple' object does not support item assignment\nprint(t)         # ([1, 2, 3], 'x')  — the list WAS extended!\n```\n\nSo the mutation happens before the assignment error. Use `t[0].extend([3])` if you\nwant to mutate the inner list without the confusing error.\n",{"id":66,"difficulty":14,"q":67,"a":68},"list-slicing-copy","Does slicing a list create a copy?","Yes — slicing produces a **new (shallow) list** containing the same element\nreferences. `lst[:]` is a common idiom for a shallow copy. But the **elements**\nthemselves are shared, so nested mutables are still linked.\n\n```python\na = [1, 2, 3]\nb = a[:]          # shallow copy\nb.append(4)\na                 # [1, 2, 3] — unaffected\n\nnested = [[1], [2]]\ncopy = nested[:]\ncopy[0].append(9)\nnested            # [[1, 9], [2]] — inner list shared\n```\n\nSlicing copies the outer list only; use `copy.deepcopy` for full independence of\nnested structures.\n",{"id":70,"difficulty":30,"q":71,"a":72},"pass-by-object","Is Python pass-by-value or pass-by-reference?","Neither exactly — Python is **pass-by-object-reference** (a.k.a. \"pass by\nassignment\"). The function receives a reference to the same object, so it can\n**mutate** a mutable argument in place, but **rebinding** the parameter doesn't\naffect the caller.\n\n```python\ndef mutate(lst): lst.append(4)     # caller sees this\ndef rebind(lst): lst = [0]         # caller does NOT see this\n\ndata = [1, 2, 3]\nmutate(data); print(data)  # [1, 2, 3, 4]\nrebind(data); print(data)  # [1, 2, 3, 4] (unchanged)\n```\n\nImmutable args (ints, strings, tuples) can't be mutated, so they *appear*\npass-by-value. The key is mutate-in-place vs reassign-the-name.\n",{"id":74,"difficulty":30,"q":75,"a":76},"deepcopy-cycles","How does deepcopy handle circular references?","`copy.deepcopy` tracks already-copied objects in a **memo dictionary**, so it\nhandles **circular references** without infinite recursion — each object is copied\nonce and reused.\n\n```python\nimport copy\na = [1, 2]\na.append(a)            # a contains itself\nb = copy.deepcopy(a)   # works — no infinite loop\nb[2] is b              # True — the cycle is preserved in the copy\n```\n\nA naive recursive copy would loop forever; `deepcopy`'s memo makes it safe. You\ncan customize copying via `__deepcopy__`\u002F`__copy__` methods on your classes.\n",{"id":78,"difficulty":14,"q":79,"a":80},"is-pitfall","When is using `is` instead of `==` a bug?","Using `is` to compare **values** is a bug — it tests identity, which only\ncoincidentally matches for cached singletons (small ints, interned strings,\n`None`). It fails unpredictably for other values.\n\n```python\nx = 1000\nx is 1000        # may be False (and raises a SyntaxWarning in 3.8+)\nx == 1000        # True\n\na = \"long string value\"\na is \"long string value\"  # often False\n```\n\nRule: use `==` for value equality; reserve `is` for `None`, `True`, `False`, and\nsentinel objects. Linters flag `is` comparisons with literals.\n",{"id":82,"difficulty":30,"q":83,"a":84},"custom-hashable","How do you make a custom class hashable?","Implement both `__eq__` and `__hash__`, keeping them **consistent**: equal objects\nmust have equal hashes. Base the hash on the same immutable fields used for\nequality.\n\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n    def __eq__(self, other):\n        return (self.x, self.y) == (other.x, other.y)\n    def __hash__(self):\n        return hash((self.x, self.y))\n\n{Point(1, 2), Point(1, 2)}   # one element — treated as equal\n```\n\nDefining `__eq__` **without** `__hash__` makes the class **unhashable** (Python\nsets `__hash__ = None`). Only hash on fields that don't change after creation.\n",{"id":86,"difficulty":14,"q":87,"a":88},"frozen-dataclass","What is a frozen dataclass?","A `@dataclass(frozen=True)` makes instances **immutable** — attempting to set an\nattribute raises `FrozenInstanceError`. Frozen dataclasses also get a `__hash__`\nautomatically, so they're usable as dict keys \u002F set members.\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass Point:\n    x: int\n    y: int\n\np = Point(1, 2)\np.x = 9          # FrozenInstanceError\n{p: \"origin\"}    # hashable\n```\n\nIt's the concise modern way to define immutable value objects with auto-generated\n`__init__`, `__eq__`, `__repr__`, and `__hash__`.\n",{"id":90,"difficulty":25,"q":91,"a":92},"string-immutability","Why are strings immutable in Python?","Once created, a `str` can't be changed — any \"modification\" returns a **new**\nstring. Immutability enables interning, safe use as dict keys (cached hash), thread\nsafety, and predictable behavior.\n\n```python\ns = \"hello\"\ns[0] = \"H\"           # TypeError: 'str' does not support item assignment\ns = s.replace(\"h\", \"H\")  # new string, rebind\ns += \" world\"        # new string each time\n```\n\nRepeated concatenation in a loop creates many throwaway strings — prefer\n`\"\".join(parts)` for efficiency, the Python analog of `StringBuilder`.\n",{"id":94,"difficulty":14,"q":95,"a":96},"tuple-vs-list","What is the difference between a tuple and a list?","- **`list`** — mutable, variable-length, for **homogeneous, changing** sequences.\n- **`tuple`** — immutable, fixed, for **heterogeneous, fixed records** (and\n  hashable, so usable as dict keys).\n\n```python\npoint = (3, 4)        # fixed record — tuple\nscores = [90, 85]     # changing collection — list\nscores.append(70)     #\npoint[0] = 9          # tuples are immutable\n\nd = {(0, 0): \"origin\"}  # tuple key ; list key would fail\n```\n\nTuples are slightly faster and more memory-efficient, and signal \"this shouldn't\nchange.\" Use a list when you need to add\u002Fremove\u002Freorder.\n",{"id":98,"difficulty":30,"q":99,"a":100},"mutate-during-iteration","What happens if you modify a list while iterating over it?","Modifying a list's size during iteration **skips or repeats elements** because the\ninternal index shifts under you — a classic bug (Python doesn't always raise, it\nsilently misbehaves; dicts\u002Fsets *do* raise `RuntimeError`).\n\n```python\nnums = [1, 2, 3, 4]\nfor n in nums:\n    if n % 2 == 0:\n        nums.remove(n)   # skips elements\nprint(nums)              # [1, 3] sometimes wrong for other inputs\n\nnums = [n for n in nums if n % 2]      # build a new list\n```\n\nIterate over a **copy** (`for n in nums[:]`) or, better, build a new list with a\ncomprehension\u002F`filter`.\n",{"id":102,"difficulty":14,"q":103,"a":104},"del-statement","What does the del statement do?","`del` **unbinds a name** (or removes an item\u002Fslice\u002Fattribute) — it doesn't directly\n\"delete\" the object. The object is garbage-collected only when its **reference\ncount hits zero**.\n\n```python\na = [1, 2, 3]\nb = a\ndel a            # unbinds 'a'; the list still lives (b references it)\nprint(b)         # [1, 2, 3]\n\ndel b[0]         # removes an item -> [2, 3]\n```\n\nSo `del a` removes the *name*, not necessarily the value. For container items it\nmutates the container. After `del a`, referencing `a` raises `NameError`.\n",{"id":106,"difficulty":14,"q":107,"a":108},"rebind-vs-mutate","What is the difference between rebinding and mutating?","**Rebinding** points a name at a **new** object (`x = [...]`); it doesn't affect\nother names pointing at the old object. **Mutating** changes an object **in\nplace** (`x.append(...)`); all names referencing it see the change.\n\n```python\na = [1, 2]\nb = a\na.append(3)   # mutate -> b sees it; b == [1, 2, 3]\na = [9]       # rebind -> b unchanged; b == [1, 2, 3]\n```\n\nThis distinction explains most \"why did my other variable change?\" confusion.\nAliases share mutations but not rebinding.\n",{"id":110,"difficulty":30,"q":111,"a":112},"class-mutable-attr","Why is a mutable class attribute shared across instances?","A mutable value assigned at **class level** (not in `__init__`) is **one object\nshared by every instance**. Mutating it through one instance affects all of them —\na common bug.\n\n```python\nclass Cart:\n    items = []          # shared by ALL instances\n    def add(self, x): self.items.append(x)\n\na, b = Cart(), Cart()\na.add(\"apple\")\nb.items                 # ['apple'] — leaked into b!\n\nclass Cart:\n    def __init__(self):\n        self.items = []  # per-instance\n```\n\nInitialize mutable attributes in `__init__` so each instance gets its own.\n",{"id":114,"difficulty":30,"q":115,"a":116},"list-mult-trap","What is the trap with [[]] * 3?","List multiplication copies the **references**, not the objects — so `[[]] * 3`\ncreates three references to the **same** inner list. Mutating one mutates all.\n\n```python\ngrid = [[]] * 3\ngrid[0].append(1)\nprint(grid)          # [[1], [1], [1]] — all share one list!\n\ngrid = [[] for _ in range(3)]  # three distinct lists\ngrid[0].append(1)\nprint(grid)          # [[1], [], []]\n```\n\nThe same applies to `[0] * 3` (fine for immutable ints) vs `[[]] * 3` (broken for\nmutables). Use a comprehension to get independent inner objects.\n",{"id":118,"difficulty":14,"q":119,"a":120},"namedtuple","What is a namedtuple and when do you use it?","`collections.namedtuple` (or `typing.NamedTuple`) creates an **immutable**\ntuple subclass with **named fields** — readable, hashable, lightweight records.\n\n```python\nfrom collections import namedtuple\nPoint = namedtuple(\"Point\", [\"x\", \"y\"])\np = Point(3, 4)\np.x          # 3  — named access\np[0]         # 3  — still index-accessible\np.x = 9      # immutable\n```\n\nIt's great for returning multiple values with clear names while keeping tuple\nsemantics. For more features (defaults, methods, mutability options), a\n`@dataclass` is the modern alternative.\n",{"id":122,"difficulty":30,"q":123,"a":124},"global-nonlocal","What do the global and nonlocal keywords do?","They let you **rebind** a name from an outer scope. `global` targets module-level\nnames; `nonlocal` targets the nearest enclosing function scope. Without them,\nassignment inside a function creates a **new local** instead.\n\n```python\ncount = 0\ndef inc():\n    global count\n    count += 1        # rebinds the module-level count\n\ndef outer():\n    x = 1\n    def inner():\n        nonlocal x\n        x = 2          # rebinds outer's x\n    inner()\n    return x           # 2\n```\n\nNote you only need them to **reassign** — you can *mutate* an outer mutable object\n(e.g. `list.append`) without `global`\u002F`nonlocal`.\n",{"id":126,"difficulty":25,"q":127,"a":128},"copy-methods","What are the ways to copy a list or dict?","Several produce a **shallow** copy; `copy.deepcopy` is the only deep one.\n\n```python\na = [1, 2, 3]\na[:]            # slice copy\na.copy()        # method\nlist(a)         # constructor\n\nd = {\"x\": 1}\nd.copy()        # method\ndict(d)         # constructor\n{**d}           # unpacking\n\nimport copy\ncopy.deepcopy(a)  # deep — independent nested objects\n```\n\nAll the shallow methods share nested mutable elements; choose `deepcopy` when you\nneed full independence (at a performance cost).\n",{"id":130,"difficulty":25,"q":131,"a":132},"none-identity","Why check for None with `is` rather than ==?","`None` is a **singleton** — there's exactly one `None` object — so `is None` is the\ncorrect, fast, and idiomatic identity check. `== None` can be **overridden** by a\ncustom `__eq__`, giving wrong or surprising results.\n\n```python\nif x is None:        # idiomatic, can't be fooled\n    ...\n\nclass Weird:\n    def __eq__(self, other): return True\nWeird() == None      # True  misleading\nWeird() is None      # False\n```\n\nPEP 8 explicitly recommends `is`\u002F`is not` for `None` comparisons.\n",{"id":134,"difficulty":14,"q":135,"a":136},"set-hashable-elements","Why must set elements be hashable?","Like dict keys, set members are stored by **hash** for O(1) membership tests, so\nthey must be **hashable** (and thus effectively immutable). Lists, dicts, and sets\ncan't be set elements; tuples and frozensets can.\n\n```python\n{1, 2, 3}            #\n{[1], [2]}           # TypeError: unhashable type: 'list'\n{(1, 2), (3, 4)}     # tuples are hashable\n{frozenset({1, 2})}  #\n```\n\nIf you need a set of sets, use `frozenset` for the inner ones. The hashability\nrequirement is the same reason lists can't be dict keys.\n",{"id":138,"difficulty":14,"q":139,"a":140},"comprehension-scope","Do comprehensions leak their loop variable?","In **Python 3**, comprehensions have their **own scope**, so the loop variable does\n**not** leak into the surrounding scope (unlike a regular `for` loop, and unlike\nPython 2).\n\n```python\nsquares = [i * i for i in range(5)]\nprint(i)        # NameError — i is local to the comprehension\n\nfor j in range(5):\n    pass\nprint(j)        # 4 — a normal for loop DOES leak\n```\n\nThis avoids accidental variable clobbering. The same isolation applies to set,\ndict, and generator comprehensions.\n",{"id":142,"difficulty":25,"q":143,"a":144},"tuple-unpacking-swap","How does tuple unpacking enable swapping variables?","Python evaluates the **right side first** into a tuple, then unpacks it into the\nleft-side names — so you can swap without a temporary variable.\n\n```python\na, b = 1, 2\na, b = b, a        # builds (2, 1), then unpacks -> a=2, b=1\n\n# also works for multiple\u002Fextended unpacking:\nfirst, *rest = [1, 2, 3, 4]   # first=1, rest=[2, 3, 4]\n```\n\nThe right-hand tuple is fully created before any assignment, which is why the swap\nis atomic and needs no temp. This is immutability of the intermediate tuple at\nwork.\n",{"id":146,"difficulty":14,"q":147,"a":148},"shallow-nested-trap","What is the trap with shallow-copying nested structures?","A shallow copy duplicates the outer container but **shares the nested objects**, so\nmutating a nested element changes both the original and the copy — a subtle\naliasing bug.\n\n```python\nimport copy\noriginal = {\"users\": [\"ada\"]}\nshallow = copy.copy(original)\nshallow[\"users\"].append(\"grace\")\noriginal[\"users\"]    # ['ada', 'grace'] — shared nested list!\n\ndeep = copy.deepcopy(original)\ndeep[\"users\"].append(\"hopper\")\noriginal[\"users\"]    # unchanged\n```\n\nUse `deepcopy` whenever you copy a structure with nested mutables you intend to\nmodify independently.\n",{"id":150,"difficulty":14,"q":151,"a":152},"frozenset","What is a frozenset?","A `frozenset` is an **immutable** version of `set` — same operations\n(union, intersection, membership) but no `add`\u002F`remove`. Because it's immutable,\nit's **hashable**, so it can be a dict key or an element of another set.\n\n```python\nfs = frozenset([1, 2, 3])\nfs.add(4)              # AttributeError — immutable\n{fs: \"a set\"}          # hashable key\n{frozenset({1}), frozenset({2})}  # set of sets\n```\n\nUse it for constant sets and whenever you need a set-like value that must be\nhashable.\n",{"id":154,"difficulty":30,"q":155,"a":156},"eq-hash-contract","What is the relationship between __eq__ and __hash__?","They must stay **consistent**: if `a == b`, then `hash(a) == hash(b)`. Otherwise\nhash-based containers (dict, set) misbehave — an object you stored becomes\nunfindable.\n\n```python\nclass Money:\n    def __init__(self, cents): self.cents = cents\n    def __eq__(self, o): return self.cents == o.cents\n    # defined __eq__ but not __hash__ -> unhashable\n{Money(100)}   # TypeError: unhashable type: 'Money'\n```\n\nDefining `__eq__` sets `__hash__` to `None` automatically (making instances\nunhashable) unless you also define `__hash__`. Hash only on fields that never\nchange after creation, or make the object immutable.\n",null,{"description":11},"Python interview questions on mutable vs immutable types, the mutable default argument trap, is vs ==, and shallow vs deep copy.","python\u002Ffundamentals\u002Fmutability","Mutability & Data Types","Fundamentals","fundamentals","2026-06-17","lfA_zY1n_f1sWG-yTVHSPtktZt0M8Eoy085LnkCD_To",[167,168,171,175,179],{"subtopic":161,"path":21,"order":20},{"subtopic":169,"path":170,"order":12},"Variables, Scope & the LEGB Rule","\u002Fpython\u002Ffundamentals\u002Fscope-legb",{"subtopic":172,"path":173,"order":174},"Numbers & Operators","\u002Fpython\u002Ffundamentals\u002Fnumbers-operators",3,{"subtopic":176,"path":177,"order":178},"Strings & String Formatting","\u002Fpython\u002Ffundamentals\u002Fstrings-formatting",4,{"subtopic":180,"path":181,"order":182},"Truthiness & Type Conversion","\u002Fpython\u002Ffundamentals\u002Ftruthiness-conversion",5,{"path":184,"title":185},"\u002Fblog\u002Fpython-mutability-mutable-immutable-types","Python Mutability — Mutable vs Immutable Types Explained",1781808676772]