[{"data":1,"prerenderedAt":2148},["ShallowReactive",2],{"fw-python":3},{"framework":4,"topics":15,"qa":140},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Fpython.yml","Python interview questions across language fundamentals, data structures and common gotchas for backend, data and automation roles.","yml","python",{},"Python",3,"frameworks\u002Fpython",1,"QsijsotyAr-3rnhJDWWZmc7hE6HAhylS5t1dKpigMOA",[16,24,33,41,50,59,68,77,86,95,104,113,122,131],{"id":17,"description":18,"extension":7,"frameworkSlug":8,"meta":19,"name":20,"order":13,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Fpython-fundamentals.yml","Mutability, data types and common Python gotchas that come up in nearly every Python interview.",{},"Fundamentals","fundamentals","topics\u002Fpython-fundamentals","s0g44vxzPWFDbmltZbaw52-Rnfw29-sEc6vjnipDrEk",{"id":25,"description":26,"extension":7,"frameworkSlug":8,"meta":27,"name":28,"order":29,"slug":30,"stem":31,"__hash__":32},"topics\u002Ftopics\u002Fpython-data-structures.yml","Lists, tuples, dicts and sets — Python's built-in collections, their performance trade-offs, and the collections module that extends them.",{},"Data Structures",2,"data-structures","topics\u002Fpython-data-structures","5tEAHhQ_2jvDcJvjqh92pHCyvfs7wtBVemoRM7jsmw0",{"id":34,"description":35,"extension":7,"frameworkSlug":8,"meta":36,"name":37,"order":11,"slug":38,"stem":39,"__hash__":40},"topics\u002Ftopics\u002Fpython-iteration.yml","List, dict and set comprehensions, generators, and the iterator protocol — Python's expressive, lazy approach to building and consuming sequences.",{},"Comprehensions & Iteration","iteration","topics\u002Fpython-iteration","MFHt1c7Td8PboHEJiPzmzQr_zE4YWxNM7l1ZZ5NDMkI",{"id":42,"description":43,"extension":7,"frameworkSlug":8,"meta":44,"name":45,"order":46,"slug":47,"stem":48,"__hash__":49},"topics\u002Ftopics\u002Fpython-functions.yml","Arguments and unpacking, closures, decorators and higher-order functions — how Python treats functions as first-class objects.",{},"Functions",4,"functions","topics\u002Fpython-functions","A7vY4l7qHsjEdCjRLnpcHG6gaVS2ZfcWNuyO4ISlSJQ",{"id":51,"description":52,"extension":7,"frameworkSlug":8,"meta":53,"name":54,"order":55,"slug":56,"stem":57,"__hash__":58},"topics\u002Ftopics\u002Fpython-oop.yml","Classes, inheritance and the MRO, dunder methods, properties and dataclasses — object-oriented Python and its data model.",{},"Object-Oriented Programming",5,"oop","topics\u002Fpython-oop","HWQNuZjsu-ieQLL6BEbbeZIHJCEhXocNXPj2wzkQUbQ",{"id":60,"description":61,"extension":7,"frameworkSlug":8,"meta":62,"name":63,"order":64,"slug":65,"stem":66,"__hash__":67},"topics\u002Ftopics\u002Fpython-exceptions.yml","try\u002Fexcept\u002Felse\u002Ffinally, the exception hierarchy, custom exceptions and context managers — robust error handling in Python.",{},"Errors & Exceptions",6,"exceptions","topics\u002Fpython-exceptions","AxiThX7RyYVRhjW0TnsHXhYt19xLlcCrOU2utOgPrfs",{"id":69,"description":70,"extension":7,"frameworkSlug":8,"meta":71,"name":72,"order":73,"slug":74,"stem":75,"__hash__":76},"topics\u002Ftopics\u002Fpython-functional.yml","map, filter and reduce, functools and itertools — composing programs from functions and lazy iterators.",{},"Functional Programming",7,"functional","topics\u002Fpython-functional","yvelsIB4sUvQAtMK3cGW7znvMHUf4DW6XEe6gbzy4Ag",{"id":78,"description":79,"extension":7,"frameworkSlug":8,"meta":80,"name":81,"order":82,"slug":83,"stem":84,"__hash__":85},"topics\u002Ftopics\u002Fpython-modules.yml","The import system, packages, __main__, and virtual environments — how Python code is organized, shared and run.",{},"Modules, Packages & Environments",8,"modules","topics\u002Fpython-modules","a8_a5opZ2WGRb6hv-AG427fbrffe_nPB8563DcP43W8",{"id":87,"description":88,"extension":7,"frameworkSlug":8,"meta":89,"name":90,"order":91,"slug":92,"stem":93,"__hash__":94},"topics\u002Ftopics\u002Fpython-concurrency.yml","Threads and the GIL, multiprocessing, asyncio and concurrent.futures — running Python code concurrently and in parallel.",{},"Concurrency & Parallelism",9,"concurrency","topics\u002Fpython-concurrency","5RIdEXxDtKsbX3veVxq6vhE87mv01LWJ2ut_nkPxEQM",{"id":96,"description":97,"extension":7,"frameworkSlug":8,"meta":98,"name":99,"order":100,"slug":101,"stem":102,"__hash__":103},"topics\u002Ftopics\u002Fpython-internals.yml","Reference counting, garbage collection, object identity and the CPython execution model — how Python manages memory and runs your code.",{},"Memory & Internals",10,"internals","topics\u002Fpython-internals","U2yHsQaj-JeXNyACZ2urxbwgXTTPHaZg1F16EMvjKow",{"id":105,"description":106,"extension":7,"frameworkSlug":8,"meta":107,"name":108,"order":109,"slug":110,"stem":111,"__hash__":112},"topics\u002Ftopics\u002Fpython-typing.yml","Type annotations, the typing module, generics and protocols — optional static typing for modern Python.",{},"Type Hints & Typing",11,"typing","topics\u002Fpython-typing","m9rEesQ-tmtEb8ibbPg4hXIEFkDaRfVfLdvDypVFl-g",{"id":114,"description":115,"extension":7,"frameworkSlug":8,"meta":116,"name":117,"order":118,"slug":119,"stem":120,"__hash__":121},"topics\u002Ftopics\u002Fpython-stdlib.yml","pathlib and os, datetime, regular expressions and JSON\u002FCSV — the batteries-included modules every Python developer reaches for.",{},"Standard Library Essentials",12,"stdlib","topics\u002Fpython-stdlib","sRFqpDw98pJhtlo77axy-qulzRwuEEAdxjjHQKKWSGA",{"id":123,"description":124,"extension":7,"frameworkSlug":8,"meta":125,"name":126,"order":127,"slug":128,"stem":129,"__hash__":130},"topics\u002Ftopics\u002Fpython-testing.yml","pytest and unittest, fixtures and mocking — writing automated tests for Python code.",{},"Testing",13,"testing","topics\u002Fpython-testing","HwvMxQFl9E4PubF9w7vHbjXIlh3Y1LpYkOTKVAmw754",{"id":132,"description":133,"extension":7,"frameworkSlug":8,"meta":134,"name":135,"order":136,"slug":137,"stem":138,"__hash__":139},"topics\u002Ftopics\u002Fpython-idioms.yml","EAFP vs LBYL, PEP 8 style, and the common gotchas and anti-patterns that separate idiomatic Python from translated code.",{},"Pythonic Idioms",14,"idioms","topics\u002Fpython-idioms","WsKznDcUUGEslzKQaB4t9l0sMLRgYpcH3joEEaq7zYA",[141,188,228,262,300,339,377,412,451,486,521,560,595,630,673,711,750,785,819,854,999,1038,1072,1111,1150,1185,1226,1265,1304,1343,1378,1413,1448,1483,1522,1557,1592,1631,1666,1705,1743,1786,1821,1856,1891,1926,1961,1996,2035,2074,2113],{"id":142,"title":143,"body":144,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":152,"navigation":153,"order":11,"path":154,"questions":155,"related":181,"seo":182,"seoDescription":183,"stem":184,"subtopic":185,"topic":90,"topicSlug":92,"updated":186,"__hash__":187},"qa\u002Fpython\u002Fconcurrency\u002Fasyncio.md","Asyncio",{"type":145,"value":146,"toc":147},"minimark",[],{"title":148,"searchDepth":29,"depth":29,"links":149},"",[],"hard","md",{},true,"\u002Fpython\u002Fconcurrency\u002Fasyncio",[156,160,165,169,173,177],{"id":157,"difficulty":150,"q":158,"a":159},"what-is-asyncio","What is asyncio and the event loop?","**asyncio** is Python's framework for **single-threaded concurrency** using an\n**event loop**. The **event loop** is a scheduler that runs many **coroutines**\ncooperatively: when one coroutine **awaits** something (typically I\u002FO), it\n**yields control** back to the loop, which runs another ready coroutine while the\nfirst waits. No thread is blocked sitting idle.\n\n```python\nimport asyncio\n\nasync def main():\n    print(\"hello\")\n    await asyncio.sleep(1)    # yields to the loop instead of blocking\n    print(\"world\")\n\nasyncio.run(main())           # creates the loop, runs main, then closes it\n```\n\nCrucially this is **concurrency, not parallelism** — one thread, one core,\ninterleaving tasks at `await` points. Rule of thumb: asyncio shines when you\nhave **many tasks that spend most of their time waiting** on I\u002FO.\n",{"id":161,"difficulty":162,"q":163,"a":164},"async-def-await","medium","What do async def and await do?","**`async def`** defines a **coroutine function** — calling it doesn't run the\nbody, it returns a **coroutine object** that must be awaited or scheduled.\n**`await`** **suspends** the current coroutine until the awaited **awaitable**\n(another coroutine, a Task, or a Future) completes, handing control back to the\nevent loop in the meantime.\n\n```python\nimport asyncio\n\nasync def fetch():\n    await asyncio.sleep(1)    # suspension point — loop runs others here\n    return \"data\"\n\nasync def main():\n    coro = fetch()            # nothing has run yet\n    result = await coro       # now it runs; main suspends until it finishes\n    print(result)\n\nasyncio.run(main())\n```\n\nYou can only `await` **inside** an `async def`. Forgetting to await a coroutine\nis a common bug — it never runs and you get a \"coroutine was never awaited\"\nwarning. Think of `await` as \"pause me here and let others run until this is\nready.\"\n",{"id":166,"difficulty":150,"q":167,"a":168},"coroutines-vs-threads","How do coroutines differ from threads?","**Threads** use **preemptive** multitasking — the OS can switch threads at\n**any** point, so shared state needs locks and context switches are relatively\nexpensive. **Coroutines** use **cooperative** multitasking on **one thread** —\nswitches happen **only at explicit `await` points**, so the code between awaits\nis effectively atomic and switching is cheap.\n\n```python\nimport asyncio\n\nasync def task(name):\n    print(f\"{name} start\")\n    await asyncio.sleep(1)        # the ONLY place this can yield\n    print(f\"{name} done\")\n\nasync def main():\n    await asyncio.gather(task(\"a\"), task(\"b\"))   # thousands are feasible\n\nasyncio.run(main())\n```\n\nBecause there's no OS thread per task, you can run **tens of thousands** of\ncoroutines cheaply, and most data races disappear. The catch: a coroutine that\nnever awaits **monopolizes** the loop. Threads tolerate blocking code;\ncoroutines do not.\n",{"id":170,"difficulty":162,"q":171,"a":172},"asyncio-gather","How do you run coroutines concurrently with asyncio.gather?","Awaiting coroutines one by one runs them **sequentially**. To run them\n**concurrently**, schedule them together with **`asyncio.gather`** (or wrap each\nin a **Task**), which lets the loop interleave their `await` points.\n\n```python\nimport asyncio\n\nasync def fetch(n):\n    await asyncio.sleep(1)\n    return n * 2\n\nasync def main():\n    # all three overlap -> ~1 second total, not 3\n    results = await asyncio.gather(fetch(1), fetch(2), fetch(3))\n    print(results)        # [2, 4, 6] — order matches the arguments\n\nasyncio.run(main())\n```\n\n`gather` returns results in argument order and, by default, propagates the first\nexception. `asyncio.create_task(coro)` schedules a coroutine to start running\nimmediately so it overlaps with later code. The key idea: concurrency comes from\n**scheduling tasks together**, not from awaiting them in turn.\n",{"id":174,"difficulty":150,"q":175,"a":176},"dont-block-the-loop","What is the \"don't block the event loop\" rule?","Because asyncio runs on **one thread**, any code that **doesn't await** —\nheavy CPU work or **blocking synchronous I\u002FO** like `time.sleep`,\n`requests.get`, or blocking DB drivers — **freezes the entire loop**. Every other\ncoroutine stalls until that call returns.\n\n```python\nimport asyncio, time\n\nasync def bad():\n    time.sleep(5)              # BLOCKS the whole loop for 5s\n\nasync def good():\n    await asyncio.sleep(5)     # yields; other tasks keep running\n\n# offload unavoidable blocking\u002FCPU work to a thread or process pool:\nasync def offloaded():\n    loop = asyncio.get_running_loop()\n    await loop.run_in_executor(None, time.sleep, 5)   # runs in a thread\n```\n\nFixes: use **async-native libraries** (`aiohttp`, `asyncpg`), and push\nCPU-bound or unavoidably-blocking calls into `run_in_executor` (a thread pool, or\na process pool for CPU work). Rule of thumb: inside `async` code, never call\nsomething that blocks without awaiting it.\n",{"id":178,"difficulty":162,"q":179,"a":180},"when-async-helps","When does asyncio actually help?","asyncio wins for **high-concurrency I\u002FO-bound** workloads — thousands of\nnetwork calls, web requests, websocket connections, or database queries that\nspend their time **waiting**. While one request waits, the loop services others,\nso a single thread handles huge concurrency cheaply.\n\n```python\nimport asyncio\n\nasync def call_api(i):\n    await asyncio.sleep(0.5)        # stand-in for a network round trip\n    return i\n\nasync def main():\n    # 1000 \"requests\" overlap on one thread in ~0.5s of wall time\n    results = await asyncio.gather(*(call_api(i) for i in range(1000)))\n    print(len(results))             # 1000\n\nasyncio.run(main())\n```\n\nIt does **not** help **CPU-bound** work — that needs multiprocessing for real\nparallelism. And for a handful of blocking calls, plain threads are often\nsimpler. Reach for asyncio when concurrency is high and the bottleneck is\nwaiting on I\u002FO.\n",null,{"description":148},"Python interview questions on asyncio and async\u002Fawait: the event loop, coroutines vs threads, asyncio.gather, the don't-block-the-loop rule, and when async helps.","python\u002Fconcurrency\u002Fasyncio","asyncio & async\u002Fawait","2026-06-18","Nmpm5dZjqwdFh49NpQFVIUo_4pc2dAboUlrFVlULydE",{"id":189,"title":190,"body":191,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":195,"navigation":153,"order":46,"path":196,"questions":197,"related":181,"seo":223,"seoDescription":224,"stem":225,"subtopic":226,"topic":90,"topicSlug":92,"updated":186,"__hash__":227},"qa\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures.md","Concurrent Futures",{"type":145,"value":192,"toc":193},[],{"title":148,"searchDepth":29,"depth":29,"links":194},[],{},"\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures",[198,202,206,210,215,219],{"id":199,"difficulty":162,"q":200,"a":201},"executor-abstraction","What is the Executor abstraction in concurrent.futures?","`concurrent.futures` provides a **high-level**, uniform interface for running\ncallables asynchronously. An **`Executor`** manages a **pool of workers** and\nhands you back **`Future`** objects representing pending results — and the **same\nAPI** works whether the workers are threads or processes, so you can swap one for\nthe other with a one-line change.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef work(x):\n    return x * 2\n\nwith ThreadPoolExecutor(max_workers=4) as ex:   # context manager auto-shuts down\n    future = ex.submit(work, 10)                # schedule the call\n    print(future.result())                      # 20\n\n# swap to processes by changing only the class name:\n# with ProcessPoolExecutor() as ex: ...\n```\n\nThe context manager (`with`) cleanly handles worker shutdown and waits for\npending work on exit. Rule of thumb: prefer `concurrent.futures` over raw\n`threading`\u002F`multiprocessing` when you just want to run a function over a pool\nand collect results.\n",{"id":203,"difficulty":162,"q":204,"a":205},"thread-vs-process-pool","When do you use ThreadPoolExecutor vs ProcessPoolExecutor?","Both share the Executor API but differ in workers. **`ThreadPoolExecutor`** runs\ntasks in **threads** within one process — cheap, shared memory, but bound by the\n**GIL**, so it only helps **I\u002FO-bound** work. **`ProcessPoolExecutor`** runs tasks\nin **separate processes**, each with its own GIL, giving **true parallelism** for\n**CPU-bound** work (at the cost of pickling arguments\u002Fresults).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor\n\n# I\u002FO-bound: many network\u002Fdisk waits -> threads\nwith ThreadPoolExecutor() as ex:\n    ex.map(download, urls)\n\n# CPU-bound: number crunching -> processes\nwith ProcessPoolExecutor() as ex:\n    ex.map(crunch, datasets)\n```\n\nBecause the API is identical, you can prototype with threads and switch to\nprocesses if the GIL becomes the bottleneck. Rule of thumb: I\u002FO-bound ->\n`ThreadPoolExecutor`; CPU-bound -> `ProcessPoolExecutor`.\n",{"id":207,"difficulty":162,"q":208,"a":209},"submit-and-futures","What does submit() return, and what is a Future?","**`submit(fn, *args)`** schedules `fn` to run in the pool and **immediately**\nreturns a **`Future`** — a handle to a result that may not exist yet. The Future\nlets you check status (`done()`, `running()`), **block for the result**\n(`result()`), retrieve an exception (`exception()`), `cancel()`, or attach a\ncallback (`add_done_callback`).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef slow_double(x):\n    return x * 2\n\nwith ThreadPoolExecutor() as ex:\n    fut = ex.submit(slow_double, 21)   # returns instantly\n    print(fut.done())                  # False — probably still running\n    print(fut.result())                # 42 — blocks until ready\n```\n\n`result(timeout=...)` blocks (up to an optional timeout) until the value is\nready. A Future decouples **starting** the work from **collecting** it, which is\nwhat makes overlapping multiple calls possible.\n",{"id":211,"difficulty":212,"q":213,"a":214},"executor-map","easy","How does executor.map work and how does it differ from submit?","**`executor.map(fn, iterable)`** is the convenient bulk form: it applies `fn` to\nevery item concurrently and returns an **iterator of results in input order** —\nanalogous to the built-in `map`, but parallel. **`submit`** is lower level,\ngiving you a Future per call for fine-grained control.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef fetch(url):\n    return len(url)\n\nurls = [\"a\", \"bb\", \"ccc\"]\nwith ThreadPoolExecutor() as ex:\n    for result in ex.map(fetch, urls):   # results stream back in order\n        print(result)                    # 1, 2, 3\n```\n\n`map` is great when you have a clean iterable and want **ordered** results with\nminimal code. Use `submit` (often with `as_completed`) when you need results\n**as they finish**, per-task error handling, or cancellation. Note `map` raises\nthe first exception when you iterate to that result.\n",{"id":216,"difficulty":162,"q":217,"a":218},"as-completed","What does as_completed do?","**`as_completed(futures)`** yields each Future **as soon as it finishes**,\nregardless of submission order — so you can process results the moment they're\nready instead of waiting for the slowest task to keep its place (as `map`'s\nordered output would).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\ndef work(n):\n    return n * n\n\nwith ThreadPoolExecutor() as ex:\n    futures = [ex.submit(work, i) for i in range(5)]\n    for fut in as_completed(futures):     # whichever finishes first\n        print(fut.result())               # order is non-deterministic\n```\n\nThis is ideal for **responsiveness** — show progress as tasks complete, or\nhandle failures immediately. Use `map` when you want results in input order; use\n`as_completed` when you want them in **completion** order.\n",{"id":220,"difficulty":162,"q":221,"a":222},"exception-propagation","How are exceptions handled with futures?","An exception raised inside a worker is **captured and stored** in its Future,\nnot raised at submit time. It **re-raises when you call `future.result()`** (or\niterate to that item in `map`). You can also inspect it without raising via\n**`future.exception()`**.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef boom(x):\n    raise ValueError(f\"bad: {x}\")\n\nwith ThreadPoolExecutor() as ex:\n    fut = ex.submit(boom, 1)\n    err = fut.exception()        # returns the ValueError, doesn't raise\n    print(err)                   # bad: 1\n    fut.result()                 # NOW it re-raises ValueError\n```\n\nThe implication: if you never call `result()` (or check `exception()`), an\nerror can **pass silently**. Always retrieve results — typically in a `try\u002Fexcept`\naround `result()` — so worker failures surface in the main thread.\n",{"description":148},"Python interview questions on concurrent.futures: the Executor abstraction, ThreadPoolExecutor vs ProcessPoolExecutor, submit and Future objects, map, as_completed, and exception handling.","python\u002Fconcurrency\u002Fconcurrent-futures","concurrent.futures","EBXrpKsBJ9HKBfCU5qZ9GBoP7V5poZ4M3DOQAmPFiLQ",{"id":229,"title":230,"body":231,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":235,"navigation":153,"order":13,"path":236,"questions":237,"related":181,"seo":257,"seoDescription":258,"stem":259,"subtopic":260,"topic":90,"topicSlug":92,"updated":186,"__hash__":261},"qa\u002Fpython\u002Fconcurrency\u002Fgil.md","Gil",{"type":145,"value":232,"toc":233},[],{"title":148,"searchDepth":29,"depth":29,"links":234},[],{},"\u002Fpython\u002Fconcurrency\u002Fgil",[238,242,246,250,254],{"id":239,"difficulty":150,"q":240,"a":241},"what-is-the-gil","What is the GIL?","The **Global Interpreter Lock** is a mutex in **CPython** that allows **only one\nthread to execute Python bytecode at a time**. Even on a multi-core machine, a\nmultithreaded pure-Python program runs its bytecode on **one core at a time** —\nthreads take turns holding the lock.\n\nIt exists to make CPython's memory management (especially **reference counting**)\nsimple and fast: without it, every refcount update would need its own lock. The\ninterpreter releases the GIL periodically and **around blocking I\u002FO** so other\nthreads can run.\n\n```python\nimport threading\n# both threads exist, but the GIL serializes their bytecode execution\ndef work():\n    total = 0\n    for _ in range(10_000_000):   # CPU-bound — holds the GIL\n        total += 1\n\nt1 = threading.Thread(target=work)\nt2 = threading.Thread(target=work)\nt1.start(); t2.start(); t1.join(); t2.join()   # ~no speedup vs one thread\n```\n\nWhy it matters: the GIL is a **CPython implementation detail** (not in the language\nspec, and absent in Jython\u002Fthe free-threaded 3.13+ build) that shapes when threads\ndo and don't help.\n",{"id":243,"difficulty":162,"q":244,"a":245},"threading-vs-multiprocessing","What is the difference between threading and multiprocessing?","**Threads** share one process and one memory space, so they're cheap to create and\nshare data directly — but in CPython they're serialized by the **GIL**.\n**Processes** each have their own interpreter and memory, so they run on\n**separate cores in true parallel**, bypassing the GIL — at the cost of higher\nstartup overhead and needing to **serialize (pickle) data** to communicate.\n\n```python\nfrom threading import Thread\nfrom multiprocessing import Process\n\nThread(target=fn)    # shared memory, GIL-bound, light\nProcess(target=fn)   # separate memory, real parallelism, heavier\n```\n\nThreads communicate through shared objects (guarded by locks); processes\ncommunicate through `Queue`, `Pipe`, or shared-memory primitives because they don't\nshare state.\n\nRule of thumb: **threads for I\u002FO-bound** concurrency, **processes for CPU-bound**\nparallelism.\n",{"id":247,"difficulty":150,"q":248,"a":249},"cpu-vs-io-bound","Why don't threads speed up CPU-bound work but help I\u002FO-bound work?","For **CPU-bound** work, threads are constantly executing bytecode, so they're\nalways contending for the **GIL** — only one runs at a time, and you get no\nparallel speedup (often a small slowdown from lock contention and context switches).\n\nFor **I\u002FO-bound** work, a thread that's waiting on the network, disk, or a database\nis **blocked outside the interpreter** — and CPython **releases the GIL during\nblocking I\u002FO**. So other threads run while one waits, giving real concurrency.\n\n```python\nimport requests, threading\n\ndef fetch(url):\n    requests.get(url)     # blocks on the network -> GIL released here\n\n# 10 threads overlap their waiting time -> much faster than sequential\nthreads = [threading.Thread(target=fetch, args=(u,)) for u in urls]\n```\n\nRule of thumb: if your bottleneck is **waiting**, use threads (or asyncio); if it's\n**computing**, use **processes** to get past the GIL.\n",{"id":251,"difficulty":150,"q":252,"a":253},"race-conditions-locks","What is a race condition and how do locks prevent it?","A **race condition** occurs when two threads access shared mutable state and the\nresult depends on **timing**. Even `x += 1` is not atomic — it's read, add, write,\nand a thread can be switched out mid-sequence, so updates get lost.\n\nA **lock** (`threading.Lock`) creates a **critical section**: only the thread\nholding it can proceed, the rest wait, so the read-modify-write happens atomically.\nUsing `with lock:` guarantees the lock is always released, even on exceptions.\n\n```python\nimport threading\ncounter = 0\nlock = threading.Lock()\n\ndef increment():\n    global counter\n    for _ in range(100_000):\n        with lock:           # only one thread in here at a time\n            counter += 1     # now safe from lost updates\n```\n\nBeware over-locking: acquiring multiple locks in different orders can cause\n**deadlock**. Rule of thumb: guard **every** access to shared mutable state, keep\ncritical sections small, and prefer thread-safe `queue.Queue` for handoff.\n",{"id":255,"difficulty":162,"q":204,"a":256},"threadpool-vs-processpool","Both come from `concurrent.futures` and share the same API — `submit` \u002F\n`map` returning `Future` objects — so you can swap them with one line. The\ndifference is the **worker type**: `ThreadPoolExecutor` runs tasks in **threads**\n(shared memory, GIL-bound) and `ProcessPoolExecutor` runs them in **separate\nprocesses** (true parallelism, pickled args).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor\n\n# I\u002FO-bound: many network\u002Ffile waits -> threads\nwith ThreadPoolExecutor(max_workers=20) as ex:\n    results = list(ex.map(download, urls))\n\n# CPU-bound: heavy computation -> processes (one per core)\nwith ProcessPoolExecutor() as ex:\n    results = list(ex.map(crunch_numbers, datasets))\n```\n\nUse **ThreadPoolExecutor for I\u002FO-bound** tasks (downloads, DB calls) where waiting\ndominates, and **ProcessPoolExecutor for CPU-bound** tasks to use all cores —\nremembering its arguments and return values must be **picklable**.\n\nRule of thumb: pick the executor by the bottleneck (waiting vs computing), and let\n`concurrent.futures` handle the pool lifecycle and result collection.\n",{"description":148},"Python interview questions on threading and the GIL — what the GIL is, threads vs multiprocessing, CPU-bound vs I\u002FO-bound work, race conditions and locks, and ThreadPoolExecutor vs ProcessPoolExecutor.","python\u002Fconcurrency\u002Fgil","Threading & the GIL","xnP8BJ-7TKpKC-39NQwkB1eRxm7A9hp5p5zsqHw6SWE",{"id":263,"title":264,"body":265,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":269,"navigation":153,"order":29,"path":270,"questions":271,"related":181,"seo":296,"seoDescription":297,"stem":298,"subtopic":264,"topic":90,"topicSlug":92,"updated":186,"__hash__":299},"qa\u002Fpython\u002Fconcurrency\u002Fmultiprocessing.md","Multiprocessing",{"type":145,"value":266,"toc":267},[],{"title":148,"searchDepth":29,"depth":29,"links":268},[],{},"\u002Fpython\u002Fconcurrency\u002Fmultiprocessing",[272,276,280,284,288,292],{"id":273,"difficulty":150,"q":274,"a":275},"multiprocessing-vs-threading","How does multiprocessing sidestep the GIL, and how is it different from threading?","**Threading** runs multiple threads inside **one process** that share one\ninterpreter — and therefore one **GIL** (Global Interpreter Lock), which lets\nonly one thread execute Python bytecode at a time. **Multiprocessing** spawns\n**separate OS processes**, each with its **own** interpreter and **own GIL**, so\nthey can run Python code in **true parallel** on multiple CPU cores.\n\n```python\nfrom multiprocessing import Process\nimport os\n\ndef work():\n    print(f\"running in pid {os.getpid()}\")  # a distinct process each time\n\nif __name__ == \"__main__\":            # required guard on Windows\u002Fspawn\n    ps = [Process(target=work) for _ in range(4)]\n    for p in ps: p.start()\n    for p in ps: p.join()\n```\n\nThe tradeoff: processes don't share memory, so passing data costs\n**serialization (pickling) and IPC overhead**, and each process has higher\nstartup cost than a thread. Rule of thumb: reach for multiprocessing when you\nneed real CPU parallelism, not just concurrency.\n",{"id":277,"difficulty":162,"q":278,"a":279},"process-vs-pool","What is the difference between Process and Pool?","A **`Process`** represents a **single** child process you start and join\nmanually — good when you have a fixed, small number of distinct tasks. A\n**`Pool`** manages a **reusable group of worker processes** and hands out work\nto them, which is far more convenient for **many homogeneous tasks** over a\ndataset.\n\n```python\nfrom multiprocessing import Pool\n\ndef square(n):\n    return n * n\n\nif __name__ == \"__main__\":\n    with Pool(processes=4) as pool:\n        results = pool.map(square, range(10))   # distributed across 4 workers\n    print(results)   # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n```\n\n`Pool` reuses workers (amortizing startup cost) and offers `map`, `imap`,\n`apply_async`, etc. Use `Process` for a few long-lived distinct jobs; use `Pool`\nwhen you're fanning the same function over many inputs.\n",{"id":281,"difficulty":150,"q":282,"a":283},"ipc-queue-pipe","How do processes communicate (Queue vs Pipe)?","Because processes don't share memory, they communicate through **IPC**\nprimitives. A **`Queue`** is a **multi-producer\u002Fmulti-consumer**, thread- and\nprocess-safe FIFO — the general-purpose choice. A **`Pipe`** is a faster but\nlower-level **two-endpoint** connection, best for communication between exactly\n**two** processes.\n\n```python\nfrom multiprocessing import Process, Queue\n\ndef producer(q):\n    q.put(\"result\")          # values are pickled across the boundary\n\nif __name__ == \"__main__\":\n    q = Queue()\n    p = Process(target=producer, args=(q,))\n    p.start()\n    print(q.get())           # \"result\"\n    p.join()\n```\n\nBoth serialize objects under the hood, so only **picklable** data flows through\nthem. Use `Queue` for fan-in\u002Ffan-out among many workers; use `Pipe` for a tight\none-to-one channel where you want the lower overhead.\n",{"id":285,"difficulty":150,"q":286,"a":287},"pickling-overhead","What is the pickling overhead, and what can't be pickled?","Every argument and return value crossing a process boundary must be\n**serialized with `pickle`**, sent, then **deserialized** on the other side.\nFor large objects this **copying cost can dwarf the parallelism gains**, and\nsome objects simply **can't be pickled**.\n\n```python\nimport pickle\n\npickle.dumps(lambda x: x)   # PicklingError — lambdas aren't picklable\n# Also unpicklable: open file handles, sockets, locks, db connections,\n# local\u002Fnested functions, and generators.\n```\n\nPicklable things include module-level functions and classes, and basic\ncontainers of picklable values. The practical implications: pass **small,\npicklable** payloads, define worker functions at **module top level**, and\navoid shipping huge data structures between processes. Minimizing what crosses\nthe boundary is the key to multiprocessing performance.\n",{"id":289,"difficulty":150,"q":290,"a":291},"shared-state","How do you share state between processes?","Since each process has its own memory, you need explicit shared-state tools.\n**`Value`** and **`Array`** put simple data in **shared memory** (fast, but\nlimited types and you must guard with a lock). A **`Manager`** hosts richer\nshared objects (`dict`, `list`, etc.) via a **server process** — more flexible\nbut slower because access is proxied.\n\n```python\nfrom multiprocessing import Process, Value, Lock\n\ndef inc(counter, lock):\n    for _ in range(1000):\n        with lock:               # protect the shared value\n            counter.value += 1\n\nif __name__ == \"__main__\":\n    counter = Value(\"i\", 0)      # shared int in shared memory\n    lock = Lock()\n    ps = [Process(target=inc, args=(counter, lock)) for _ in range(4)]\n    for p in ps: p.start()\n    for p in ps: p.join()\n    print(counter.value)         # 4000\n```\n\nPrefer **message passing** (Queue\u002FPipe) over shared state when you can — it's\neasier to reason about. Reach for `Value`\u002F`Array` for hot, simple counters and a\n`Manager` only when you genuinely need shared complex objects.\n",{"id":293,"difficulty":162,"q":294,"a":295},"when-multiprocessing","When should you use multiprocessing instead of threads or asyncio?","Use multiprocessing for **CPU-bound** work — number crunching, image\nprocessing, data transforms — where you need to **saturate multiple cores** with\nPython code. Threads and asyncio can't do that because the **GIL** serializes\nbytecode execution; only separate processes get separate GILs.\n\n```python\nfrom multiprocessing import Pool\n\ndef heavy(n):                      # pure-Python CPU work\n    return sum(i * i for i in range(n))\n\nif __name__ == \"__main__\":\n    with Pool() as pool:           # defaults to os.cpu_count() workers\n        print(pool.map(heavy, [10_000_000] * 8))   # runs in parallel\n```\n\nFor **I\u002FO-bound** work (network, disk, DB), threads or asyncio are usually\nbetter — they're cheaper and the GIL is released during I\u002FO anyway. Rule of\nthumb: CPU-bound -> multiprocessing; I\u002FO-bound -> threads\u002Fasyncio.\n",{"description":148},"Python interview questions on the multiprocessing module: how it sidesteps the GIL, Process vs Pool, IPC with Queue\u002FPipe, pickling limits, and shared state.","python\u002Fconcurrency\u002Fmultiprocessing","q6g-NDE__tlhDOdIPbX1WLFlzxD4custMmavX-rPQ3s",{"id":301,"title":302,"body":303,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":307,"navigation":153,"order":55,"path":308,"questions":309,"related":181,"seo":334,"seoDescription":335,"stem":336,"subtopic":337,"topic":28,"topicSlug":30,"updated":186,"__hash__":338},"qa\u002Fpython\u002Fdata-structures\u002Fcollections-module.md","Collections Module",{"type":145,"value":304,"toc":305},[],{"title":148,"searchDepth":29,"depth":29,"links":306},[],{},"\u002Fpython\u002Fdata-structures\u002Fcollections-module",[310,314,318,322,326,330],{"id":311,"difficulty":212,"q":312,"a":313},"counter","What is collections.Counter and what is it good for?","**`Counter`** is a `dict` subclass for **counting hashable items**. You pass it\nany iterable and it tallies occurrences into a `{element: count}` mapping, with\nhandy extras like `most_common()`. Missing keys return **0** instead of raising.\n\n```python\nfrom collections import Counter\nc = Counter(\"mississippi\")\n# Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})\n\nc.most_common(2)     # [('i', 4), ('s', 4)] — top N by count\nc[\"z\"]               # 0 — missing key, no KeyError\nc.update(\"pp\")       # add more counts -> p becomes 4\n\nCounter([1, 1, 2]) + Counter([1, 3])   # Counter({1: 3, 2: 1, 3: 1})\n```\n\nIt replaces the manual `d[x] = d.get(x, 0) + 1` pattern and supports arithmetic\nbetween counters. Reach for `Counter` whenever the task is \"how many of each?\" —\nword frequencies, tallies, or finding the most common elements.\n",{"id":315,"difficulty":212,"q":316,"a":317},"defaultdict","How does collections.defaultdict work, and how is it different from dict.setdefault?","**`defaultdict(factory)`** is a `dict` that **auto-creates a missing key's value**\nby calling the zero-argument `factory` (like `list`, `int`, or `set`) the first\ntime you access it — so you can append or increment without initializing.\n\n```python\nfrom collections import defaultdict\n\ngroups = defaultdict(list)\ngroups[\"fruits\"].append(\"apple\")   # key auto-created as [] then appended\n# {'fruits': ['apple']}\n\ncounts = defaultdict(int)\nfor ch in \"aab\":\n    counts[ch] += 1                # missing keys start at 0\n# {'a': 2, 'b': 1}\n```\n\nThe difference from **`setdefault`**: `setdefault` evaluates its default\n**on every call** even when the key exists (wasteful), whereas `defaultdict`\nonly calls the factory **on a miss**. Use `defaultdict` for grouping and\ncounting loops; use plain `setdefault` for a one-off default insertion.\n",{"id":319,"difficulty":162,"q":320,"a":321},"deque","What is a collections.deque and why use it over a list?","A **`deque`** (\"double-ended queue\") gives **O(1) appends and pops at *both*\nends**, whereas a `list` is O(n) for operations at the front (every element\nshifts). It also supports a **`maxlen`** for a fixed-size sliding window.\n\n```python\nfrom collections import deque\n\nd = deque([1, 2, 3])\nd.appendleft(0)    # O(1) — list.insert(0, x) would be O(n)\nd.append(4)        # O(1)\nd.popleft()        # O(1) — efficient FIFO queue\n\nwindow = deque(maxlen=3)\nfor x in [1, 2, 3, 4]:\n    window.append(x)   # auto-drops from the left\n# deque([2, 3, 4], maxlen=3)\n```\n\nUse a deque for **queues, BFS, and sliding windows**; with `maxlen` it\nauto-discards the oldest item when full (great for \"last N\" buffers). Stick with\na list when you only push\u002Fpop at the end or need fast random indexing.\n",{"id":323,"difficulty":162,"q":324,"a":325},"ordereddict","Is OrderedDict still useful now that regular dicts keep insertion order (3.7+)?","Mostly not for ordering itself — since **Python 3.7** a plain `dict` already\npreserves insertion order, so `OrderedDict` is no longer needed just to remember\norder. But it still has a couple of **distinct features** a regular dict lacks.\n\n```python\nfrom collections import OrderedDict\n\n# 1) order-sensitive equality\nOrderedDict(a=1, b=2) == OrderedDict(b=2, a=1)   # False\ndict(a=1, b=2)        == dict(b=2, a=1)          # True (order ignored)\n\n# 2) move_to_end and a popitem(last=...) toggle\nod = OrderedDict(a=1, b=2, c=3)\nod.move_to_end(\"a\")        # OrderedDict([('b',2),('c',3),('a',1)])\nod.popitem(last=False)     # pop from the FRONT — FIFO\n```\n\nSo use `OrderedDict` when you need **order-sensitive `==`**, **`move_to_end`**,\nor **`popitem(last=False)`** — for example, building an LRU cache. For plain\n\"keep the order I inserted,\" a regular dict is now enough.\n",{"id":327,"difficulty":162,"q":328,"a":329},"chainmap","What is collections.ChainMap?","**`ChainMap`** groups multiple dicts into a **single, layered view** without\ncopying them. Lookups search the underlying mappings **in order** and return the\nfirst match, so earlier maps **shadow** later ones.\n\n```python\nfrom collections import ChainMap\n\ndefaults = {\"color\": \"red\", \"size\": \"M\"}\noverrides = {\"color\": \"blue\"}\nsettings = ChainMap(overrides, defaults)\n\nsettings[\"color\"]    # 'blue' — first map wins\nsettings[\"size\"]     # 'M'    — falls through to defaults\n\nsettings[\"size\"] = \"L\"   # writes go to the FIRST map (overrides) only\n```\n\nIt's perfect for **layered configuration** (CLI args over env vars over\ndefaults) and scope-like lookups, because it stays live — changing a source dict\nshows up immediately. Note that **writes and deletes only affect the first\nmapping**. Use it to merge config layers without flattening them into one dict.\n",{"id":331,"difficulty":212,"q":332,"a":333},"namedtuple-crossref","How does namedtuple fit into the collections module?","**`collections.namedtuple`** is a factory that creates an **immutable tuple\nsubclass with named fields** — lightweight, hashable records that read like\nobjects but behave like tuples (indexing, unpacking, comparison).\n\n```python\nfrom collections import namedtuple\n\nPoint = namedtuple(\"Point\", [\"x\", \"y\"])\np = Point(3, 4)\np.x, p[0]        # 3, 3  — named and positional access\np._asdict()      # {'x': 3, 'y': 4}\np._replace(x=9)  # Point(x=9, y=4) — returns a NEW tuple (immutable)\n```\n\nIt rounds out the module's record-like tools alongside `Counter`,\n`defaultdict`, and `deque`. For richer needs — type hints, defaults, methods —\n`typing.NamedTuple` or a `@dataclass` is the modern choice. Use `namedtuple` for\nsimple, immutable, self-documenting records.\n",{"description":148},"Python interview questions on the collections module — Counter, defaultdict, deque, OrderedDict, ChainMap, and how they relate to namedtuple.","python\u002Fdata-structures\u002Fcollections-module","The collections Module","LZW7rGcpTd9BsXIZIOFd-X_5qK8sKAY3AZmE-BjNbyY",{"id":340,"title":341,"body":342,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":346,"navigation":153,"order":11,"path":347,"questions":348,"related":181,"seo":373,"seoDescription":374,"stem":375,"subtopic":341,"topic":28,"topicSlug":30,"updated":186,"__hash__":376},"qa\u002Fpython\u002Fdata-structures\u002Fdictionaries.md","Dictionaries",{"type":145,"value":343,"toc":344},[],{"title":148,"searchDepth":29,"depth":29,"links":345},[],{},"\u002Fpython\u002Fdata-structures\u002Fdictionaries",[349,353,357,361,365,369],{"id":350,"difficulty":212,"q":351,"a":352},"insertion-ordering","Are Python dictionaries ordered?","Yes. Since **Python 3.7**, dictionaries **preserve insertion order** as a\n**language guarantee** — iterating a dict yields keys in the order they were\nfirst added. (This was an implementation detail in CPython 3.6, then made\nofficial in 3.7.)\n\n```python\nd = {}\nd[\"b\"] = 1\nd[\"a\"] = 2\nd[\"c\"] = 3\nlist(d)            # ['b', 'a', 'c'] — insertion order, not sorted\n\nd[\"b\"] = 9         # updating a value does NOT change order\nlist(d)            # ['b', 'a', 'c']\n```\n\nNote that **updating** an existing key keeps its original position; only the\nfirst insertion fixes the order. Reassigning doesn't move it. Because ordering\nis guaranteed, plain `dict` now covers most cases that once needed\n`OrderedDict`.\n",{"id":354,"difficulty":212,"q":355,"a":356},"get-vs-bracket-setdefault","What is the difference between d[key], d.get(key), and d.setdefault()?","**`d[key]`** raises `KeyError` if the key is missing. **`d.get(key, default)`**\nreturns a default (or `None`) instead of raising — a safe read.\n**`d.setdefault(key, default)`** returns the existing value if present, but if\nmissing it **inserts** the default and returns it.\n\n```python\nd = {\"a\": 1}\nd[\"b\"]               # KeyError\nd.get(\"b\")           # None — no error\nd.get(\"b\", 0)        # 0   — supplied default\n\nd.setdefault(\"a\", 99)  # 1  — already present, unchanged\nd.setdefault(\"c\", []).append(5)  # inserts c=[], then appends -> {'c': [5]}\n```\n\nUse `get` for a safe lookup that **doesn't mutate**, and `setdefault` to\n**read-or-initialize** in one step (handy for grouping). For heavy grouping work,\n`collections.defaultdict` is usually cleaner.\n",{"id":358,"difficulty":162,"q":359,"a":360},"merging-dicts","What are the ways to merge two dictionaries?","The modern way is the **`|` merge operator** (Python 3.9+), which returns a new\ndict; **`|=`** merges in place. Before 3.9, the idiom was **`{**a, **b}`\nunpacking**, and `dict.update()` merges in place.\n\n```python\na = {\"x\": 1, \"y\": 2}\nb = {\"y\": 9, \"z\": 3}\n\na | b           # {'x': 1, 'y': 9, 'z': 3}  — new dict (3.9+)\n{**a, **b}      # {'x': 1, 'y': 9, 'z': 3}  — same, works pre-3.9\n\na.update(b)     # mutates a in place -> {'x': 1, 'y': 9, 'z': 3}\n```\n\nIn every approach the **right-hand dict wins** on key collisions (`y` becomes\n`9`). Use `|` for a clean new dict on modern Python, `{**a, **b}` for\ncompatibility, and `update()`\u002F`|=` when you want to mutate in place.\n",{"id":362,"difficulty":162,"q":363,"a":364},"keys-values-items-views","What do keys(), values(), and items() return, and what is a view object?","They return **view objects** — dynamic, read-only windows onto the dict that\n**reflect changes live** rather than copying the data. They're iterable and\nsupport set-like operations, but they're not lists.\n\n```python\nd = {\"a\": 1, \"b\": 2}\nkeys = d.keys()\nd[\"c\"] = 3\nlist(keys)          # ['a', 'b', 'c'] — view updated automatically!\n\nkeys[0]             # TypeError — a view isn't indexable\nlist(d.keys())      # ['a', 'b', 'c'] — materialize when you need a list\n\nd.keys() & {\"a\"}    # {'a'} — keys views support set operations\n```\n\nBecause a view is **live**, it's memory-cheap but you must `list(...)` it to\nindex or snapshot it. Also avoid mutating the dict's size while iterating a view\n— that raises `RuntimeError`. Use views to iterate efficiently; copy to a list\nwhen you need a stable, indexable sequence.\n",{"id":366,"difficulty":162,"q":367,"a":368},"keys-hashable-lookup","Why must dictionary keys be hashable, and why are lookups O(1)?","A dict is a **hash table**: it computes `hash(key)` to decide which bucket the\nentry lives in, giving **average O(1)** insertion and lookup regardless of size.\nFor this to work, a key's hash must **never change**, so keys must be\n**hashable** — effectively **immutable**.\n\n```python\nd = {}\nd[(1, 2)] = \"ok\"     # tuple is immutable -> hashable\nd[[1, 2]] = \"no\"     # TypeError: unhashable type: 'list'\n\n# O(1): lookup time doesn't grow with the dict's size\n\"x\" in d             # hashes \"x\", checks one bucket — not a full scan\n```\n\nIf a mutable key could change after insertion, its hash would change and the\nentry would land in the wrong bucket — you'd never find it again. That's why\nlists, dicts, and sets can't be keys, but tuples and frozensets can. The\nhash-table design is exactly what makes membership tests near-instant.\n",{"id":370,"difficulty":212,"q":371,"a":372},"dict-comprehension","What is a dict comprehension?","A **dict comprehension** builds a dictionary in one expression using\n`{key: value for item in iterable}`, optionally with a filtering `if`. It's the\nconcise, readable alternative to a `for` loop that calls `d[k] = v`.\n\n```python\nsquares = {n: n * n for n in range(5)}\n# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}\n\nprices = {\"apple\": 3, \"pear\": 0, \"fig\": 7}\nin_stock = {k: v for k, v in prices.items() if v > 0}\n# {'apple': 3, 'fig': 7}\n\ninverted = {v: k for k, v in prices.items()}   # swap keys\u002Fvalues\n```\n\nLike other comprehensions it has its **own scope** (no leaked loop variable) and\nis generally faster than the equivalent loop. Use it to transform, filter, or\ninvert mappings clearly — but keep it readable rather than cramming in logic.\n",{"description":148},"Python interview questions on dict insertion ordering, get vs bracket access, setdefault, merging dicts, view objects, why keys must be hashable, and dict comprehensions.","python\u002Fdata-structures\u002Fdictionaries","LzhuhDxYYShbZNpJ1GNHVfGL3cDVdrMl9q-LhVisbXs",{"id":378,"title":379,"body":380,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":384,"navigation":153,"order":13,"path":385,"questions":386,"related":181,"seo":407,"seoDescription":408,"stem":409,"subtopic":410,"topic":28,"topicSlug":30,"updated":186,"__hash__":411},"qa\u002Fpython\u002Fdata-structures\u002Flists.md","Lists",{"type":145,"value":381,"toc":382},[],{"title":148,"searchDepth":29,"depth":29,"links":383},[],{},"\u002Fpython\u002Fdata-structures\u002Flists",[387,391,395,399,403],{"id":388,"difficulty":162,"q":389,"a":390},"list-vs-array","What is the difference between a Python list and an array?","A **`list`** is Python's built-in, **dynamically-sized, heterogeneous**\ncontainer — it can hold objects of any type because each slot stores a\n**reference** to a boxed Python object. The standard-library **`array.array`**\n(and NumPy's `ndarray`) is **homogeneous and typed**, storing raw C values\ncontiguously, which is far more **memory-efficient** for large numeric data.\n\n```python\nfrom array import array\nnums = [1, 2, \"three\"]      # list — mixed types allowed\ntyped = array(\"i\", [1, 2, 3])  # array — all C ints, compact\ntyped.append(\"x\")           # TypeError — type-checked\n\nimport sys\nsys.getsizeof([1, 2, 3])    # bigger: stores pointers\nsys.getsizeof(array(\"i\", [1, 2, 3]))  # smaller: packed ints\n```\n\nRule of thumb: reach for a plain **`list`** for general-purpose, mixed-type\ncollections; use **`array`** or **NumPy** when you have large amounts of\nuniform numeric data and care about memory or vectorized speed.\n",{"id":392,"difficulty":162,"q":393,"a":394},"slicing-semantics","How does list slicing work, including negative steps?","A slice is `lst[start:stop:step]`. `start` is **inclusive**, `stop` is\n**exclusive**, and any part can be **omitted** (defaults: start of list, end of\nlist, step 1). A **negative step** walks the list **backwards**, and when it's\nnegative the defaults for `start`\u002F`stop` flip to the **end** and the\n**beginning**.\n\n```python\na = [0, 1, 2, 3, 4, 5]\na[1:4]      # [1, 2, 3]   — stop is exclusive\na[:3]       # [0, 1, 2]   — omitted start = 0\na[::2]      # [0, 2, 4]   — every other element\na[::-1]     # [5, 4, 3, 2, 1, 0]  — reversed copy\na[4:1:-1]   # [4, 3, 2]   — backwards, stop exclusive\n```\n\nSlicing **never raises** for out-of-range indices (`a[10:20]` is just `[]`),\nunlike single-element indexing. Remember `a[::-1]` is the idiomatic way to get a\n**reversed shallow copy**.\n",{"id":396,"difficulty":162,"q":397,"a":398},"append-extend-insert","What is the difference between append, extend, and insert?","**`append(x)`** adds a single item to the end in **amortized O(1)**.\n**`extend(iterable)`** adds **each element** of an iterable to the end (also\namortized O(1) per element). **`insert(i, x)`** places an item at index `i`,\nwhich is **O(n)** because every following element must shift right.\n\n```python\na = [1, 2, 3]\na.append([4, 5])   # [1, 2, 3, [4, 5]]  — the LIST is one element\na = [1, 2, 3]\na.extend([4, 5])   # [1, 2, 3, 4, 5]    — unpacks the iterable\na.insert(0, 99)    # [99, 1, 2, 3, 4, 5] — O(n) shift\n```\n\nThe classic trap: `append([4, 5])` nests the whole list as one item, whereas\n`extend([4, 5])` merges its elements. Avoid `insert(0, …)` in a loop — it's\nO(n) each time; use `collections.deque` for fast front insertion.\n",{"id":400,"difficulty":162,"q":401,"a":402},"comprehension-vs-loop","Are list comprehensions faster than equivalent for loops?","Usually **yes**. A comprehension runs its iteration in **optimized C-level\nbytecode** and avoids the repeated `list.append` **attribute lookup and method\ncall** that a manual loop incurs, so it's typically **20–40% faster** as well as\nmore concise. It also creates the loop variable in its **own scope**, so it\ndoesn't leak.\n\n```python\n# manual loop — explicit append each iteration\nsquares = []\nfor n in range(1000):\n    squares.append(n * n)\n\n# comprehension — faster and clearer\nsquares = [n * n for n in range(1000)]\n```\n\nUse a comprehension when you're **building a list** from an expression. But if\nthe body has side effects or grows complex (nested conditionals, multiple\nstatements), a readable `for` loop is the better choice — clarity beats a small\nspeed win.\n",{"id":404,"difficulty":212,"q":405,"a":406},"sort-vs-sorted","What is the difference between list.sort() and sorted()?","**`list.sort()`** sorts a list **in place** and returns **`None`** — it mutates\nthe original and only works on lists. **`sorted(iterable)`** returns a **new\nsorted list** and leaves the input untouched, accepting **any iterable** (tuples,\nsets, generators, dict keys).\n\n```python\nnums = [3, 1, 2]\nresult = nums.sort()        # result is None! nums is now [1, 2, 3]\n\noriginal = (3, 1, 2)\nnew = sorted(original)      # new = [1, 2, 3], original unchanged\nsorted([\"bb\", \"a\"], key=len, reverse=True)  # ['bb', 'a']\n```\n\nBoth are **stable** (equal elements keep their order) and run in **O(n log n)**\n(Timsort). Watch the gotcha: `x = mylist.sort()` leaves `x` as `None`. Use\n`sort()` to save memory when you don't need the original; use `sorted()` when you\nmust preserve it or are sorting a non-list iterable.\n",{"description":148},"Python interview questions on lists vs arrays, slicing semantics and negative steps, append vs extend vs insert, comprehensions, and sort vs sorted.","python\u002Fdata-structures\u002Flists","Lists & Slicing","UKHUEr1MtYNcjg937qLbMk2iR2kGGF2n3zhJyYXTljY",{"id":413,"title":414,"body":415,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":419,"navigation":153,"order":46,"path":420,"questions":421,"related":181,"seo":446,"seoDescription":447,"stem":448,"subtopic":449,"topic":28,"topicSlug":30,"updated":186,"__hash__":450},"qa\u002Fpython\u002Fdata-structures\u002Fsets.md","Sets",{"type":145,"value":416,"toc":417},[],{"title":148,"searchDepth":29,"depth":29,"links":418},[],{},"\u002Fpython\u002Fdata-structures\u002Fsets",[422,426,430,434,438,442],{"id":423,"difficulty":212,"q":424,"a":425},"set-operations","What are the main set operations in Python?","Sets support the classic mathematical operations, each with an **operator** and\nan equivalent **method**: **union** (`|`), **intersection** (`&`),\n**difference** (`-`), and **symmetric difference** (`^`, items in exactly one\nset).\n\n```python\na = {1, 2, 3}\nb = {2, 3, 4}\n\na | b    # {1, 2, 3, 4}      union — in either\na & b    # {2, 3}            intersection — in both\na - b    # {1}               difference — in a, not b\na ^ b    # {1, 4}            symmetric difference — in one, not both\n\na.union(b)              # method form, accepts any iterable\na.intersection([2, 3])  # b can be a list here\n```\n\nThe **operator** forms require both operands to be sets, while the **method**\nforms accept any iterable. Use them for fast \"what's common \u002F unique \u002F missing\"\nquestions instead of nested loops.\n",{"id":427,"difficulty":162,"q":428,"a":429},"set-vs-list-membership","Why is membership testing faster in a set than a list?","A `set` is backed by a **hash table**, so `x in s` is **average O(1)** — it\nhashes `x` and checks one bucket. A `list` has no such index, so `x in lst` is\n**O(n)** — it scans elements one by one until it finds a match or reaches the\nend.\n\n```python\nbig_list = list(range(1_000_000))\nbig_set  = set(big_list)\n\n999_999 in big_list   # O(n) — scans up to a million items\n999_999 in big_set    # O(1) — single hash lookup\n```\n\nFor repeated membership checks over a large collection, converting to a set\nfirst is a huge win. The trade-off is that sets are **unordered** and elements\nmust be **hashable**. Rule of thumb: if you mostly ask \"is X in here?\", use a\nset, not a list.\n",{"id":431,"difficulty":212,"q":432,"a":433},"dedup-with-set","How do you remove duplicates from a list using a set?","Wrapping a list in **`set()`** removes duplicates instantly, since a set can't\nhold repeated values. The catch is that a set is **unordered**, so this doesn't\npreserve the original order.\n\n```python\nnums = [3, 1, 2, 3, 1]\nunique = list(set(nums))      # e.g. [1, 2, 3] — order NOT guaranteed\n\n# order-preserving dedup (dict keys are unique AND ordered since 3.7):\nordered = list(dict.fromkeys(nums))   # [3, 1, 2]\n```\n\nUse `set()` when you only care about the **distinct values**; when order\nmatters, use **`dict.fromkeys()`**, which keeps first-seen order thanks to\nguaranteed dict ordering. Both require the elements to be hashable.\n",{"id":435,"difficulty":212,"q":436,"a":437},"add-discard-remove","What is the difference between add, discard, and remove on a set?","**`add(x)`** inserts an element (a no-op if it's already present). To delete,\n**`remove(x)`** raises `KeyError` if the element is missing, while\n**`discard(x)`** removes it **silently** if present and does nothing otherwise.\n\n```python\ns = {1, 2, 3}\ns.add(2)          # already there — no change\ns.add(4)          # {1, 2, 3, 4}\n\ns.remove(4)       # {1, 2, 3}\ns.remove(99)      # KeyError — not in set\ns.discard(99)     # no error, no change\ns.pop()           # removes and returns an arbitrary element\n```\n\nChoose **`discard`** when \"remove if it's there\" is the intent (no need to guard\nwith a membership check), and **`remove`** when a missing element is genuinely an\nerror you want surfaced. `pop()` removes an arbitrary element since sets are\nunordered.\n",{"id":439,"difficulty":162,"q":440,"a":441},"frozenset","What is a frozenset and when do you need one?","A **`frozenset`** is the **immutable** version of `set` — it supports all the\nread operations (union, intersection, membership) but has **no `add`\u002F`remove`**.\nBecause it's immutable, it's **hashable**, so it can be a **dict key** or an\n**element of another set**.\n\n```python\nfs = frozenset([1, 2, 3])\nfs.add(4)              # AttributeError — immutable\nfs & {2, 3, 4}         # frozenset({2, 3}) — set ops still work\n\n{fs: \"a group\"}        # usable as a dict key\n{frozenset({1, 2}), frozenset({3, 4})}   # a set OF sets\n```\n\nThe classic use is a **set of sets**: regular sets are unhashable, so the inner\nones must be frozensets. Also use a frozenset for a constant collection you want\nto guarantee can't be mutated. Reach for it whenever you need a set-like value\nthat must be hashable.\n",{"id":443,"difficulty":212,"q":444,"a":445},"set-comprehension","What is a set comprehension?","A **set comprehension** builds a set in one expression with\n`{expr for item in iterable}`, automatically **deduplicating** the results. It's\nthe set sibling of list and dict comprehensions, with curly braces and no\n`key:value` pair.\n\n```python\nsquares = {n * n for n in range(-3, 4)}\n# {0, 1, 4, 9} — note 9 appears once even though -3 and 3 both map to it\n\nwords = [\"Hi\", \"hi\", \"HEY\"]\nlowered = {w.lower() for w in words}   # {'hi', 'hey'}\n```\n\nIt's ideal when you want **unique transformed values** in a single readable step.\nWatch out that `{}` alone is an **empty dict**, not an empty set — use `set()`\nfor an empty set. Use a set comprehension when both transformation and\ndeduplication are the goal.\n",{"description":148},"Python interview questions on set operations, O(1) membership vs lists, deduplication, add\u002Fdiscard\u002Fremove, frozensets, and set comprehensions.","python\u002Fdata-structures\u002Fsets","Sets & Frozensets","cTcBrrIA7tgVyW4p3hQf_V6QjLhZcf7eBJuL-k_iHL0",{"id":452,"title":453,"body":454,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":458,"navigation":153,"order":29,"path":459,"questions":460,"related":181,"seo":481,"seoDescription":482,"stem":483,"subtopic":484,"topic":28,"topicSlug":30,"updated":186,"__hash__":485},"qa\u002Fpython\u002Fdata-structures\u002Ftuples.md","Tuples",{"type":145,"value":455,"toc":456},[],{"title":148,"searchDepth":29,"depth":29,"links":457},[],{},"\u002Fpython\u002Fdata-structures\u002Ftuples",[461,465,469,473,477],{"id":462,"difficulty":212,"q":463,"a":464},"tuple-vs-list","What is the difference between a tuple and a list, and when should you use each?","A **`list`** is **mutable** and variable-length — use it for a **homogeneous,\nchanging** collection you add to, remove from, or reorder. A **`tuple`** is\n**immutable** and fixed — use it for a **heterogeneous, fixed record** whose\nshape won't change, like a coordinate or a database row.\n\n```python\nscores = [90, 85, 70]   # changing collection -> list\nscores.append(60)       # fine\n\npoint = (3, 4)          # fixed record -> tuple\npoint[0] = 9            # TypeError — tuples are immutable\n```\n\nTuples are also slightly **faster and more memory-efficient**, and because\nthey're **hashable** they can serve as dict keys or set members (a list can't).\nRule of thumb: reach for a tuple when the data is a fixed bundle that shouldn't\nchange, and a list when you need to mutate the collection.\n",{"id":466,"difficulty":212,"q":467,"a":468},"packing-unpacking","What are tuple packing and unpacking, and how do you write a single-element tuple?","**Packing** collects several values into one tuple — the parentheses are often\noptional. **Unpacking** spreads a tuple's items into separate names in one\nassignment, which is how you return and receive multiple values cleanly.\n\n```python\nt = 1, 2, 3          # packing (parentheses optional)\na, b, c = t          # unpacking -> a=1, b=2, c=3\nfirst, *rest = t     # extended unpacking -> first=1, rest=[2, 3]\n\none = (5)            # NOT a tuple — just int 5 in parentheses\none = (5,)           # a 1-element tuple — the trailing comma makes it\n```\n\nThe key gotcha is the **single-element tuple**: it's the **trailing comma**, not\nthe parentheses, that creates a tuple — `(5)` is just the integer `5`, while\n`(5,)` is a one-tuple. When in doubt, the comma is what defines a tuple.\n",{"id":470,"difficulty":162,"q":471,"a":472},"shallow-immutability","Is a tuple's immutability deep? Can a tuple contain mutable objects?","A tuple's **immutability is shallow**. You can't reassign or resize its slots,\nbut each slot just holds a **reference** — and if that reference points to a\nmutable object (like a list), that object can still be changed in place.\n\n```python\nt = (1, [2, 3])\nt[1].append(4)     # allowed — mutating the list inside the tuple\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 that contains a list is **not\nhashable**, because hashability requires every element to be immutable too —\nso `hash((1, [2]))` raises `TypeError`. Bottom line: the tuple's structure is\nfrozen, but the objects it points to may not be.\n",{"id":474,"difficulty":162,"q":475,"a":476},"namedtuple","What is a namedtuple and how does collections.namedtuple compare to typing.NamedTuple?","A **namedtuple** is an **immutable** tuple subclass with **named fields** —\ngiving you readable, hashable, lightweight records that still behave like\ntuples (index access, unpacking, comparison). There are two ways to define one.\n\n```python\nfrom collections import namedtuple\nPoint = namedtuple(\"Point\", [\"x\", \"y\"])\np = Point(3, 4)\np.x, p[0]        # 3, 3  — named AND index access\np.x = 9          # AttributeError — immutable\n\nfrom typing import NamedTuple\nclass Point(NamedTuple):     # class syntax with type hints + defaults\n    x: int\n    y: int = 0\n```\n\nThe `collections` form is concise; the `typing.NamedTuple` class form adds\n**type annotations, defaults, and the ability to add methods**. Use a namedtuple\nfor small fixed records when you want clarity over a bare tuple; reach for a\n`@dataclass` when you need mutability or richer behavior.\n",{"id":478,"difficulty":162,"q":479,"a":480},"tuple-dict-key","Why can a tuple be used as a dictionary key when a list cannot?","Dict keys and set members must be **hashable**, which in practice means\n**immutable** with a stable hash for the object's lifetime. A tuple of immutable\nvalues is hashable, so it works as a key; a list is mutable and **unhashable**,\nso it doesn't.\n\n```python\ngrid = {}\ngrid[(0, 0)] = \"origin\"   # tuple key — works\ngrid[(1, 2)] = \"point\"\n\ngrid[[3, 4]] = \"x\"        # TypeError: unhashable type: 'list'\n```\n\nThis makes tuples ideal for **composite \u002F multi-part keys** — like `(row, col)`\ncoordinates or `(lat, lon)` pairs. The one caveat: a tuple is only hashable if\n**all its contents are** too, so `(1, [2])` still fails. Use an immutable tuple\nwhenever you need a multi-value key.\n",{"description":148},"Python interview questions on tuples vs lists, packing and unpacking, single-element tuples, shallow immutability, namedtuples, and tuples as dict keys.","python\u002Fdata-structures\u002Ftuples","Tuples & Named Tuples","k8gJCQHd3O516LEW4hNwzbc-dMsxspsIWpQ8XWKiCig",{"id":487,"title":488,"body":489,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":493,"navigation":153,"order":13,"path":494,"questions":495,"related":181,"seo":516,"seoDescription":517,"stem":518,"subtopic":519,"topic":63,"topicSlug":65,"updated":186,"__hash__":520},"qa\u002Fpython\u002Fexceptions\u002Fcontext-managers.md","Context Managers",{"type":145,"value":490,"toc":491},[],{"title":148,"searchDepth":29,"depth":29,"links":492},[],{},"\u002Fpython\u002Fexceptions\u002Fcontext-managers",[496,500,504,508,512],{"id":497,"difficulty":212,"q":498,"a":499},"what-is-context-manager","What is a context manager and what does the with statement do?","A **context manager** is an object that defines **setup and teardown** logic to run\naround a block of code. The **`with` statement** uses it to guarantee that the\nteardown happens — even if the block raises an exception or returns early — so you\ndon't have to write manual `try\u002Ffinally`.\n\n```python\nwith open(\"data.txt\") as f:   # __enter__ runs, returns the file\n    data = f.read()\n# __exit__ runs here automatically — the file is closed\n```\n\nThe classic use is resource management: files, network sockets, database\nconnections, and locks. Rule of thumb: any \"acquire then must-release\" pattern is\na candidate for a `with` block.\n",{"id":501,"difficulty":162,"q":502,"a":503},"enter-exit","How do __enter__ and __exit__ work?","To make a class a context manager you implement two dunder methods. **`__enter__`**\nruns at the start of the `with` block and its return value is bound to the `as`\nvariable. **`__exit__`** runs when the block ends — always — receiving the\nexception type, value, and traceback (all `None` if the block succeeded).\n\n```python\nclass Timer:\n    def __enter__(self):\n        import time; self.start = time.time()\n        return self                      # bound to 'as t'\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        import time; print(time.time() - self.start)\n        return False                     # don't suppress exceptions\n\nwith Timer() as t:\n    do_work()\n```\n\n`__exit__` always runs, which is what makes cleanup reliable. Rule of thumb:\nacquire the resource in `__enter__`, release it in `__exit__`, and `return self`\nif callers need the object.\n",{"id":505,"difficulty":162,"q":506,"a":507},"contextlib-decorator","How does contextlib.contextmanager simplify writing one?","The **`@contextlib.contextmanager`** decorator lets you write a context manager as a\n**generator** instead of a class. Code **before `yield`** is the setup (`__enter__`),\nthe **yielded value** becomes the `as` target, and code **after `yield`** is the\nteardown (`__exit__`).\n\n```python\nfrom contextlib import contextmanager\n\n@contextmanager\ndef opened(path):\n    f = open(path)          # setup\n    try:\n        yield f             # value bound to 'as'\n    finally:\n        f.close()           # teardown — runs even on error\n\nwith opened(\"data.txt\") as f:\n    print(f.read())\n```\n\nThe `try\u002Ffinally` around the `yield` is essential — without it the teardown is\nskipped when the block raises. Rule of thumb: reach for the decorator for simple,\none-off managers; write a class when you need state across multiple methods.\n",{"id":509,"difficulty":150,"q":510,"a":511},"exceptions-in-exit","How does a context manager handle exceptions in __exit__?","When the `with` block raises, Python passes the exception details into `__exit__`.\nThe crucial part is the **return value**: returning a **truthy** value tells Python\nto **suppress** the exception, while returning `False`\u002F`None` lets it **propagate**\nnormally.\n\n```python\nclass Suppress:\n    def __enter__(self): return self\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        if exc_type is ValueError:\n            print(\"swallowed:\", exc_val)\n            return True          # suppress ValueError\n        return False             # re-raise anything else\n\nwith Suppress():\n    raise ValueError(\"oops\")     # swallowed, program continues\n```\n\n`contextlib.suppress(ValueError)` is the ready-made version of this pattern. Rule\nof thumb: only suppress exceptions you genuinely intend to ignore — accidentally\nreturning a truthy value hides bugs.\n",{"id":513,"difficulty":212,"q":514,"a":515},"multiple-context-managers","How do you use multiple context managers and what are real uses?","You can manage several resources in one `with` by separating them with commas (or\nusing parenthesized form in 3.10+). They are **entered left to right** and\n**exited in reverse order**, so cleanup unwinds correctly.\n\n```python\nwith open(\"in.txt\") as src, open(\"out.txt\", \"w\") as dst:\n    dst.write(src.read())\n# dst closed first, then src\n\nimport threading\nlock = threading.Lock()\nwith lock:                 # acquire on enter, release on exit\n    shared_counter += 1\n```\n\nCommon real uses: **files** (auto-close), **locks** (auto-release even on error),\n**database transactions** (commit\u002Frollback), and temporarily **changing state**\nlike `decimal.localcontext`. Rule of thumb: if you ever write `try\u002Ffinally` to\nrelease something, a context manager expresses it more clearly.\n",{"description":148},"Python interview questions on context managers and the with statement, __enter__\u002F__exit__, contextlib.contextmanager, exception handling in __exit__, and multiple context managers.","python\u002Fexceptions\u002Fcontext-managers","Context Managers & with","_wYx3YKUE7hQadgYFrkMXwIkQXcyoJFa0Pm0YBK9cu8",{"id":522,"title":523,"body":524,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":528,"navigation":153,"order":11,"path":529,"questions":530,"related":181,"seo":555,"seoDescription":556,"stem":557,"subtopic":558,"topic":63,"topicSlug":65,"updated":186,"__hash__":559},"qa\u002Fpython\u002Fexceptions\u002Fcustom-exceptions.md","Custom Exceptions",{"type":145,"value":525,"toc":526},[],{"title":148,"searchDepth":29,"depth":29,"links":527},[],{},"\u002Fpython\u002Fexceptions\u002Fcustom-exceptions",[531,535,539,543,547,551],{"id":532,"difficulty":162,"q":533,"a":534},"hierarchy","What is the Python exception hierarchy and why shouldn't you catch BaseException?","All exceptions inherit from **`BaseException`** at the root. Directly under it sit a\nfew special ones — **`SystemExit`**, **`KeyboardInterrupt`**, and\n**`GeneratorExit`** — that are **not** meant to be caught by normal error handling.\nEverything you'd normally catch (and every built-in error like `ValueError`)\ndescends from **`Exception`**, which is itself a child of `BaseException`.\n\n```python\n# BaseException\n#  ├── SystemExit\n#  ├── KeyboardInterrupt\n#  └── Exception          \u003C- catch THIS, not BaseException\n#       ├── ValueError\n#       ├── KeyError\n#       └── ...\n```\n\nCatching `BaseException` (or a bare `except:`) traps `KeyboardInterrupt` and\n`SystemExit`, so Ctrl-C and clean shutdowns stop working. **Catch `Exception`**\n(or narrower) and let the system-level ones propagate.\n",{"id":536,"difficulty":212,"q":537,"a":538},"defining-custom","How do you define a custom exception class?","Subclass **`Exception`** (almost never `BaseException`). The simplest custom\nexception needs no body at all — `pass` is enough, since it inherits message\nhandling from `Exception`.\n\n```python\nclass ConfigError(Exception):\n    \"\"\"Raised when configuration is invalid.\"\"\"\n\nraise ConfigError(\"missing 'port' key\")\n\ntry:\n    ...\nexcept ConfigError as e:\n    print(e)            # missing 'port' key\n```\n\nGive the class a clear, specific name ending in `Error`, and a docstring describing\nwhen it's raised. Even an empty subclass is valuable because it lets callers catch\n*your* error specifically instead of a generic `Exception`.\n",{"id":540,"difficulty":162,"q":541,"a":542},"when-to-subclass","When and why should you create custom exceptions?","Create a custom exception when callers need to **distinguish your error** from\nothers and handle it differently. A common pattern is a single **base exception for\nyour library\u002Fapp**, with specific subclasses beneath it — so users can catch the\nbase to handle \"anything from this library\" or a subclass for fine-grained control.\n\n```python\nclass PaymentError(Exception):\n    \"\"\"Base for all payment problems.\"\"\"\n\nclass CardDeclined(PaymentError): pass\nclass InsufficientFunds(PaymentError): pass\n\ntry:\n    charge(card)\nexcept InsufficientFunds:\n    retry_later()\nexcept PaymentError:          # catches any other payment error\n    alert_support()\n```\n\nDon't invent a custom exception when a built-in already fits — bad input is a\n`ValueError`, a missing key is a `KeyError`. Add your own only when it carries\nmeaning the built-ins can't.\n",{"id":544,"difficulty":162,"q":545,"a":546},"passing-args","How do you pass arguments and messages to an exception?","Arguments passed to an exception are stored in its **`.args`** tuple, and the first\none becomes the string returned by `str(exception)`. To attach **structured data**,\nadd attributes in a custom `__init__` (and call `super().__init__()` so the message\nstill works).\n\n```python\nraise ValueError(\"bad value\", 42)\n# e.args == (\"bad value\", 42)\n\nclass ApiError(Exception):\n    def __init__(self, message, status_code):\n        super().__init__(message)   # sets the message \u002F .args\n        self.status_code = status_code\n\ntry:\n    raise ApiError(\"not found\", 404)\nexcept ApiError as e:\n    print(e, e.status_code)         # not found 404\n```\n\nUse a plain message for simple cases; add attributes when handlers need to inspect\ndetails (like an HTTP status or the offending value) rather than parse the text.\n",{"id":548,"difficulty":162,"q":549,"a":550},"base-catches-subclasses","Why does catching a base exception class also catch its subclasses?","`except` matches via **`isinstance`**, so a handler for a base class catches the\nbase **and every subclass**. That's why `except Exception` catches almost\neverything, and why ordering matters: put **specific subclasses before** their base,\nor the base will intercept them first.\n\n```python\nclass AppError(Exception): pass\nclass NotFound(AppError): pass\n\ntry:\n    raise NotFound(\"user\")\nexcept AppError:            # matches — NotFound IS-A AppError\n    print(\"caught by base\")\n\ntry:\n    raise NotFound(\"user\")\nexcept AppError:            # this runs first...\n    print(\"base\")\nexcept NotFound:            # ...so this is unreachable!\n    print(\"specific\")\n```\n\nOrder `except` clauses **most-specific first**. The same rule is why\n`except Exception` should come last among your handlers.\n",{"id":552,"difficulty":212,"q":553,"a":554},"common-builtins","What are some common built-in exceptions and when are they raised?","Knowing the standard ones lets you catch precisely and raise the *right* error.\n**`ValueError`** — right type, wrong value (`int(\"abc\")`). **`TypeError`** — wrong\ntype entirely (`\"x\" + 1`). **`KeyError`** — missing dict key. **`IndexError`** —\nlist index out of range. **`AttributeError`** — missing attribute. **`KeyError`**\nand **`IndexError`** both subclass **`LookupError`**.\n\n```python\nint(\"abc\")          # ValueError\n\"x\" + 1             # TypeError\n{\"a\": 1}[\"b\"]       # KeyError\n[1, 2][5]           # IndexError\nNone.foo            # AttributeError\n```\n\nRaise the built-in that best describes the problem instead of a generic\n`Exception` — `ValueError` for bad arguments, `TypeError` for wrong types — so\ncallers can handle them idiomatically.\n",{"description":148},"Python interview questions on the exception hierarchy, BaseException vs Exception, defining custom exception classes, passing messages, and how catching a base class catches its subclasses.","python\u002Fexceptions\u002Fcustom-exceptions","Custom Exceptions & the Hierarchy","_NsIl_DBSx_bgYPk7Jw_vU-Q07dm94XjSeGNaH_9WLM",{"id":561,"title":562,"body":563,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":567,"navigation":153,"order":29,"path":568,"questions":569,"related":181,"seo":590,"seoDescription":591,"stem":592,"subtopic":593,"topic":63,"topicSlug":65,"updated":186,"__hash__":594},"qa\u002Fpython\u002Fexceptions\u002Ftry-except.md","Try Except",{"type":145,"value":564,"toc":565},[],{"title":148,"searchDepth":29,"depth":29,"links":566},[],{},"\u002Fpython\u002Fexceptions\u002Ftry-except",[570,574,578,582,586],{"id":571,"difficulty":162,"q":572,"a":573},"four-clauses","What do the try, except, else, and finally clauses do and when does each run?","A full statement has four parts. **`try`** holds the code that might fail.\n**`except`** runs only if a matching exception was raised in `try`. **`else`**\nruns only if `try` finished **without** raising — it's for the \"success path\"\ncode you don't want wrapped in the `try`. **`finally`** runs **always**, whether\nthere was an exception or not, and even if you `return`\u002F`break` — it's for cleanup.\n\n```python\ntry:\n    conn = open_db()          # might raise\nexcept DBError as e:\n    log(e)                    # runs only on failure\nelse:\n    conn.commit()             # runs only on success\nfinally:\n    conn.close()              # always runs — cleanup\n```\n\nPutting the success-only code in `else` (instead of at the bottom of `try`) keeps\nthe `try` block narrow, so `except` only catches errors from the risky line.\n",{"id":575,"difficulty":162,"q":576,"a":577},"catch-multiple","How do you catch multiple exception types, and what does `as e` give you?","Group related types in a **tuple** after `except`, or use separate `except`\nclauses when each needs different handling. The `as e` binds the **exception\ninstance**, letting you inspect its message, args, or attributes.\n\n```python\ntry:\n    value = int(raw)\nexcept (ValueError, TypeError) as e:   # tuple -> one handler for both\n    print(f\"bad input: {e}\")           # e is the exception object\nexcept KeyError:                       # separate handler, different logic\n    print(\"missing key\")\n```\n\nClauses are checked **top to bottom**, and the **first match wins** — so order\nspecific exceptions before their base classes. Use the tuple form when several\ntypes share handling; use separate clauses when they don't.\n",{"id":579,"difficulty":162,"q":580,"a":581},"bare-except","Why is a bare `except:` considered bad practice?","A bare `except:` (or `except BaseException:`) catches **everything** — including\n`KeyboardInterrupt` and `SystemExit`, which are how the user Ctrl-Cs or the\nprogram exits. It also **swallows bugs** like `NameError` or `TypeError` that you'd\nrather see crash loudly, making problems invisible.\n\n```python\ntry:\n    do_work()\nexcept:                 # too broad — even Ctrl-C is caught\n    pass                # silent failure — hides real bugs\n\ntry:\n    do_work()\nexcept Exception as e:  # catches errors, not Ctrl-C \u002F SystemExit\n    log.exception(e)    # at least record what happened\n```\n\nCatch the **narrowest** exception you can actually handle. If you must catch\nbroadly, use `except Exception` (not bare), and always log rather than silently\n`pass`.\n",{"id":583,"difficulty":150,"q":584,"a":585},"finally-return","What happens when `finally` contains a `return`?","`finally` always runs, even when `try` or `except` already has a `return`. If\n`finally` itself **returns** (or raises), it **overrides** the pending return or\nexception — the value from `try`\u002F`except` is silently discarded. This is a classic\ngotcha.\n\n```python\ndef f():\n    try:\n        return 1        # value queued...\n    finally:\n        return 2        # ...but finally's return wins\nf()                     # 2, not 1\n\ndef g():\n    try:\n        raise ValueError\n    finally:\n        return \"swallowed\"   # finally return suppresses the exception!\ng()                     # \"swallowed\" — exception vanishes\n```\n\nAvoid `return`\u002F`break` inside `finally`: it masks both return values and\nexceptions. Keep `finally` for cleanup only.\n",{"id":587,"difficulty":150,"q":588,"a":589},"reraise-chaining","How do you re-raise an exception and what is exception chaining?","A bare **`raise`** (no argument) inside an `except` block **re-raises the current\nexception**, preserving its original traceback — useful for logging then letting it\npropagate. To translate one error into another while keeping the cause, use\n**`raise NewError from original`**, which sets `__cause__` and shows both in the\ntraceback.\n\n```python\ntry:\n    parse(config)\nexcept KeyError as e:\n    log.error(\"config invalid\")\n    raise                       # re-raise same exception, original traceback\n\ntry:\n    value = data[\"port\"]\nexcept KeyError as e:\n    raise ConfigError(\"missing port\") from e   # explicit chaining\n```\n\nWhen an exception is raised **inside** an `except` block without `from`, Python\nstill links it implicitly via `__context__` (\"During handling of the above...\"). Use\n`from e` to make the cause explicit, or `from None` to suppress the chain.\n",{"description":148},"Python interview questions on try\u002Fexcept\u002Felse\u002Ffinally, catching multiple exception types, why bare except is bad, finally with return, and exception chaining with raise from.","python\u002Fexceptions\u002Ftry-except","try \u002F except \u002F else \u002F finally","zypRLfxfYjZySj7ZLjRlUt8LzLvDa0sVDs3sO4N7lzk",{"id":596,"title":597,"body":598,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":602,"navigation":153,"order":13,"path":603,"questions":604,"related":181,"seo":625,"seoDescription":626,"stem":627,"subtopic":628,"topic":72,"topicSlug":74,"updated":186,"__hash__":629},"qa\u002Fpython\u002Ffunctional\u002Ffunctools.md","Functools",{"type":145,"value":599,"toc":600},[],{"title":148,"searchDepth":29,"depth":29,"links":601},[],{},"\u002Fpython\u002Ffunctional\u002Ffunctools",[605,609,613,617,621],{"id":606,"difficulty":162,"q":607,"a":608},"lru-cache","What does functools.lru_cache do?","**`functools.lru_cache`** is a decorator that **memoizes** a function — it stores\nresults keyed by the arguments and returns the cached value on repeat calls,\navoiding recomputation. `maxsize` caps how many results are kept, evicting the\n**least-recently-used** entries; `lru_cache(maxsize=None)` (or `functools.cache`\nin 3.9+) caches without limit.\n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=None)\ndef fib(n):\n    return n if n \u003C 2 else fib(n - 1) + fib(n - 2)\n\nfib(50)                 # fast — each n computed once\nfib.cache_info()        # hits, misses, maxsize, currsize\nfib.cache_clear()       # reset the cache\n```\n\nArguments must be **hashable** (they're used as dict keys), and the function should\nbe **pure** — caching an impure function returns stale results. Rule of thumb: use\nit for expensive, deterministic calls with repeated inputs.\n",{"id":610,"difficulty":162,"q":611,"a":612},"partial","What is functools.partial used for?","**`functools.partial`** creates a new callable with some arguments of an existing\nfunction **pre-filled**. It's a clean way to specialize a general function without\nwriting a wrapper or a `lambda`.\n\n```python\nfrom functools import partial\n\ndef power(base, exp):\n    return base ** exp\n\nsquare = partial(power, exp=2)   # exp fixed to 2\ncube   = partial(power, exp=3)\n\nsquare(5)    # 25\ncube(2)      # 8\n```\n\nPartials are handy for callbacks, event handlers, and configuring functions passed\nto `map`\u002F`sorted`\u002FGUI bindings. Rule of thumb: reach for `partial` when you keep\ncalling the same function with one or two fixed arguments.\n",{"id":614,"difficulty":162,"q":615,"a":616},"wraps","Why do you use functools.wraps when writing a decorator?","A decorator replaces the original function with a wrapper, which **loses the\noriginal's metadata** — its `__name__`, `__doc__`, and signature now point at the\nwrapper. **`functools.wraps`** copies that metadata from the wrapped function onto\nthe wrapper, so introspection, debugging, and documentation still work.\n\n```python\nfrom functools import wraps\n\ndef log(fn):\n    @wraps(fn)                 # copy fn's metadata to wrapper\n    def wrapper(*args, **kwargs):\n        print(\"calling\", fn.__name__)\n        return fn(*args, **kwargs)\n    return wrapper\n\n@log\ndef greet(): \"say hi\"\ngreet.__name__   # 'greet'  (without @wraps it'd be 'wrapper')\n```\n\nWithout `@wraps`, tools like `help()`, tracebacks, and doc generators show the\nwrapper instead of the real function. Rule of thumb: always add `@wraps(fn)` to the\ninner function of any decorator.\n",{"id":618,"difficulty":162,"q":619,"a":620},"reduce","What does functools.reduce do?","**`functools.reduce`** repeatedly applies a two-argument function across an iterable,\n**folding** it down to a single accumulated value. It carries a running result,\ncombining it with each element in turn; an optional **initializer** seeds the\naccumulator (and makes it safe on empty iterables).\n\n```python\nfrom functools import reduce\n\nreduce(lambda acc, x: acc + x, [1, 2, 3, 4])      # 10\nreduce(lambda acc, x: acc * x, [1, 2, 3, 4], 1)   # 24, seeded with 1\n```\n\nFor common folds Python already has built-ins (`sum`, `max`, `min`, `any`, `all`)\nthat are clearer and faster — `reduce` shines for custom accumulation logic. Rule\nof thumb: prefer a built-in or an explicit loop unless the fold is genuinely\nbespoke, since `reduce` can hurt readability.\n",{"id":622,"difficulty":150,"q":623,"a":624},"cached-property-singledispatch","What are cached_property and singledispatch?","**`functools.cached_property`** turns a method into a property whose result is\n**computed once and stored on the instance**, so later accesses are cheap. The\ncached value lives in the instance `__dict__` and is recomputed only if you delete\nit. **`functools.singledispatch`** creates a **generic function** that dispatches to\ndifferent implementations based on the **type of the first argument** — function\noverloading by type.\n\n```python\nfrom functools import cached_property, singledispatch\n\nclass Dataset:\n    @cached_property\n    def stats(self):           # expensive; runs once per instance\n        return expensive_scan(self.data)\n\n@singledispatch\ndef describe(x): return f\"value: {x}\"\n@describe.register\ndef _(x: list): return f\"list of {len(x)}\"\n@describe.register\ndef _(x: int): return f\"int {x}\"\n\ndescribe([1, 2])   # 'list of 2'\ndescribe(7)        # 'int 7'\n```\n\n`cached_property` trades memory for speed on costly, stable computations;\n`singledispatch` keeps type-specific behaviour in separate, registerable functions\ninstead of a big `if\u002Fisinstance` chain. Rule of thumb: cache derived values that\ndon't change, and dispatch when behaviour varies cleanly by argument type.\n",{"description":148},"Python interview questions on functools — lru_cache and cache, partial, wraps, reduce, cached_property, and singledispatch.","python\u002Ffunctional\u002Ffunctools","functools","O3Gm_dZN9ODH7b85aBNZuE1Zy-RWDP_xQIzayQMjmcM",{"id":631,"title":632,"body":633,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":637,"navigation":153,"order":11,"path":638,"questions":639,"related":181,"seo":668,"seoDescription":669,"stem":670,"subtopic":671,"topic":72,"topicSlug":74,"updated":186,"__hash__":672},"qa\u002Fpython\u002Ffunctional\u002Fitertools.md","Itertools",{"type":145,"value":634,"toc":635},[],{"title":148,"searchDepth":29,"depth":29,"links":636},[],{},"\u002Fpython\u002Ffunctional\u002Fitertools",[640,644,648,652,656,660,664],{"id":641,"difficulty":162,"q":642,"a":643},"infinite-iterators","What do count, cycle, and repeat do?","These are the three **infinite iterators**. **`count(start, step)`** yields an\nendless arithmetic sequence. **`cycle(iterable)`** repeats an iterable's items\nforever. **`repeat(value, times)`** yields the same value endlessly, or `times`\ntimes if given.\n\n```python\nfrom itertools import count, cycle, repeat, islice\n\nlist(islice(count(10, 2), 3))      # [10, 12, 14]\nlist(islice(cycle(\"AB\"), 5))       # ['A', 'B', 'A', 'B', 'A']\nlist(repeat(7, 3))                 # [7, 7, 7]\n```\n\nBecause `count` and `cycle` never stop, you must bound them — with `islice`, `zip`,\nor a `break` — or your loop runs forever. They're ideal for generating ids,\nround-robin assignment, or padding.\n",{"id":645,"difficulty":212,"q":646,"a":647},"chain","What does itertools.chain do?","`chain(*iterables)` lazily **concatenates** multiple iterables into one stream,\nwithout building an intermediate combined list. `chain.from_iterable(iter_of_iters)`\ndoes the same when the iterables come from a single iterable (e.g. a list of lists).\n\n```python\nfrom itertools import chain\n\nlist(chain([1, 2], [3, 4], [5]))        # [1, 2, 3, 4, 5]\n\nrows = [[1, 2], [3, 4], [5, 6]]\nlist(chain.from_iterable(rows))         # [1, 2, 3, 4, 5, 6] — flatten one level\n```\n\nIt's the memory-friendly way to iterate over several sequences as if they were one,\nand the idiomatic one-level flatten.\n",{"id":649,"difficulty":162,"q":650,"a":651},"islice","What is islice and how is it different from regular slicing?","`islice(iterable, stop)` or `islice(iterable, start, stop, step)` slices any\n**iterator lazily** — including infinite ones and generators that don't support\n`[ ]` indexing. Unlike list slicing, it **can't use negative indices** (it can't\nlook backward in a stream) and it **consumes** the underlying iterator.\n\n```python\nfrom itertools import islice, count\n\nlist(islice(count(), 2, 7))      # [2, 3, 4, 5, 6] — works on an infinite source\ngen = (x * x for x in range(10))\nlist(islice(gen, 3))             # [0, 1, 4] — slice a generator\n```\n\nUse `islice` to take a window from a stream without materializing it. For a concrete\nlist where you want negative indices, ordinary `seq[a:b]` is fine.\n",{"id":653,"difficulty":162,"q":654,"a":655},"combinatorics","What do combinations, permutations, and product produce?","These generate **combinatorial** results lazily. **`permutations(it, r)`** —\nordered arrangements (order matters). **`combinations(it, r)`** — unordered\nselections (order doesn't, no repeats). **`product(*its)`** — the Cartesian product\n(nested loops), with `repeat=n` for self-products.\n\n```python\nfrom itertools import permutations, combinations, product\n\nlist(permutations([1, 2, 3], 2))   # (1,2)(1,3)(2,1)(2,3)(3,1)(3,2)\nlist(combinations([1, 2, 3], 2))   # (1,2)(1,3)(2,3)\nlist(product([0, 1], repeat=2))    # (0,0)(0,1)(1,0)(1,1)\n```\n\nCounts grow fast (factorial \u002F exponential), so keep `r` and inputs small or consume\nlazily. `product(a, b)` replaces a nested `for` over two sequences.\n",{"id":657,"difficulty":150,"q":658,"a":659},"groupby","Why does itertools.groupby require sorted input?","`groupby` groups **only consecutive** items that share a key — it does **not** sort\nfirst. So the same key appearing in non-adjacent positions creates **multiple\ngroups**. To get one group per key, **sort by the same key function first**.\n\n```python\nfrom itertools import groupby\n\ndata = [\"apple\", \"avocado\", \"banana\", \"apricot\"]\n# WRONG — not sorted: 'a' group splits because 'banana' is between\nfor k, g in groupby(data, key=lambda s: s[0]):\n    print(k, list(g))      # a [...] , b [...] , a [apricot]\n\ndata.sort(key=lambda s: s[0])      # sort by the SAME key\nfor k, g in groupby(data, key=lambda s: s[0]):\n    print(k, list(g))      # a [...], b [...]  — correct\n```\n\nAlso note each group is a **lazy sub-iterator** that's invalidated when you advance\nto the next group — materialize it with `list()` if you need it later. Always sort\nby the grouping key before `groupby`.\n",{"id":661,"difficulty":162,"q":662,"a":663},"accumulate","What does itertools.accumulate do?","`accumulate(iterable, func=operator.add)` yields **running totals** — each output is\nthe function applied cumulatively, so by default you get a running sum. Pass a\ndifferent binary `func` for running max, product, etc.\n\n```python\nfrom itertools import accumulate\nimport operator\n\nlist(accumulate([1, 2, 3, 4]))                 # [1, 3, 6, 10] — running sum\nlist(accumulate([1, 2, 3, 4], operator.mul))   # [1, 2, 6, 24] — running product\nlist(accumulate([3, 1, 4, 1, 5], max))         # [3, 3, 4, 4, 5] — running max\n```\n\nUnlike `functools.reduce`, which returns only the **final** value, `accumulate`\nyields **every intermediate** result lazily. Use it for prefix sums and similar\nscans.\n",{"id":665,"difficulty":162,"q":666,"a":667},"laziness-memory","What is the main benefit of itertools being lazy?","Every `itertools` function returns a **lazy iterator** that computes items **on\ndemand**, so it never holds the whole sequence in memory. This lets you process\n**huge or infinite** streams in **constant memory**, and chain operations into a\npipeline that only does the work actually consumed.\n\n```python\nfrom itertools import count, islice\n\n# find the first 5 squares over 1000 — from an infinite source\nsquares = (n * n for n in count(1))\nbig = (s for s in squares if s > 1000)\nprint(list(islice(big, 5)))    # computed lazily, nothing materialized\n\nsum(islice(count(1), 1_000_000))   # no million-element list built\n```\n\nThe trade-off is that iterators are **single-pass** and not indexable. Reach for\n`itertools` when streaming or composing transformations over large data;\nmaterialize to a list only when you need random access or multiple passes.\n",{"description":148},"Python interview questions on itertools — count\u002Fcycle\u002Frepeat, chain, islice, combinations\u002Fpermutations\u002Fproduct, groupby's sorted-input rule, accumulate, and the memory benefit of lazy iterators.","python\u002Ffunctional\u002Fitertools","itertools","1Gxse2j1ry-O6nBlZJIbeCn668VKgt8dM8TEa8_-mMU",{"id":674,"title":675,"body":676,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":680,"navigation":153,"order":29,"path":681,"questions":682,"related":181,"seo":706,"seoDescription":707,"stem":708,"subtopic":709,"topic":72,"topicSlug":74,"updated":186,"__hash__":710},"qa\u002Fpython\u002Ffunctional\u002Fmap-filter-reduce.md","Map Filter Reduce",{"type":145,"value":677,"toc":678},[],{"title":148,"searchDepth":29,"depth":29,"links":679},[],{},"\u002Fpython\u002Ffunctional\u002Fmap-filter-reduce",[683,687,691,695,698,702],{"id":684,"difficulty":212,"q":685,"a":686},"map-basics","What does map do and is it lazy?","`map(func, iterable)` applies `func` to each item, returning a **lazy iterator** in\nPython 3 — nothing is computed until you iterate it (or wrap it in `list()`). This\nsaves memory on large inputs because results are produced one at a time.\n\n```python\nnums = [1, 2, 3]\nresult = map(lambda x: x * 2, nums)\nprint(result)          # \u003Cmap object ...> — not evaluated yet\nprint(list(result))    # [2, 4, 6] — now it runs\n```\n\nBecause it's lazy, `map` never builds the full result in memory unless you ask for\nit. In Python 2 `map` returned a list — a common gotcha when porting code.\n",{"id":688,"difficulty":162,"q":689,"a":690},"map-multiple-iterables","How does map work with multiple iterables?","Pass several iterables and `map` calls `func` with **one item from each, in\nparallel** — `func` must accept that many arguments. It **stops at the shortest**\niterable, so mismatched lengths simply truncate.\n\n```python\na = [1, 2, 3]\nb = [10, 20, 30]\nlist(map(lambda x, y: x + y, a, b))   # [11, 22, 33]\n\nlist(map(pow, [2, 3, 4], [10, 11]))   # [1024, 177147] — stops at 2 items\n```\n\nThis is handy for element-wise combining without an explicit `zip`. If you need the\n**longest** length (padding the gaps), use `itertools.zip_longest` instead.\n",{"id":692,"difficulty":212,"q":693,"a":694},"filter","What does filter do?","`filter(predicate, iterable)` keeps only the items for which `predicate` returns\ntruthy — also a **lazy iterator** in Python 3. As a special case, passing `None` as\nthe predicate keeps the items that are **truthy themselves**.\n\n```python\nnums = [0, 1, 2, 0, 3]\nlist(filter(lambda x: x > 1, nums))   # [2, 3]\nlist(filter(None, nums))              # [1, 2, 3] — drops falsy values\n```\n\nUse it to drop elements by a condition. For complex conditions, a comprehension\nwith `if` is usually more readable than `filter(lambda ...)`.\n",{"id":618,"difficulty":162,"q":696,"a":697},"What is functools.reduce and why isn't it a built-in anymore?","`functools.reduce(func, iterable, initial)` **folds** an iterable into a single\nvalue by repeatedly applying a two-argument function, carrying an accumulator. In\nPython 3 it was **moved out of the builtins into `functools`** — Guido considered it\nless readable than an explicit loop, so it was demoted to discourage casual use.\n\n```python\nfrom functools import reduce\n\nreduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0)   # 10\n# step by step: 0+1=1, 1+2=3, 3+3=6, 6+4=10\n```\n\nFor common reductions, prefer the dedicated built-ins — `sum`, `max`, `min`,\n`math.prod`, `any`, `all` — which are clearer and faster. Reach for `reduce` only\nwhen no built-in expresses the fold.\n",{"id":699,"difficulty":162,"q":700,"a":701},"vs-comprehensions","When should you use map\u002Ffilter versus a comprehension?","A **list\u002Fgenerator comprehension** is the more Pythonic choice when you'd otherwise\nwrite a `lambda`, because it's more readable and avoids the function-call overhead\nper item. `map`\u002F`filter` shine when you can pass an **existing named function**\n(no lambda) and want a lazy iterator with minimal syntax.\n\n```python\nnums = [1, 2, 3, 4]\n\n[x * 2 for x in nums if x % 2]     # comprehension — clearest with a condition\nlist(map(str, nums))               # map with a built-in — clean, no lambda\nlist(map(lambda x: x * 2, nums))   # lambda makes map less readable\n```\n\nRule of thumb: if it needs a `lambda`, use a comprehension; if you're mapping an\nalready-named function, `map` reads fine. For laziness on huge data, a **generator\nexpression** `(... for ...)` gives both.\n",{"id":703,"difficulty":162,"q":704,"a":705},"consume-once","Why can a map or filter object only be consumed once?","`map`\u002F`filter` (and generators) are **one-shot iterators**: iterating them advances\nan internal position that is never reset. Once exhausted, they yield nothing on a\nsecond pass — a frequent source of \"my second loop is empty\" bugs.\n\n```python\ndoubled = map(lambda x: x * 2, [1, 2, 3])\nlist(doubled)    # [2, 4, 6]\nlist(doubled)    # [] — already consumed!\n\ndoubled = list(map(lambda x: x * 2, [1, 2, 3]))  # materialize once\nsum(doubled); max(doubled)   # reuse freely\n```\n\nIf you need to iterate more than once, **convert to a list** (or other concrete\ncollection) up front. Use the lazy iterator directly only when a single pass is\nenough.\n",{"description":148},"Python interview questions on map laziness and multiple iterables, filter, functools.reduce, map\u002Ffilter vs comprehensions, and consuming lazy iterators only once.","python\u002Ffunctional\u002Fmap-filter-reduce","map, filter & reduce","y6sFK7mPcsPaQc2XKJdXy2VVJ6uRfXj-LWrXBILxPQc",{"id":712,"title":713,"body":714,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":718,"navigation":153,"order":29,"path":719,"questions":720,"related":181,"seo":745,"seoDescription":746,"stem":747,"subtopic":748,"topic":45,"topicSlug":47,"updated":186,"__hash__":749},"qa\u002Fpython\u002Ffunctions\u002Farguments.md","Arguments",{"type":145,"value":715,"toc":716},[],{"title":148,"searchDepth":29,"depth":29,"links":717},[],{},"\u002Fpython\u002Ffunctions\u002Farguments",[721,725,729,733,737,741],{"id":722,"difficulty":162,"q":723,"a":724},"args-kwargs","What do *args and **kwargs do?","In a function signature, **`*args`** collects extra **positional** arguments\ninto a tuple, and **`**kwargs`** collects extra **keyword** arguments into a\ndict. They let a function accept a variable number of arguments. The names\nare convention — only the `*`\u002F`**` matter.\n\n```python\ndef log(level, *args, **kwargs):\n    print(level, args, kwargs)\n\nlog(\"INFO\", 1, 2, user=\"ada\", id=7)\n# INFO (1, 2) {'user': 'ada', 'id': 7}\n```\n\n`args` is always a `tuple` and `kwargs` always a `dict`. They're essential\nfor writing wrappers\u002Fdecorators that forward arbitrary arguments through to\nanother callable.\n",{"id":726,"difficulty":212,"q":727,"a":728},"positional-vs-keyword","What is the difference between positional and keyword arguments?","**Positional arguments** are matched to parameters by their **order**.\n**Keyword arguments** are matched by **name** (`param=value`), so order\ndoesn't matter among them. At the call site you can mix the two, but every\npositional argument must come **before** any keyword argument.\n\n```python\ndef greet(name, greeting): ...\n\ngreet(\"Ada\", \"Hello\")              # both positional\ngreet(name=\"Ada\", greeting=\"Hi\")   # both keyword (order free)\ngreet(\"Ada\", greeting=\"Hi\")        # mix: positional first\ngreet(name=\"Ada\", \"Hi\")            # SyntaxError — kw before positional\n```\n\nKeyword arguments make calls self-documenting and let you skip over earlier\nparameters that have defaults. Use them for clarity on boolean flags and\nlong argument lists.\n",{"id":730,"difficulty":212,"q":731,"a":732},"default-args","How do default argument values work?","A parameter with `name=value` is **optional** — if the caller omits it, the\ndefault is used. Defaults are evaluated **once**, when the `def` runs, so\nusing a **mutable** default (`[]`, `{}`) is a classic trap: the same object\npersists across calls.\n\n```python\ndef connect(host, port=5432, timeout=30):\n    ...\nconnect(\"db\")                 # uses port=5432, timeout=30\nconnect(\"db\", timeout=5)      # override one by keyword\n\ndef bad(item, bucket=[]):     # DON'T — shared list\n    bucket.append(item); return bucket\n```\n\nThe safe pattern for a mutable default is `bucket=None` plus\n`if bucket is None: bucket = []` inside the body. Parameters with defaults\nmust come after those without.\n",{"id":734,"difficulty":162,"q":735,"a":736},"keyword-only","What are keyword-only arguments?","Any parameter listed **after a bare `*`** (or after `*args`) is\n**keyword-only** — it can never be passed positionally and must be named at\nthe call site. This forces clearer calls and prevents accidental\npositional mistakes.\n\n```python\ndef make_request(url, *, timeout=30, verify=True):\n    ...\n\nmake_request(\"http:\u002F\u002Fx\", timeout=5)     # OK\nmake_request(\"http:\u002F\u002Fx\", 5)             # TypeError — timeout is kw-only\n```\n\nKeyword-only parameters are great for optional flags whose meaning isn't\nobvious from position (especially booleans). The lone `*` is just a\nseparator; it doesn't collect anything.\n",{"id":738,"difficulty":150,"q":739,"a":740},"positional-only","What are positional-only parameters?","Parameters listed **before a `\u002F`** in the signature are **positional-only**\n(Python 3.8+) — they cannot be passed by keyword. This is useful for APIs\nwhere the parameter name is an implementation detail you don't want callers\nto depend on.\n\n```python\ndef divide(a, b, \u002F):\n    return a \u002F b\n\ndivide(10, 2)          # OK\ndivide(a=10, b=2)      # TypeError — a, b are positional-only\n```\n\nIt also frees those names for use in `**kwargs`. Many built-ins (like\n`len`, `pow`) are positional-only. Combined with `*`, a signature can have\npositional-only, normal, and keyword-only sections.\n",{"id":742,"difficulty":162,"q":743,"a":744},"param-ordering","What is the correct order of parameters in a signature?","A full signature follows a fixed order:\n**positional-only `\u002F`, then normal, then `*args`, then keyword-only, then\n`**kwargs`**. Within each group, parameters without defaults precede those\nwith defaults.\n\n```python\ndef f(pos_only, \u002F, normal, *args, kw_only, **kwargs):\n    ...\n\n# call-site unpacking mirrors this:\ndef g(a, b, c): ...\nnums = [1, 2, 3]\ng(*nums)                 # spread list into positionals\ng(**{\"a\": 1, \"b\": 2, \"c\": 3})   # spread dict into keywords\n```\n\nGetting the order wrong is a `SyntaxError`. The `*`\u002F`\u002F` markers partition\nthe signature; remember the sequence \"positional-only → normal → varargs →\nkeyword-only → varkwargs.\"\n",{"description":148},"Python interview questions on args and kwargs, positional vs keyword arguments, defaults, keyword-only and positional-only parameters, unpacking at the call site, and parameter ordering rules.","python\u002Ffunctions\u002Farguments","Function Arguments","T4lWvDZqp8gjS6SokbQyTXGimya-uvBG8xPkeyfFJ4k",{"id":751,"title":752,"body":753,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":757,"navigation":153,"order":11,"path":758,"questions":759,"related":181,"seo":780,"seoDescription":781,"stem":782,"subtopic":783,"topic":45,"topicSlug":47,"updated":186,"__hash__":784},"qa\u002Fpython\u002Ffunctions\u002Fclosures.md","Closures",{"type":145,"value":754,"toc":755},[],{"title":148,"searchDepth":29,"depth":29,"links":756},[],{},"\u002Fpython\u002Ffunctions\u002Fclosures",[760,764,768,772,776],{"id":761,"difficulty":150,"q":762,"a":763},"what-is-a-closure","What is a closure and what are free variables?","A **closure** is a nested function that **remembers variables from its\nenclosing scope** even after that outer function has returned. The\nremembered names are called **free variables** — they're neither local\nparameters nor globals. Python stores them on the function's `__closure__`\nattribute.\n\n```python\ndef multiplier(factor):\n    def multiply(n):\n        return n * factor      # 'factor' is a free variable\n    return multiply\n\ndouble = multiplier(2)\ndouble(5)                      # 10\ndouble.__closure__[0].cell_contents   # 2 — captured value\n```\n\nThe inner function keeps the binding alive via a **cell object**, which is\nwhy `multiplier` can return and `double` still works. Closures are how\nPython functions carry private state without a class.\n",{"id":765,"difficulty":162,"q":766,"a":767},"nonlocal","What does the nonlocal keyword do?","By default, assigning to a name inside a function creates a **new local**.\n**`nonlocal`** tells Python that an assignment should instead **rebind a\nvariable in the nearest enclosing function scope** — letting a closure\nmutate, not just read, the captured variable.\n\n```python\ndef counter():\n    count = 0\n    def increment():\n        nonlocal count        # rebind outer 'count'\n        count += 1\n        return count\n    return increment\n\nc = counter()\nc(); c()                       # 1, then 2\n```\n\nWithout `nonlocal`, `count += 1` would raise `UnboundLocalError` (it reads\nthen assigns a local). Use `nonlocal` for enclosing-function scope and\n`global` for module scope.\n",{"id":769,"difficulty":150,"q":770,"a":771},"late-binding","Why do closures in a loop all capture the same value?","Closures capture **variables, not values** — this is **late binding**. A\nfunction created in a loop looks up the loop variable when it's *called*, not\nwhen it's defined, so every closure sees the variable's **final** value.\n\n```python\nfuncs = [lambda: i for i in range(3)]\n[f() for f in funcs]           # [2, 2, 2]  — all see final i\n\n# fix: bind the current value via a default argument\nfuncs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]           # [0, 1, 2]\n```\n\nThe default-argument trick works because **defaults are evaluated at\ndefinition time**, snapshotting `i` per iteration. A factory function that\ntakes `i` as a parameter achieves the same. This is a favorite interview\ngotcha.\n",{"id":773,"difficulty":150,"q":774,"a":775},"closure-cell","How can you inspect a closure's captured variables?","A function with free variables has a non-`None` **`__closure__`** — a tuple\nof **cell** objects, each holding one captured binding accessible via\n`cell_contents`. The matching names are listed in\n`__code__.co_freevars`. Functions with no closure have `__closure__ is\nNone`.\n\n```python\ndef make(x, y):\n    def inner():\n        return x + y\n    return inner\n\nf = make(3, 4)\nf.__code__.co_freevars               # ('x', 'y')\n[c.cell_contents for c in f.__closure__]   # [3, 4]\n```\n\nThis is mostly useful for debugging or teaching how closures actually\nstore state. The cells are shared live, so `nonlocal` rebinds are visible\nthrough `cell_contents`.\n",{"id":777,"difficulty":162,"q":778,"a":779},"closures-vs-classes","When should you use a closure instead of a class?","Both bundle **behavior with state**. A **closure** is lighter and ideal\nwhen you need a *single* method and a little hidden state. A **class** wins\nwhen you need multiple methods, inheritance, or explicit, inspectable\nstate. Common closure uses include **factories, decorators, and callbacks**.\n\n```python\n# closure: tiny stateful function\ndef make_adder(n):\n    return lambda x: x + n\nadd10 = make_adder(10)\n\n# class: equivalent but heavier\nclass Adder:\n    def __init__(self, n): self.n = n\n    def __call__(self, x): return x + self.n\n```\n\nRule of thumb: one behavior + private state → closure; many behaviors or\nshared interface → class. Decorators are the canonical real-world closure.\n",{"description":148},"Python interview questions on closures and free variables, __closure__, the nonlocal keyword, late binding in loops and the default-argument fix, closures vs classes, and common closure uses.","python\u002Ffunctions\u002Fclosures","Closures & Scope","UgESsdHC55Ja_QpbICRE7AnqAyUYkc7CLH5xgyGMyz8",{"id":786,"title":787,"body":788,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":792,"navigation":153,"order":13,"path":793,"questions":794,"related":181,"seo":815,"seoDescription":816,"stem":817,"subtopic":787,"topic":45,"topicSlug":47,"updated":186,"__hash__":818},"qa\u002Fpython\u002Ffunctions\u002Fdecorators.md","Decorators",{"type":145,"value":789,"toc":790},[],{"title":148,"searchDepth":29,"depth":29,"links":791},[],{},"\u002Fpython\u002Ffunctions\u002Fdecorators",[795,799,803,807,811],{"id":796,"difficulty":162,"q":797,"a":798},"what-is-decorator","What is a decorator and how does it work?","A **decorator** is a **callable that takes a function and returns a (usually\nwrapped) function**, letting you add behaviour **without modifying** the original.\nThe `@decorator` syntax above a `def` is just sugar for **reassigning the name** to\nthe decorator's result: `func = decorator(func)`.\n\n```python\ndef log_calls(func):\n    def wrapper(*args, **kwargs):    # accept any signature\n        print(f\"calling {func.__name__}\")\n        return func(*args, **kwargs) # delegate to the original\n    return wrapper\n\n@log_calls\ndef add(a, b):\n    return a + b\n# equivalent to: add = log_calls(add)\n\nadd(2, 3)   # prints \"calling add\", returns 5\n```\n\nThis works because functions are **first-class objects** — they can be passed\naround and returned. Decorators are the idiomatic way to factor out\n**cross-cutting concerns** (logging, timing, caching, access control).\n",{"id":800,"difficulty":150,"q":801,"a":802},"functools-wraps","Why should you use functools.wraps in a decorator?","Without it, the wrapper **replaces the original function's identity**: the\ndecorated object reports the **wrapper's** `__name__`, `__doc__`, signature, and\n`__module__`, which breaks introspection, debugging, and tools that rely on\nmetadata. **`functools.wraps`** copies that metadata from the original onto the\nwrapper.\n\n```python\nimport functools\n\ndef log_calls(func):\n    @functools.wraps(func)       # copy name, docstring, __wrapped__, etc.\n    def wrapper(*args, **kwargs):\n        return func(*args, **kwargs)\n    return wrapper\n\n@log_calls\ndef greet():\n    \"say hello\"\n    ...\n\ngreet.__name__   # \"greet\"  (without wraps -> \"wrapper\")\ngreet.__doc__    # \"say hello\"\n```\n\nIt also sets `__wrapped__`, so `inspect.signature` and unwrapping still work.\nRule of thumb: **always** apply `@functools.wraps(func)` to your wrapper — it's\neffectively free and prevents subtle bugs.\n",{"id":804,"difficulty":150,"q":805,"a":806},"decorator-with-arguments","How do you write a decorator that takes arguments?","You add **another layer of nesting**: an outer function takes the **decorator's\narguments** and returns the actual decorator, which takes the function and returns\nthe wrapper. So `@repeat(3)` first **calls** `repeat(3)` to get a decorator, which\nis then applied to the function.\n\n```python\nimport functools\n\ndef repeat(n):                       # takes the decorator argument\n    def decorator(func):             # takes the function\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            for _ in range(n):\n                result = func(*args, **kwargs)\n            return result\n        return wrapper\n    return decorator\n\n@repeat(3)                           # repeat(3) returns 'decorator'\ndef ping():\n    print(\"pong\")\n```\n\nThe mental model: `@repeat(3)` is `ping = repeat(3)(ping)` — three calls deep.\nRemember the parentheses: `@repeat(3)` (with args) differs from `@repeat` (passing\nthe function directly), and forgetting them is a common bug.\n",{"id":808,"difficulty":150,"q":809,"a":810},"class-based-decorator","How do you implement a decorator as a class?","A class becomes a decorator by being **callable** — define **`__call__`**. The\n`__init__` receives the decorated function; `__call__` runs the wrapping logic on\neach invocation. This is handy when the decorator needs to **hold state** (like a\ncall count) in a clean, attribute-based way.\n\n```python\nimport functools\n\nclass CountCalls:\n    def __init__(self, func):\n        functools.update_wrapper(self, func)  # the class-based wraps\n        self.func = func\n        self.count = 0\n    def __call__(self, *args, **kwargs):\n        self.count += 1\n        print(f\"call #{self.count}\")\n        return self.func(*args, **kwargs)\n\n@CountCalls\ndef hello():\n    print(\"hi\")\n\nhello(); hello()      # \"call #1\" then \"call #2\"\nhello.count           # 2 — state lives on the instance\n```\n\nUse `functools.update_wrapper` (the function-form of `wraps`) to preserve\nmetadata. Class decorators shine for **stateful** decorators; for simple stateless\nones, a nested function with a `nonlocal` closure is usually lighter.\n",{"id":812,"difficulty":162,"q":813,"a":814},"stacking-decorators","When you stack multiple decorators, in what order do they apply?","Decorators apply **bottom-up** (nearest the function first) at **definition\ntime**, but the resulting wrappers **execute top-down** at **call time**. Stacking\nis just nested application: the top decorator wraps the result of the ones below\nit.\n\n```python\n@a\n@b\ndef f(): ...\n# equivalent to: f = a(b(f))   — b wraps first, a wraps outermost\n\ndef bold(fn):\n    return lambda: \"\u003Cb>\" + fn() + \"\u003C\u002Fb>\"\ndef italic(fn):\n    return lambda: \"\u003Ci>\" + fn() + \"\u003C\u002Fi>\"\n\n@bold\n@italic\ndef text():\n    return \"hi\"\n\ntext()   # \"\u003Cb>\u003Ci>hi\u003C\u002Fi>\u003C\u002Fb>\"  — bold is outer, runs around italic\n```\n\nSo the **closest** decorator is applied first but its logic runs **innermost**.\nOrder matters whenever decorators have side effects or transform results — e.g.\nput `@staticmethod` outermost, or `@app.route` above `@login_required` so auth\nruns before the view.\n",{"description":148},"Python interview questions on decorators, functools.wraps, decorators with arguments, class-based decorators, stacking order, and real-world use cases.","python\u002Ffunctions\u002Fdecorators","lkoUwwiWM-viu-zDimboEggNtVDCmWFyyrcd9HEU_xI",{"id":820,"title":821,"body":822,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":826,"navigation":153,"order":46,"path":827,"questions":828,"related":181,"seo":849,"seoDescription":850,"stem":851,"subtopic":852,"topic":45,"topicSlug":47,"updated":186,"__hash__":853},"qa\u002Fpython\u002Ffunctions\u002Flambdas.md","Lambdas",{"type":145,"value":823,"toc":824},[],{"title":148,"searchDepth":29,"depth":29,"links":825},[],{},"\u002Fpython\u002Ffunctions\u002Flambdas",[829,833,837,841,845],{"id":830,"difficulty":212,"q":831,"a":832},"lambda-syntax","What is a lambda and what are its limitations?","A **lambda** is an anonymous, single-**expression** function:\n`lambda args: expression`. It returns the expression's value automatically\n(no `return`). Its limitation is exactly that — it can hold only **one\nexpression**, no statements, assignments, loops, or annotations.\n\n```python\nsquare = lambda x: x * x\nsquare(5)                  # 25\n\nadd = lambda a, b=1: a + b # defaults allowed\nadd(10)                    # 11\n\n# NOT allowed: lambda x: (y = x; return y)  — no statements\n```\n\nBecause it's an expression, a lambda can be passed inline wherever a\nfunction is expected. Keep them short; anything needing multiple lines or a\ndocstring should be a named `def`.\n",{"id":834,"difficulty":162,"q":835,"a":836},"lambda-vs-def","When should you use a lambda versus a def?","Use a **lambda** for a tiny throwaway function passed **inline** as an\nargument (a sort key, a callback). Use **`def`** for anything reused, named,\ndocumented, or non-trivial. A named function gives a useful `__name__` in\ntracebacks; a lambda just shows `\u003Clambda>`.\n\n```python\n# good lambda: inline, one-off\nsorted(words, key=lambda w: len(w))\n\n# prefer def: reused \u002F needs a name\ndef by_length(w):\n    return len(w)\n```\n\nPEP 8 even discourages **assigning** a lambda to a name\n(`f = lambda x: ...`) — if you need a name, just use `def`. Lambdas shine as\narguments, not as definitions.\n",{"id":838,"difficulty":162,"q":839,"a":840},"higher-order-functions","What is a higher-order function?","A **higher-order function** is one that **takes a function as an argument\nand\u002For returns a function**. They enable composing and parameterizing\nbehavior. Built-in examples include `map`, `filter`, `sorted`, and the\n`functools` tools.\n\n```python\ndef apply_twice(fn, x):\n    return fn(fn(x))           # takes a function\n\napply_twice(lambda n: n + 3, 10)   # 16\n\nlist(map(str.upper, [\"a\", \"b\"]))   # ['A', 'B']\nlist(filter(lambda n: n > 0, [-1, 2, -3, 4]))  # [2, 4]\n```\n\nHigher-order functions are the foundation of functional-style Python and of\ndecorators (which both take and return functions). They let you pass\n*behavior* around as data.\n",{"id":842,"difficulty":162,"q":843,"a":844},"key-argument","How does the key argument work in sorted, max, and min?","`key` takes a **function** applied to each element to derive the value used\nfor **comparison** — the elements themselves aren't changed, only how\nthey're ranked. It's used by `sorted`, `list.sort`, `max`, and `min`.\n\n```python\nwords = [\"banana\", \"kiwi\", \"apple\"]\n\nsorted(words, key=len)                 # ['kiwi', 'apple', 'banana']\nmax(words, key=len)                    # 'banana'\nsorted(words, key=str.lower)           # case-insensitive\n\npeople = [(\"ada\", 36), (\"grace\", 45)]\nmax(people, key=lambda p: p[1])        # ('grace', 45)\n```\n\n`key` is called **once per element** (efficient), unlike the old `cmp`\nstyle. For multi-level sorts, return a **tuple**:\n`key=lambda p: (p.last, p.first)`.\n",{"id":846,"difficulty":162,"q":847,"a":848},"first-class-functions","What does it mean that functions are first-class objects?","In Python, functions are **first-class objects**: they can be assigned to\nvariables, stored in data structures, passed as arguments, and **returned**\nfrom other functions — just like any value. This is what makes higher-order\nfunctions and closures possible.\n\n```python\ndef shout(s): return s.upper() + \"!\"\n\nf = shout                 # assign to a variable\nf(\"hi\")                   # 'HI!'\n\ndispatch = {\"loud\": shout}        # store in a dict\ndispatch[\"loud\"](\"hey\")           # 'HEY!'\n\ndef make_op(op):                  # return a function\n    return (lambda a, b: a + b) if op == \"+\" else (lambda a, b: a - b)\nmake_op(\"+\")(2, 3)                # 5\n```\n\nTreating functions as values enables strategy\u002Fdispatch tables, callbacks,\nand decorators. There's no separate \"function pointer\" concept — the\nfunction *is* the object.\n",{"description":148},"Python interview questions on lambda syntax and limitations, lambda vs def, higher-order functions, the key argument in sorted\u002Fmax\u002Fmin, and functions as first-class objects.","python\u002Ffunctions\u002Flambdas","Lambdas & Higher-Order Functions","9_y3LjVbDK5c4cIpcmC5IQFqaQxIZJqZxiPop420_2E",{"id":855,"title":856,"body":857,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":861,"navigation":153,"order":13,"path":862,"questions":863,"related":181,"seo":993,"seoDescription":994,"stem":995,"subtopic":996,"topic":20,"topicSlug":21,"updated":997,"__hash__":998},"qa\u002Fpython\u002Ffundamentals\u002Fmutability.md","Mutability",{"type":145,"value":858,"toc":859},[],{"title":148,"searchDepth":29,"depth":29,"links":860},[],{},"\u002Fpython\u002Ffundamentals\u002Fmutability",[864,868,872,876,880,884,888,892,896,900,904,908,912,916,920,924,928,932,935,939,943,947,951,955,958,962,966,970,974,978,982,986,989],{"id":865,"difficulty":212,"q":866,"a":867},"mutable-immutable","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":869,"difficulty":150,"q":870,"a":871},"default-arg","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":873,"difficulty":162,"q":874,"a":875},"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":877,"difficulty":162,"q":878,"a":879},"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":881,"difficulty":162,"q":882,"a":883},"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":885,"difficulty":150,"q":886,"a":887},"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":889,"difficulty":162,"q":890,"a":891},"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":893,"difficulty":150,"q":894,"a":895},"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":897,"difficulty":150,"q":898,"a":899},"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":901,"difficulty":150,"q":902,"a":903},"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":905,"difficulty":162,"q":906,"a":907},"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":909,"difficulty":150,"q":910,"a":911},"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":913,"difficulty":150,"q":914,"a":915},"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":917,"difficulty":162,"q":918,"a":919},"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":921,"difficulty":150,"q":922,"a":923},"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":925,"difficulty":162,"q":926,"a":927},"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":929,"difficulty":212,"q":930,"a":931},"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":462,"difficulty":162,"q":933,"a":934},"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":936,"difficulty":150,"q":937,"a":938},"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":940,"difficulty":162,"q":941,"a":942},"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":944,"difficulty":162,"q":945,"a":946},"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":948,"difficulty":150,"q":949,"a":950},"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":952,"difficulty":150,"q":953,"a":954},"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":474,"difficulty":162,"q":956,"a":957},"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":959,"difficulty":150,"q":960,"a":961},"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":963,"difficulty":212,"q":964,"a":965},"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":967,"difficulty":212,"q":968,"a":969},"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":971,"difficulty":162,"q":972,"a":973},"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":975,"difficulty":162,"q":976,"a":977},"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":979,"difficulty":212,"q":980,"a":981},"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":983,"difficulty":162,"q":984,"a":985},"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":439,"difficulty":162,"q":987,"a":988},"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":990,"difficulty":150,"q":991,"a":992},"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",{"description":148},"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","2026-06-17","lfA_zY1n_f1sWG-yTVHSPtktZt0M8Eoy085LnkCD_To",{"id":1000,"title":1001,"body":1002,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1006,"navigation":153,"order":11,"path":1007,"questions":1008,"related":181,"seo":1033,"seoDescription":1034,"stem":1035,"subtopic":1036,"topic":20,"topicSlug":21,"updated":186,"__hash__":1037},"qa\u002Fpython\u002Ffundamentals\u002Fnumbers-operators.md","Numbers Operators",{"type":145,"value":1003,"toc":1004},[],{"title":148,"searchDepth":29,"depth":29,"links":1005},[],{},"\u002Fpython\u002Ffundamentals\u002Fnumbers-operators",[1009,1013,1017,1021,1025,1029],{"id":1010,"difficulty":212,"q":1011,"a":1012},"numeric-types","What are Python's built-in numeric types?","Three: **`int`** (whole numbers, unlimited size), **`float`** (double-precision\nbinary floating point), and **`complex`** (a real + imaginary part written with\n`j`). `bool` is technically a subclass of `int` (`True == 1`).\n\n```python\na = 42          # int\nb = 3.14        # float\nc = 2 + 3j      # complex\nc.real, c.imag  # (2.0, 3.0)\nTrue + True     # 2  — bool is an int subclass\n```\n\nWhy it matters: mixing types **promotes** to the wider one (`int + float -> float`),\nand knowing the three types explains conversion and precision behavior.\n",{"id":1014,"difficulty":162,"q":1015,"a":1016},"division-floor-modulo","How do `\u002F`, `\u002F\u002F`, and `%` behave, especially with negatives?","`\u002F` is **true division** and always returns a `float`. `\u002F\u002F` is **floor division**\n— it rounds **toward negative infinity**, not toward zero. `%` is the matching\nmodulo, and in Python its **result takes the sign of the divisor**.\n\n```python\n7 \u002F 2       # 3.5    — always float\n7 \u002F\u002F 2      # 3\n-7 \u002F\u002F 2     # -4     — floors toward -infinity, not -3\n-7 % 2      # 1      — sign follows the divisor (2)\n7 % -2      # -1     — sign follows the divisor (-2)\n```\n\nThe identity always holds: `(a \u002F\u002F b) * b + (a % b) == a`. Rule of thumb: Python's\nfloor\u002Fmodulo differ from C\u002FJava for negatives — expect non-negative `%` when the\ndivisor is positive.\n",{"id":1018,"difficulty":212,"q":1019,"a":1020},"int-arbitrary-precision","Why don't Python integers overflow?","Python `int` has **arbitrary precision** — it grows to hold any value, limited only\nby available memory. There is no fixed 32\u002F64-bit width, so computations never\nsilently wrap around like in C or Java.\n\n```python\n2 ** 100        # 1267650600228229401496703205376\nx = 10 ** 1000  # a 1001-digit integer — no overflow\nimport sys\nsys.maxsize     # largest \"native\" int, but ints can exceed it freely\n```\n\nWhy it matters: you can compute huge factorials or cryptographic numbers directly,\nbut very large ints cost more memory and arithmetic gets slower. Rule of thumb:\ninteger overflow is simply not a concern in Python.\n",{"id":1022,"difficulty":162,"q":1023,"a":1024},"float-precision-decimal","Why is 0.1 + 0.2 not exactly 0.3?","Floats are stored in **binary (IEEE 754)**, and values like 0.1 and 0.2 have no\nexact binary representation — so tiny rounding errors accumulate. This is inherent\nto binary floating point, not a Python bug.\n\n```python\n0.1 + 0.2            # 0.30000000000000004\n0.1 + 0.2 == 0.3     # False\nround(0.1 + 0.2, 2)  # 0.3   — round for display\nimport math\nmath.isclose(0.1 + 0.2, 0.3)   # True — tolerant comparison\n\nfrom decimal import Decimal\nDecimal(\"0.1\") + Decimal(\"0.2\")   # Decimal('0.3')  — exact\n```\n\nRule of thumb: never compare floats with `==`; use `math.isclose` or round, and\nreach for **`Decimal`** when you need exact decimal arithmetic (e.g. money).\n",{"id":1026,"difficulty":162,"q":1027,"a":1028},"bitwise-operators","What are Python's bitwise operators?","Bitwise operators work on the binary representation of integers: **`&`** (and),\n**`|`** (or), **`^`** (xor), **`~`** (not\u002Finvert), **`\u003C\u003C`** (left shift), and\n**`>>`** (right shift). Shifting left by `n` multiplies by `2**n`.\n\n```python\n5 & 3    # 1   (0b101 & 0b011)\n5 | 3    # 7   (0b111)\n5 ^ 3    # 6   (0b110)\n~5       # -6  (~x == -(x+1))\n1 \u003C\u003C 4   # 16  (1 * 2**4)\n20 >> 2  # 5   (20 \u002F\u002F 4)\n```\n\nWhy it matters: bitwise ops power flags\u002Fbitmasks, fast power-of-two math, and\nlow-level protocols. Rule of thumb: `~x` equals `-(x + 1)` because of two's\ncomplement.\n",{"id":1030,"difficulty":212,"q":1031,"a":1032},"divmod-and-power","What do `divmod` and `**` do?","**`divmod(a, b)`** returns the quotient and remainder as a single tuple\n`(a \u002F\u002F b, a % b)` in one call. **`**`** is exponentiation; with a third argument,\nthe built-in `pow(base, exp, mod)` does efficient modular exponentiation.\n\n```python\ndivmod(17, 5)     # (3, 2)  — quotient and remainder together\n2 ** 10           # 1024\npow(2, 10)        # 1024  — same as **\npow(2, 10, 1000)  # 24    — (2**10) % 1000, computed efficiently\n```\n\nRule of thumb: use `divmod` when you need both results (e.g. converting seconds to\nminutes\u002Fseconds), and `pow(a, b, m)` for modular math instead of `(a ** b) % m`.\n",{"description":148},"Python interview questions on int\u002Ffloat\u002Fcomplex, floor division and modulo with negatives, arbitrary precision, float precision, bitwise operators, and divmod.","python\u002Ffundamentals\u002Fnumbers-operators","Numbers & Operators","y_gRkCXYAeOaIuz3yr39UOW1-0X0sEQJiimG2onF8sA",{"id":1039,"title":1040,"body":1041,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1045,"navigation":153,"order":29,"path":1046,"questions":1047,"related":181,"seo":1067,"seoDescription":1068,"stem":1069,"subtopic":1070,"topic":20,"topicSlug":21,"updated":186,"__hash__":1071},"qa\u002Fpython\u002Ffundamentals\u002Fscope-legb.md","Scope Legb",{"type":145,"value":1042,"toc":1043},[],{"title":148,"searchDepth":29,"depth":29,"links":1044},[],{},"\u002Fpython\u002Ffundamentals\u002Fscope-legb",[1048,1052,1056,1060,1063],{"id":1049,"difficulty":162,"q":1050,"a":1051},"legb-rule","What is the LEGB rule?","**LEGB** describes the order Python searches for a name: **Local** (inside the\ncurrent function), **Enclosing** (any outer functions), **Global** (the module's\ntop level), then **Built-in** (names like `len`, `print`). The first match wins,\nand the search stops there.\n\n```python\nx = \"global\"\ndef outer():\n    x = \"enclosing\"\n    def inner():\n        x = \"local\"\n        print(x)     # \"local\"  — Local found first\n    inner()\nouter()\n```\n\nWhy it matters: nearly every \"why is this variable that value?\" question reduces\nto walking **L -> E -> G -> B** until a name is found.\n",{"id":1053,"difficulty":162,"q":1054,"a":1055},"global-vs-nonlocal","What is the difference between `global` and `nonlocal`?","Both let you **rebind** a name from an outer scope instead of creating a new local.\n`global` targets the **module-level** name; `nonlocal` targets the **nearest\nenclosing function** scope (and that name must already exist there).\n\n```python\ncount = 0\ndef inc():\n    global count\n    count += 1        # rebinds module-level count\n\ndef outer():\n    x = 1\n    def inner():\n        nonlocal x\n        x = 2          # rebinds outer's x, not a new local\n    inner()\n    return x           # 2\n```\n\nRule of thumb: you only need these keywords to **reassign** an outer name — you can\nalways *mutate* an outer mutable object (e.g. `list.append`) without them.\n",{"id":1057,"difficulty":150,"q":1058,"a":1059},"unbound-local-error","Why does assigning to a name make it local and cause UnboundLocalError?","Python decides a name's scope **at compile time** by scanning the whole function\nbody. If a name is **assigned anywhere** in a function, it is treated as **local\nfor the entire function** — even on lines before the assignment. Reading it before\nit's bound raises **UnboundLocalError**.\n\n```python\nx = 10\ndef f():\n    print(x)      # UnboundLocalError: x is local because of the line below\n    x = 20        # this assignment makes x local everywhere in f\n```\n\nThe fix is to declare `global x` (or `nonlocal x`) if you meant the outer name, or\nsimply read a different name. Rule of thumb: **an assignment anywhere makes the\nname local everywhere** in that function.\n",{"id":1061,"difficulty":150,"q":770,"a":1062},"late-binding-closures","Closures capture **variables, not values** — this is **late binding**. The inner\nfunction looks up the loop variable **when it is called**, by which time the loop\nhas finished and the variable holds its final value.\n\n```python\nfuncs = [lambda: i for i in range(3)]\n[f() for f in funcs]      # [2, 2, 2]  — all see the final i\n\n# Fix: bind the current value via a default argument\nfuncs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]      # [0, 1, 2]\n```\n\nThe default-argument trick captures `i`'s value at definition time. Rule of thumb:\nif loop-created closures behave strangely, you're hitting late binding — bind the\nvalue explicitly.\n",{"id":1064,"difficulty":162,"q":1065,"a":1066},"module-vs-function-shadowing","How does name shadowing work between module and function scope?","A local name **shadows** (hides) an outer name of the same identity for the\nduration of the scope. Assigning to it inside a function creates a separate local\nthat leaves the **module-level** name untouched.\n\n```python\nvalue = \"module\"\ndef f():\n    value = \"function\"   # new local — shadows the global\n    print(value)         # \"function\"\nf()\nprint(value)             # \"module\"  — unchanged\n\nlist = [1, 2]            # shadows the built-in list() in this scope!\n```\n\nWatch out for shadowing **built-ins** (`list`, `id`, `sum`, `type`) — it silently\nbreaks later calls. Rule of thumb: keep names distinct from outer scopes and\nbuilt-ins to avoid surprising lookups.\n",{"description":148},"Python interview questions on the LEGB scope rule, global vs nonlocal, UnboundLocalError, late binding in loop closures, and name shadowing.","python\u002Ffundamentals\u002Fscope-legb","Variables, Scope & the LEGB Rule","Dpt_Q0OFjMvd7AoVKH6T5OWFzpQa5ln0EU8zsuvK55I",{"id":1073,"title":1074,"body":1075,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1079,"navigation":153,"order":46,"path":1080,"questions":1081,"related":181,"seo":1106,"seoDescription":1107,"stem":1108,"subtopic":1109,"topic":20,"topicSlug":21,"updated":186,"__hash__":1110},"qa\u002Fpython\u002Ffundamentals\u002Fstrings-formatting.md","Strings Formatting",{"type":145,"value":1076,"toc":1077},[],{"title":148,"searchDepth":29,"depth":29,"links":1078},[],{},"\u002Fpython\u002Ffundamentals\u002Fstrings-formatting",[1082,1086,1090,1094,1098,1102],{"id":1083,"difficulty":162,"q":1084,"a":1085},"fstring-vs-format-vs-percent","What is the difference between f-strings, .format(), and % formatting?","All three interpolate values into strings. **`%`** is the oldest C-style syntax.\n**`str.format()`** uses `{}` placeholders and is more flexible. **f-strings**\n(Python 3.6+) embed expressions **inline** and are the fastest and most readable.\n\n```python\nname, n = \"Ada\", 3\n\"Hi %s, %d items\" % (name, n)        # old % style\n\"Hi {}, {} items\".format(name, n)    # str.format\nf\"Hi {name}, {n} items\"              # f-string — preferred\nf\"{n * 2 = }\"                        # \"n * 2 = 6\"  — self-documenting\n```\n\nRule of thumb: prefer **f-strings** for new code — they evaluate expressions\ndirectly and avoid the argument-ordering errors of `%` and `.format()`.\n",{"id":1087,"difficulty":162,"q":1088,"a":1089},"str-vs-bytes","What is the difference between str and bytes?","A **`str`** is a sequence of **Unicode code points** (text); **`bytes`** is a\nsequence of **raw bytes** (0-255). You convert between them with **`encode`**\n(str -> bytes) and **`decode`** (bytes -> str), specifying an encoding like UTF-8.\n\n```python\ns = \"café\"\nb = s.encode(\"utf-8\")    # b'caf\\xc3\\xa9'  — 5 bytes (é is 2)\nb.decode(\"utf-8\")        # \"café\"  — back to text\nlen(s), len(b)           # (4, 5)\ns + b                    # TypeError — can't mix str and bytes\n```\n\nWhy it matters: files and network sockets deal in **bytes**, your program logic in\n**str**. Rule of thumb: decode bytes to str as early as possible and encode back to\nbytes only at the I\u002FO boundary.\n",{"id":1091,"difficulty":212,"q":1092,"a":1093},"common-string-methods","What are the common string methods like split, strip, and join?","**`split`** breaks a string into a list on a separator; **`strip`** removes\nleading\u002Ftrailing whitespace (or given characters); **`join`** glues an iterable of\nstrings together with a separator. All return **new** strings since `str` is\nimmutable.\n\n```python\n\"  a,b,c  \".strip()            # \"a,b,c\"\n\"a,b,c\".split(\",\")            # [\"a\", \"b\", \"c\"]\n\",\".join([\"a\", \"b\", \"c\"])     # \"a,b,c\"\n\"Hello\".lower(), \"Hi\".upper() # (\"hello\", \"HI\")\n\"hello\".replace(\"l\", \"L\")     # \"heLLo\"\n```\n\nRule of thumb: `sep.join(list)` is the inverse of `text.split(sep)`, and chaining\nthese covers most everyday text wrangling.\n",{"id":1095,"difficulty":162,"q":1096,"a":1097},"join-vs-concat-loop","Why use join instead of += to build a string in a loop?","Strings are **immutable**, so each `+=` creates a **brand-new string** and copies\neverything so far — turning a loop into O(n^2) work and lots of garbage. **`join`**\nallocates the result **once**, making it O(n).\n\n```python\n# Slow — new string every iteration\nresult = \"\"\nfor word in words:\n    result += word\n\n# Fast — single allocation\nresult = \"\".join(words)\n```\n\nRule of thumb: collect pieces in a list (or generator) and call `\"\".join(...)` —\nit's the Python equivalent of a `StringBuilder`.\n",{"id":1099,"difficulty":212,"q":1100,"a":1101},"raw-strings","What is a raw string and when do you use it?","A **raw string** (`r\"...\"`) tells Python **not to process backslash escapes**, so\n`\\n`, `\\t`, etc. stay as literal backslash-plus-character. They're ideal for\n**regular expressions** and **Windows file paths**.\n\n```python\nprint(\"a\\tb\")    # a    b   — \\t is a tab\nprint(r\"a\\tb\")   # a\\tb     — backslash kept literally\n\nimport re\nre.findall(r\"\\d+\", \"x12y3\")   # ['12', '3']  — no double-backslashing\npath = r\"C:\\Users\\name\"       # backslashes stay intact\n```\n\nRule of thumb: reach for `r\"...\"` whenever your string is full of backslashes —\nit avoids the noise and bugs of escaping every one.\n",{"id":1103,"difficulty":162,"q":1104,"a":1105},"format-spec-mini-language","How does the format spec mini-language work (padding, :.2f)?","Inside `{}` (or after `:` in `format`), a **format spec** controls alignment,\nwidth, and precision: `{value:[fill][align][width][,][.precision][type]}`. It\nworks in f-strings and `str.format` alike.\n\n```python\nf\"{42:5}\"        # \"   42\"    — width 5, right-aligned (default for numbers)\nf\"{'hi':\u003C5}|\"    # \"hi   |\"   — left-align in width 5\nf\"{'hi':^5}|\"    # \" hi  |\"   — center\nf\"{42:05}\"       # \"00042\"    — zero-padded\nf\"{3.14159:.2f}\" # \"3.14\"     — 2 decimal places\nf\"{1234567:,}\"   # \"1,234,567\"  — thousands separator\nf\"{0.25:.1%}\"    # \"25.0%\"    — percentage\n```\n\nRule of thumb: `.2f` controls decimals, a number sets width, and `\u003C`\u002F`>`\u002F`^` set\nalignment — combine them for clean tabular output.\n",{"description":148},"Python interview questions on f-strings vs .format vs %, str vs bytes, common string methods, join vs +=, raw strings, and the format spec mini-language.","python\u002Ffundamentals\u002Fstrings-formatting","Strings & String Formatting","Fly7brM0I0JbkO0F5-ZVq4BC9z9GcKpnaukrHV5K3lA",{"id":1112,"title":1113,"body":1114,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1118,"navigation":153,"order":55,"path":1119,"questions":1120,"related":181,"seo":1145,"seoDescription":1146,"stem":1147,"subtopic":1148,"topic":20,"topicSlug":21,"updated":186,"__hash__":1149},"qa\u002Fpython\u002Ffundamentals\u002Ftruthiness-conversion.md","Truthiness Conversion",{"type":145,"value":1115,"toc":1116},[],{"title":148,"searchDepth":29,"depth":29,"links":1117},[],{},"\u002Fpython\u002Ffundamentals\u002Ftruthiness-conversion",[1121,1125,1129,1133,1137,1141],{"id":1122,"difficulty":212,"q":1123,"a":1124},"falsy-values","Which values are falsy in Python?","A handful of values are **falsy** (treated as `False` in a boolean context):\n**`None`**, **`False`**, **zero** of any numeric type (`0`, `0.0`, `0j`), and\n**empty containers\u002Fsequences** (`\"\"`, `[]`, `{}`, `()`, `set()`, `range(0)`).\nAlmost everything else is **truthy**.\n\n```python\nbool(0), bool(\"\"), bool([]), bool(None)   # all False\nbool(1), bool(\"x\"), bool([0]), bool(\" \")  # all True\nif not items:        # idiomatic empty check\n    print(\"empty\")\n```\n\nWhy it matters: idiomatic Python uses `if items:` rather than `if len(items) > 0:`.\nRule of thumb: \"empty or zero or None\" is falsy; everything else is truthy.\n",{"id":1126,"difficulty":162,"q":1127,"a":1128},"bool-len-protocol","How does Python decide whether a custom object is truthy?","Python calls **`__bool__`** first; if it's not defined, it falls back to\n**`__len__`** (zero length is falsy). If neither exists, the object is **always\ntruthy** — the default for plain instances.\n\n```python\nclass Box:\n    def __init__(self, items): self.items = items\n    def __len__(self): return len(self.items)   # used for truthiness\n\nbool(Box([]))     # False — __len__ is 0\nbool(Box([1]))    # True\n\nclass Always:\n    def __bool__(self): return False   # __bool__ wins over __len__\nbool(Always())    # False\n```\n\nRule of thumb: define `__bool__` (or `__len__`) so `if obj:` makes sense for your\ntype; otherwise every instance is truthy.\n",{"id":1130,"difficulty":212,"q":1131,"a":1132},"explicit-conversion","How do explicit type conversions like int(), str(), and list() work?","Python provides **constructor functions** that convert between types: `int()`,\n`float()`, `str()`, `bool()`, `list()`, `tuple()`, `set()`, `dict()`. They build a\n**new** object and raise `ValueError` if the input can't be converted.\n\n```python\nint(\"42\")        # 42\nint(\"3.9\")       # ValueError — not a valid int literal\nint(3.9)         # 3   — truncates toward zero\nfloat(\"3.14\")    # 3.14\nstr(255)         # \"255\"\nlist(\"abc\")      # ['a', 'b', 'c']\nlist({1: \"a\"})   # [1]  — iterates keys\n```\n\nRule of thumb: these are **explicit** conversions you call yourself — Python rarely\nconverts types implicitly, so reach for the constructor you need.\n",{"id":1134,"difficulty":212,"q":1135,"a":1136},"empty-container-truthiness","Are empty containers and None falsy?","Yes. Every **empty** built-in container is falsy — `[]`, `{}`, `()`, `set()`,\n`\"\"` — and so is **`None`**. A non-empty container is truthy regardless of what it\ncontains, even `[0]` or `[False]`.\n\n```python\nbool([]), bool({}), bool(set()), bool(\"\")   # all False\nbool([0]), bool([False]), bool({None})       # all True — non-empty!\nbool(None)                                   # False\n```\n\nWatch the trap: `[0]` is **truthy** because it has one element, even though that\nelement is falsy. Rule of thumb: container truthiness depends on **length**, not on\nthe elements' values.\n",{"id":1138,"difficulty":162,"q":1139,"a":1140},"and-or-short-circuit","What do `and` and `or` actually return?","`and`\u002F`or` **short-circuit** and return one of their **operands**, not a strict\n`True`\u002F`False`. `and` returns the **first falsy** operand (or the last if all are\ntruthy); `or` returns the **first truthy** operand (or the last if all are falsy).\n\n```python\n0 and 5         # 0   — first falsy, second never evaluated\n2 and 5         # 5   — both truthy, returns last\n0 or \"default\"  # \"default\"  — first truthy\nNone or 0 or [] # []  — all falsy, returns the last\nname = user_input or \"guest\"   # common default idiom\n```\n\nRule of thumb: `x or default` supplies a fallback, and short-circuiting means the\nright side is skipped when the result is already decided.\n",{"id":1142,"difficulty":212,"q":1143,"a":1144},"is-none-vs-eq-none","Why use `is None` instead of `== None`?","`None` is a **singleton** — there is exactly one `None` object — so `is None`\nchecks **identity**, which is fast and can't be fooled. `== None` calls `__eq__`,\nwhich a class can **override** to return a misleading result.\n\n```python\nif x is None:        # idiomatic, reliable\n    ...\n\nclass Weird:\n    def __eq__(self, other): return True\nWeird() == None      # True   — misleading!\nWeird() is None      # False  — correct\n```\n\nRule of thumb: always compare against `None`, `True`, and `False` with **`is`\u002F\n`is not`** — PEP 8 explicitly recommends it.\n",{"description":148},"Python interview questions on falsy values, __bool__\u002F__len__, explicit type conversion, truthiness of empty containers, short-circuiting and\u002For, and is None vs == None.","python\u002Ffundamentals\u002Ftruthiness-conversion","Truthiness & Type Conversion","H0Pq-9nSUmc-J53irQW_l_BUHoaEHBvAnRvB87cpL4I",{"id":1151,"title":1152,"body":1153,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1157,"navigation":153,"order":13,"path":1158,"questions":1159,"related":181,"seo":1180,"seoDescription":1181,"stem":1182,"subtopic":1183,"topic":135,"topicSlug":137,"updated":186,"__hash__":1184},"qa\u002Fpython\u002Fidioms\u002Feafp-lbyl.md","Eafp Lbyl",{"type":145,"value":1154,"toc":1155},[],{"title":148,"searchDepth":29,"depth":29,"links":1156},[],{},"\u002Fpython\u002Fidioms\u002Feafp-lbyl",[1160,1164,1168,1172,1176],{"id":1161,"difficulty":212,"q":1162,"a":1163},"eafp-lbyl-meaning","What do EAFP and LBYL mean?","**EAFP** = \"**Easier to Ask Forgiveness than Permission**\": just **attempt**\nthe operation and handle the exception if it fails. **LBYL** = \"**Look Before\nYou Leap**\": **check** the preconditions first, then act only if the checks\npass. They are two styles for handling things that *might* go wrong.\n\n```python\n# LBYL — check first\nif \"key\" in config:\n    value = config[\"key\"]\n\n# EAFP — try and handle failure\ntry:\n    value = config[\"key\"]\nexcept KeyError:\n    value = default\n```\n\nEAFP is the **Pythonic** default — it reads naturally and avoids redundant\nchecks. Rule of thumb: in Python, **try the operation and catch the specific\nexception** rather than pre-validating every condition.\n",{"id":1165,"difficulty":162,"q":1166,"a":1167},"why-eafp-preferred","Why is EAFP preferred in Python?","EAFP fits Python's design: exceptions are **cheap** and **idiomatic**, and the\nstyle avoids **duplicating logic**. With LBYL you often check a condition and\nthen perform the same lookup\u002Foperation again, doing the work twice. EAFP also\nstays correct when an object merely **behaves like** the expected type\n(duck typing) rather than passing an explicit type check.\n\n```python\n# LBYL duplicates the access and breaks on duck-typed objects\nif hasattr(obj, \"read\") and callable(obj.read):\n    data = obj.read()\n\n# EAFP — just use it; let the exception surface real problems\ntry:\n    data = obj.read()\nexcept AttributeError:\n    data = None\n```\n\nThe happy path stays uncluttered and the error handling is explicit. Rule of\nthumb: write the **common case** as straight-line code and catch the\n**specific** exception for the rare failure.\n",{"id":1169,"difficulty":150,"q":1170,"a":1171},"lbyl-race-condition","What race condition does LBYL invite?","LBYL introduces a **TOCTOU** bug — \"**Time Of Check to Time Of Use**\". Between\nthe moment you **check** a condition and the moment you **act** on it, another\nthread or process can change the state, so the check is **stale** and the\naction fails or corrupts data. EAFP avoids the gap by acting atomically and\nhandling failure.\n\n```python\nimport os\n# LBYL — file can be deleted between the check and the open (race!)\nif os.path.exists(path):\n    with open(path) as f:    # may still raise FileNotFoundError\n        data = f.read()\n\n# EAFP — no window; the open either works or raises\ntry:\n    with open(path) as f:\n        data = f.read()\nexcept FileNotFoundError:\n    data = None\n```\n\nThe check-then-act pattern is unsafe in concurrent or filesystem contexts.\nRule of thumb: for files, sockets, and shared state, **attempt the operation**\nand handle the exception rather than checking first.\n",{"id":1173,"difficulty":162,"q":1174,"a":1175},"dict-get-vs-check","When should you use dict.get versus checking for a key?","`dict.get(key, default)` returns the value if present and the **default**\n(`None` if unspecified) otherwise — a clean one-liner that avoids both the\n`if key in d` check **and** a `try\u002Fexcept KeyError`. Use a membership check or\n`try\u002Fexcept` only when you must **distinguish a missing key from a stored\n`None`**, or run different logic in each branch.\n\n```python\ncounts = {\"a\": 1}\ncounts.get(\"b\")          # None — no KeyError\ncounts.get(\"b\", 0)       # 0    — supply a default\n\n# need to tell \"missing\" from \"stored None\"? then check explicitly\nif \"b\" in counts:\n    ...\n# or accumulate with setdefault \u002F defaultdict\ncounts.setdefault(\"c\", 0)\n```\n\nFor counting\u002Fgrouping, `collections.defaultdict` or `Counter` is even cleaner.\nRule of thumb: reach for `.get()` with a default for \"value or fallback\", and\nonly branch explicitly when missing-ness itself is meaningful.\n",{"id":1177,"difficulty":150,"q":1178,"a":1179},"eafp-pitfalls","What are the pitfalls of EAFP, and how do you avoid them?","EAFP done carelessly causes two problems. First, a **too-broad `except`**\n(bare `except:` or `except Exception`) can **swallow unrelated bugs** —\ncatching a `KeyError` you didn't intend, or hiding a `NameError`. Second, the\n`try` block should wrap **only** the line that can fail, so you don't\naccidentally catch exceptions from surrounding code.\n\n```python\n# Bad — hides real errors and over-wide try block\ntry:\n    value = config[\"key\"]\n    result = expensive_call(value)   # its errors get caught too!\nexcept Exception:\n    value = default\n\n# Good — narrow exception, minimal try body\ntry:\n    value = config[\"key\"]\nexcept KeyError:\n    value = default\nresult = expensive_call(value)       # outside the try\n```\n\nAlways catch the **most specific** exception and keep the `try` body small.\nRule of thumb: EAFP means \"catch the *one* expected failure\", never \"catch\neverything and hope\".\n",{"description":148},"Python interview questions on EAFP vs LBYL, why try\u002Fexcept is preferred, the race conditions LBYL invites, and dict.get versus checking for a key.","python\u002Fidioms\u002Feafp-lbyl","EAFP vs LBYL","bCZ-XKDsdon8EdwPgbyY1qZW_3AfSyoMZOhOsdPFSIw",{"id":1186,"title":1187,"body":1188,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1192,"navigation":153,"order":11,"path":1193,"questions":1194,"related":181,"seo":1221,"seoDescription":1222,"stem":1223,"subtopic":1224,"topic":135,"topicSlug":137,"updated":186,"__hash__":1225},"qa\u002Fpython\u002Fidioms\u002Fgotchas.md","Gotchas",{"type":145,"value":1189,"toc":1190},[],{"title":148,"searchDepth":29,"depth":29,"links":1191},[],{},"\u002Fpython\u002Fidioms\u002Fgotchas",[1195,1199,1202,1206,1210,1213,1217],{"id":1196,"difficulty":150,"q":1197,"a":1198},"mutable-default-arg","Why is a mutable default argument a gotcha?","A default argument is **evaluated once when the function is defined**, not on each\ncall. So a mutable default like `[]` or `{}` is **created a single time and shared**\nacross every call that doesn't override it — state leaks between calls.\n\n```python\ndef append_to(item, target=[]):   # the SAME list every call\n    target.append(item)\n    return target\n\nappend_to(1)   # [1]\nappend_to(2)   # [1, 2]  \u003C- surprise!\n\ndef append_to(item, target=None): # the fix: None sentinel\n    if target is None:\n        target = []               # fresh list per call\n    target.append(item)\n    return target\n```\n\nUse **`None` as the default** and create the real object inside the body. This is the\nsingle most famous Python footgun.\n",{"id":1061,"difficulty":150,"q":1200,"a":1201},"What is the late-binding closure gotcha in loops?","Closures in Python capture **variables by reference, not by value**. When you create\nfunctions in a loop, they all close over the **same** loop variable, which holds its\n**final value** by the time they're called.\n\n```python\nfuncs = [lambda: i for i in range(3)]\n[f() for f in funcs]          # [2, 2, 2]  — not [0, 1, 2]!\n\n# fix: bind the current value via a default argument\nfuncs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]          # [0, 1, 2]\n```\n\nThe default-argument trick captures `i`'s value **at definition time**. (`functools.\npartial` works too.) Remember: closures see the variable's *latest* value, not a\nsnapshot.\n",{"id":1203,"difficulty":162,"q":1204,"a":1205},"modify-while-iterating","What goes wrong when you modify a list while iterating it?","Changing a list's **size during iteration** shifts the internal index, causing\nelements to be **skipped or repeated**. With dicts and sets it's worse — Python\nraises `RuntimeError: dictionary changed size during iteration`.\n\n```python\nnums = [1, 2, 3, 4]\nfor n in nums:\n    if n % 2 == 0:\n        nums.remove(n)        # skips elements — buggy\nprint(nums)                   # [1, 3] for some inputs, wrong for others\n\nnums = [n for n in nums if n % 2]   # build a new list instead\n```\n\nFix it by iterating over a **copy** (`for n in nums[:]`) or, better, building a new\ncollection with a comprehension or `filter`. Never mutate a container's size while\nlooping over it.\n",{"id":1207,"difficulty":162,"q":1208,"a":1209},"is-vs-eq-cached","Why does `is` give surprising results on numbers and strings?","`is` tests **identity** (same object), while `==` tests **value**. CPython **caches**\nsmall integers (−5 to 256) and **interns** some strings, so `is` *coincidentally*\nreturns `True` for those — but fails for values outside the cache, making it look\nunreliable.\n\n```python\na = 256; b = 256\na is b          # True  — cached\n\nc = 1000; d = 1000\nc is d          # often False — not cached\nc == d          # True   — always correct\n\nx = \"hi\"; y = \"hi\"\nx is y          # True (interned) — don't rely on it\n```\n\nCaching is an **implementation detail**, not a guarantee. Rule: use `==` for value\ncomparison; reserve `is` for singletons (`None`, `True`, `False`).\n",{"id":579,"difficulty":162,"q":1211,"a":1212},"Why is a bare except an anti-pattern?","A bare **`except:`** (or `except Exception:` used carelessly) catches **everything** —\nincluding `KeyboardInterrupt` and `SystemExit` — and **swallows the error silently**,\nhiding bugs and making programs impossible to interrupt or debug.\n\n```python\ntry:\n    risky()\nexcept:                  # catches EVERYTHING, even Ctrl-C\n    pass                 # error vanishes — undebuggable\n\ntry:\n    risky()\nexcept ValueError as e:  # catch only what you expect\n    log.error(\"bad value: %s\", e)\n    raise                # or handle it deliberately\n```\n\nCatch the **specific exceptions** you can actually handle, and avoid `pass` in an\n`except` (at minimum log it). If you must catch broadly, use `except Exception` (not\nbare) so system-exiting signals still propagate.\n",{"id":1214,"difficulty":150,"q":1215,"a":1216},"mutable-class-attr","Why is a mutable class attribute a common bug?","A mutable value assigned **at class level** (outside `__init__`) is **one object\nshared by every instance**. Mutating it through any instance affects **all** of them\n— usually not what you intend.\n\n```python\nclass Cart:\n    items = []                  # SHARED across all instances\n    def add(self, x):\n        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 — correct\n```\n\nInitialize mutable attributes **inside `__init__`** so each instance gets its own.\nClass-level attributes are fine for **immutable** constants\u002Fdefaults, but never for\nmutable per-instance state.\n",{"id":1218,"difficulty":212,"q":1219,"a":1220},"shadowing-builtins","What is the problem with shadowing builtins?","Naming a variable after a builtin — `list`, `dict`, `id`, `str`, `type`, `sum` —\n**shadows** it in that scope, so the original becomes unusable and you get confusing\nerrors later when you try to call it.\n\n```python\nlist = [1, 2, 3]          # shadows the built-in list type\nother = list((4, 5))      # TypeError: 'list' object is not callable\n\nid = 42                   # now id() is gone\nid(other)                 # TypeError: 'int' object is not callable\n```\n\nPick non-conflicting names: `items`\u002F`values` instead of `list`, `mapping` instead of\n`dict`, `user_id` instead of `id`. Linters flag builtin shadowing — heed the warning\nto avoid these baffling bugs.\n",{"description":148},"Python interview questions on common gotchas: mutable default arguments, late-binding closures, modifying a list while iterating, is vs ==, bare except, mutable class attributes, and shadowing builtins.","python\u002Fidioms\u002Fgotchas","Common Gotchas & Anti-patterns","dQ6TWsvM0wnn7CS37n3Xav6bFViKq0g1Er9l_hofq4c",{"id":1227,"title":1228,"body":1229,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1233,"navigation":153,"order":29,"path":1234,"questions":1235,"related":181,"seo":1260,"seoDescription":1261,"stem":1262,"subtopic":1263,"topic":135,"topicSlug":137,"updated":186,"__hash__":1264},"qa\u002Fpython\u002Fidioms\u002Fpep8-style.md","Pep8 Style",{"type":145,"value":1230,"toc":1231},[],{"title":148,"searchDepth":29,"depth":29,"links":1232},[],{},"\u002Fpython\u002Fidioms\u002Fpep8-style",[1236,1240,1244,1248,1252,1256],{"id":1237,"difficulty":212,"q":1238,"a":1239},"what-is-pep8","What is PEP 8?","**PEP 8** is the official **style guide for Python code** — a set of conventions for\nformatting and naming that make code **consistent and readable** across the\ncommunity. It's a *recommendation*, not a language rule: code that violates PEP 8\nstill runs fine, but consistency aids collaboration.\n\n```python\n# PEP 8 style\ndef calculate_total(items, tax_rate=0.0):\n    subtotal = sum(items)\n    return subtotal * (1 + tax_rate)\n\n# not PEP 8 — cramped, inconsistent spacing\u002Fnaming\ndef calcTotal(items,taxRate=0.0):\n    subTotal=sum(items);return subTotal*(1+taxRate)\n```\n\nPEP 8 covers indentation (4 spaces), naming, whitespace, imports, and line length.\nIts guiding principle: **readability counts**, since code is read far more often than\nit's written.\n",{"id":1241,"difficulty":212,"q":1242,"a":1243},"naming-conventions","What are PEP 8's naming conventions?","PEP 8 assigns a distinct case to each kind of name so readers can tell them apart at\na glance. **`snake_case`** for functions, variables, and modules;\n**`PascalCase`** (CapWords) for classes; **`UPPER_SNAKE_CASE`** for constants. A\nsingle leading underscore signals \"internal\".\n\n```python\nMAX_RETRIES = 3                  # constant — UPPER_SNAKE_CASE\n\nclass HttpClient:                # class — PascalCase\n    def send_request(self):      # method — snake_case\n        retry_count = 0          # variable — snake_case\n        self._session = None     # _leading underscore = \"internal\"\n```\n\nAvoid single-character names like `l`, `O`, `I` (they look like digits). Method\u002F\nfunction names use the same `snake_case` as variables; only classes and exceptions\nuse `PascalCase`.\n",{"id":1245,"difficulty":212,"q":1246,"a":1247},"imports-line-length","What does PEP 8 say about imports and line length?","**Imports** go at the **top** of the file, **one per line**, grouped in order:\nstandard library, third-party, then local — separated by blank lines. For **line\nlength**, PEP 8 recommends a maximum of **79 characters** (72 for docstrings\u002F\ncomments), though many modern teams relax this to 88 or 100.\n\n```python\n# standard library\nimport os\nimport sys\n\n# third-party\nimport requests\n\n# local\nfrom myapp.utils import helper\n\n# avoid: import os, sys   (multiple on one line)\n```\n\nKeeping imports sorted and grouped makes dependencies obvious. The line-length cap\nkeeps code readable in side-by-side diffs and narrow editors; tools like `isort`\nautomate import ordering.\n",{"id":1249,"difficulty":212,"q":1250,"a":1251},"zen-of-python","What is the Zen of Python?","The **Zen of Python** (PEP 20) is a collection of **19 guiding aphorisms** that\ncapture Python's design philosophy. You can read it any time by running\n**`import this`**. It informs *why* the language and its idioms look the way they do.\n\n```python\nimport this\n# Beautiful is better than ugly.\n# Explicit is better than implicit.\n# Simple is better than complex.\n# Readability counts.\n# There should be one-- and preferably only one --obvious way to do it.\n# ...and 14 more\n```\n\nThe aphorisms favor **clarity, simplicity, and explicitness** over cleverness. They\naren't enforced rules but a cultural compass — when two approaches compete, the Zen\nusually points to the more Pythonic one.\n",{"id":1253,"difficulty":212,"q":1254,"a":1255},"formatters-linters","What are black and ruff, and how do formatters differ from linters?","A **formatter** automatically **rewrites** your code into a consistent layout; a\n**linter** **analyzes** code and **reports** style violations and likely bugs without\n(usually) changing it. **black** is the dominant formatter (opinionated,\nnear-zero-config); **ruff** is an extremely fast linter (and formatter) that\nconsolidates many older tools.\n\n```python\n# before black\nx = {'a':1,'b':2}\n# after black\nx = {\"a\": 1, \"b\": 2}\n\n# command line\n# black .          -> reformats files in place\n# ruff check .     -> reports lint issues\n# ruff check --fix -> auto-fixes what it can\n```\n\nRunning a formatter ends style arguments in code review (the tool decides), while a\nlinter catches unused imports, undefined names, and anti-patterns. Most teams run\nboth, often automatically via pre-commit hooks or CI.\n",{"id":1257,"difficulty":162,"q":1258,"a":1259},"when-break-pep8","When is it acceptable to break PEP 8?","PEP 8 itself says **\"A Foolish Consistency is the Hobgoblin of Little Minds\"** —\nstyle serves readability, so break the rules when following them would make code\n**less readable** or when you must stay consistent with **surrounding code** or an\nexisting API.\n\n```python\n# matching an external library's camelCase API\ndef setUp(self):           # unittest requires this exact name\n    ...\n\n# aligning related assignments can aid readability in some cases\nx      = 1\nlonger = 2\n```\n\nLegitimate reasons: compatibility with code that predates PEP 8, conforming to a\nframework's required names, or when a rule genuinely hurts clarity in context. The\nrule of thumb: **deviate only with a clear readability or compatibility\njustification**, not out of laziness.\n",{"description":148},"Python interview questions on PEP 8, naming conventions, imports and line length, the Zen of Python, formatters and linters like black and ruff, and when it is acceptable to break PEP 8.","python\u002Fidioms\u002Fpep8-style","PEP 8 & Style","XZbVx5iojwhWxTkdsYPJmC4TKVdwn0NbvswTvPh9UZ0",{"id":1266,"title":1267,"body":1268,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":1272,"navigation":153,"order":11,"path":1273,"questions":1274,"related":181,"seo":1299,"seoDescription":1300,"stem":1301,"subtopic":1302,"topic":99,"topicSlug":101,"updated":186,"__hash__":1303},"qa\u002Fpython\u002Finternals\u002Fcpython-model.md","Cpython Model",{"type":145,"value":1269,"toc":1270},[],{"title":148,"searchDepth":29,"depth":29,"links":1271},[],{},"\u002Fpython\u002Finternals\u002Fcpython-model",[1275,1279,1283,1287,1291,1295],{"id":1276,"difficulty":150,"q":1277,"a":1278},"source-to-bytecode","How does CPython go from source to running code?","CPython first **compiles** your `.py` source into **bytecode** — a compact,\nplatform-independent instruction set for the Python **virtual machine**. That\nbytecode is then executed by the **interpreter loop** (a big evaluation loop,\nhistorically a giant `switch`), which runs one bytecode instruction at a time.\nCompiled bytecode is cached as **`.pyc`** files in `__pycache__`.\n\n```python\n# mymod.py  ->  compiled to  __pycache__\u002Fmymod.cpython-3xx.pyc\n# The .pyc is reused if the source is unchanged (matched by hash\u002Ftimestamp),\n# so imports skip recompiling. It is NOT machine code — still bytecode.\ndef add(a, b):\n    return a + b\n```\n\nThe key points: bytecode is an **intermediate** representation (not native\nmachine code), `.pyc` is just a **cache** to skip recompilation on import, and the\nVM interprets it at runtime. Rule of thumb: source -> bytecode -> interpreter\nloop, with `.pyc` caching the middle step.\n",{"id":1280,"difficulty":150,"q":1281,"a":1282},"cpython-vs-others","What is CPython, and how does it differ from PyPy and Jython?","**CPython** is the **reference implementation** written in C — it's what you get\nfrom python.org and what most people mean by \"Python.\" The **language** is a\nspec; an **implementation** runs it. Alternatives include **PyPy** (with a\n**JIT** that often runs much faster), **Jython** (runs on the **JVM**), and\n**IronPython** (on **.NET**).\n\n```python\nimport platform\nprint(platform.python_implementation())   # 'CPython', 'PyPy', 'Jython', ...\n```\n\nThey differ in performance and integration: PyPy speeds up long-running pure\nPython via JIT, Jython\u002FIronPython interoperate with Java\u002F.NET libraries, and\nCPython has the widest C-extension ecosystem (NumPy, etc.) plus the **GIL**. Rule\nof thumb: \"Python\" is the language; CPython is the dominant implementation, and\nothers trade ecosystem reach for speed or platform integration.\n",{"id":1284,"difficulty":162,"q":1285,"a":1286},"dis-module","How do you inspect bytecode with the dis module?","The **`dis`** (\"disassemble\") module shows the **bytecode instructions** a\nfunction compiles to — useful for understanding what Python actually does under\nthe hood and for comparing the cost of two approaches.\n\n```python\nimport dis\n\ndef add(a, b):\n    return a + b\n\ndis.dis(add)\n# Example output (abbreviated):\n#   LOAD_FAST   a\n#   LOAD_FAST   b\n#   BINARY_OP   +        # add the two\n#   RETURN_VALUE\n```\n\nEach line is one VM instruction the interpreter loop executes. `dis` is great for\nanswering \"is this comprehension really faster?\" or seeing how the compiler\ndesugars a construct. Rule of thumb: when you want to know what the interpreter\n*literally* runs, disassemble it with `dis`.\n",{"id":1288,"difficulty":150,"q":1289,"a":1290},"frames-call-stack","What are frames and the call stack?","Every time a function is called, CPython creates a **frame object** — a record\nholding that call's **local variables**, the current **instruction pointer**, and\na reference to the caller. Frames are pushed onto the **call stack** as functions\ncall each other and popped as they return. This is the structure a **traceback**\nwalks when printing an error.\n\n```python\nimport inspect\n\ndef inner():\n    frame = inspect.currentframe()\n    print(frame.f_code.co_name)        # 'inner'\n    print(frame.f_back.f_code.co_name) # 'outer' — the caller's frame\n\ndef outer():\n    inner()\n\nouter()\n```\n\nFrames are why each call has **isolated locals** and why exceptions can report the\nfull chain of calls. Deep\u002Finfinite recursion piles up frames until\n`RecursionError` (the stack limit). Rule of thumb: one frame per active call, all\nchained together as the call stack.\n",{"id":1292,"difficulty":162,"q":1293,"a":1294},"compiled-or-interpreted","Is Python compiled or interpreted?","**Both.** Python source is first **compiled** to **bytecode** (a real compilation\nstep), and that bytecode is then **interpreted** by the CPython virtual machine at\nruntime. So the common \"Python is interpreted\" is only half the story — there's a\ncompile phase, just to bytecode rather than to native machine code.\n\n```python\nimport py_compile\npy_compile.compile(\"mymod.py\")   # explicitly produces the .pyc bytecode\n\n# At runtime, the VM's interpreter loop executes that bytecode.\n```\n\nThe distinction that matters: Python compiles to **portable bytecode**, not to\nCPU-specific machine code (the way C does ahead-of-time). PyPy adds a **JIT** that\n*does* compile hot bytecode to machine code at runtime. Rule of thumb: Python is\ncompiled to bytecode and then interpreted.\n",{"id":1296,"difficulty":150,"q":1297,"a":1298},"gil-role","What role does the GIL play in CPython's execution model?","The **GIL** (Global Interpreter Lock) is a single mutex that lets **only one\nthread execute Python bytecode at a time** in a CPython process. The interpreter\nloop holds it while running instructions and periodically releases it (and during\nblocking I\u002FO), so threads take turns rather than running Python code in parallel.\n\n```python\n# Two threads doing CPU-bound Python work do NOT run in parallel:\n# the GIL serializes their bytecode execution on one core.\n# threads -> good for I\u002FO-bound (GIL released during I\u002FO)\n# processes -> needed for CPU-bound parallelism (each has its own GIL)\n```\n\nIt exists largely to make CPython's memory management (reference counting)\nsimpler and C extensions safer. The consequence is that **threads don't give CPU\nparallelism** — that's what multiprocessing is for (see the Concurrency &\nParallelism topic). Rule of thumb: the GIL means one-bytecode-at-a-time per\nprocess, so use processes for CPU-bound parallelism.\n",{"description":148},"Python interview questions on the CPython execution model: source to bytecode to .pyc, the interpreter loop, the dis module, frames and the call stack, and whether Python is compiled or interpreted.","python\u002Finternals\u002Fcpython-model","The CPython Execution Model","BTfRbdwE60ns7SueJvUjjVuR4jxN9D_oqBK90FfzmXg",{"id":1305,"title":1306,"body":1307,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":1311,"navigation":153,"order":13,"path":1312,"questions":1313,"related":181,"seo":1338,"seoDescription":1339,"stem":1340,"subtopic":1341,"topic":99,"topicSlug":101,"updated":186,"__hash__":1342},"qa\u002Fpython\u002Finternals\u002Fgarbage-collection.md","Garbage Collection",{"type":145,"value":1308,"toc":1309},[],{"title":148,"searchDepth":29,"depth":29,"links":1310},[],{},"\u002Fpython\u002Finternals\u002Fgarbage-collection",[1314,1318,1322,1326,1330,1334],{"id":1315,"difficulty":162,"q":1316,"a":1317},"reference-counting","How does reference counting work in Python?","Every CPython object carries a **reference count** — the number of references\npointing at it. The count goes **up** when you bind a new name, append it to a\ncontainer, or pass it to a function, and **down** when a name is reassigned, goes\nout of scope, or is `del`'d. When the count hits **zero**, the object is\n**immediately deallocated**.\n\n```python\na = [1, 2, 3]    # the list's refcount is 1\nb = a            # 2 — another reference\ndel a            # 1 — still alive (b references it)\nb = None         # 0 — list is freed right away\n```\n\nThis is **deterministic** and prompt — memory is reclaimed the instant the last\nreference disappears, not at some later sweep.\n\nWhy it matters: reference counting handles the vast majority of garbage, but it\n**can't free reference cycles** on its own, which is why CPython adds a second\ncollector.\n",{"id":1319,"difficulty":150,"q":1320,"a":1321},"cyclic-gc","Why does Python need a cyclic garbage collector?","Reference counting fails on **reference cycles** — objects that refer to each other,\nso their counts never reach zero even when nothing outside the cycle reaches them.\nWithout help they'd leak forever. CPython's **cyclic garbage collector** (the `gc`\nmodule) periodically finds and frees these unreachable cycles.\n\n```python\nimport gc\na, b = {}, {}\na['b'] = b        # a -> b\nb['a'] = a        # b -> a   (a cycle)\ndel a, b          # both refcounts stay at 1 — NOT freed by refcounting\ngc.collect()      # the cyclic collector reclaims the unreachable cycle\n```\n\nThe collector works by tracking **container** objects (lists, dicts, instances) and\ndetecting groups whose only references are internal to the group.\n\nRule of thumb: you rarely call `gc.collect()` manually — it runs automatically —\nbut it's worth knowing cycles are collected **later**, not immediately like refcounts.\n",{"id":1323,"difficulty":162,"q":1324,"a":1325},"sys-getrefcount","What does sys.getrefcount tell you and why is it always higher than expected?","`sys.getrefcount(obj)` returns the object's current **reference count**. It almost\nalways reads **one higher** than you expect because **passing the object as the\nargument** creates a temporary reference for the duration of the call.\n\n```python\nimport sys\nx = []\nsys.getrefcount(x)   # 2 — one for `x`, one for the argument itself\n\ny = x\nsys.getrefcount(x)   # 3 — `x`, `y`, and the argument\n```\n\nSmall ints and interned strings show huge counts because they're **cached\nsingletons** shared across the whole interpreter.\n\nWhy it matters: it's a debugging\u002Fteaching tool for understanding aliasing and leaks\n— just remember to **subtract one** for the call's own reference, and don't rely on\nexact values across implementations.\n",{"id":1327,"difficulty":150,"q":1328,"a":1329},"weak-references","What is a weak reference and when do you use one?","A **weak reference** (`weakref` module) points to an object **without increasing its\nreference count**, so it does **not** keep the object alive. When the last *strong*\nreference is gone, the object is collected and the weak reference returns `None`.\n\n```python\nimport weakref\nclass Node: pass\n\nn = Node()\nr = weakref.ref(n)   # weak — doesn't count toward n's lifetime\nr()                  # \u003CNode object> — still alive\ndel n                # last strong ref gone -> object freed\nr()                  # None — referent is dead\n```\n\nThey're ideal for **caches** and **observer\u002Fparent-child back-references** where you\nwant to reference an object but not prevent its cleanup — `weakref.WeakValueDictionary`\nis a common cache type.\n\nRule of thumb: use a weak reference to **break a cycle** or avoid a memory leak when\none object should not own the lifetime of another.\n",{"id":1331,"difficulty":150,"q":1332,"a":1333},"del-pitfalls","What are the pitfalls of the __del__ method?","`__del__` is a **finalizer** called when an object is about to be destroyed — but\nits timing is **not guaranteed**. It runs only when the refcount hits zero (or later,\nvia the cyclic collector), so you can't rely on **when**, or even **whether**, it\nruns. Note `del obj` only decrements the refcount; it doesn't directly call `__del__`.\n\n```python\nclass Resource:\n    def __del__(self):\n        print(\"cleaning up\")   # may run late, or not at all on interpreter exit\n\nr = Resource()\nr2 = r\ndel r                          # nothing happens — r2 still references it\ndel r2                         # NOW refcount is 0 -> __del__ runs\n```\n\nObjects in a reference cycle that define `__del__` historically couldn't be\ncollected at all (improved in Python 3.4+ via PEP 442), and exceptions raised inside\n`__del__` are **ignored**.\n\nRule of thumb: don't use `__del__` for important cleanup — use **context managers\n(`with`)** or `try\u002Ffinally`, which give deterministic, explicit release.\n",{"id":1335,"difficulty":150,"q":1336,"a":1337},"generational-gc","What is generational garbage collection?","The cyclic collector is **generational**: it sorts tracked objects into **three\ngenerations (0, 1, 2)** based on how many collections they've survived. New objects\nstart in **generation 0**, which is scanned **most frequently**; survivors are\n**promoted** to older generations that are scanned **less often**.\n\nThe idea is the **weak generational hypothesis**: most objects die young, so it's\nefficient to focus collection effort on the youngest generation and rarely re-scan\nlong-lived objects.\n\n```python\nimport gc\ngc.get_count()        # (gen0, gen1, gen2) allocation counters\ngc.get_threshold()    # (700, 10, 10) — triggers per generation\ngc.collect(0)         # collect only generation 0 (cheap, frequent)\n```\n\nA generation-0 collection is fast and common; full (generation-2) collections are\nrarer and more expensive.\n\nRule of thumb: this is mostly automatic, but for latency-sensitive code you can tune\nthresholds, call `gc.collect()` strategically, or `gc.disable()` it if you manage\nlifetimes carefully.\n",{"description":148},"Python interview questions on memory management — reference counting, the cyclic garbage collector and reference cycles, sys.getrefcount, weak references, __del__ pitfalls, and generational GC.","python\u002Finternals\u002Fgarbage-collection","Garbage Collection & Reference Counting","FHx3siFM4NKe-jUoUL6tXG3myt5m8Tb5BnZ4L36YsOU",{"id":1344,"title":1345,"body":1346,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1350,"navigation":153,"order":29,"path":1351,"questions":1352,"related":181,"seo":1373,"seoDescription":1374,"stem":1375,"subtopic":1376,"topic":99,"topicSlug":101,"updated":186,"__hash__":1377},"qa\u002Fpython\u002Finternals\u002Fidentity-interning.md","Identity Interning",{"type":145,"value":1347,"toc":1348},[],{"title":148,"searchDepth":29,"depth":29,"links":1349},[],{},"\u002Fpython\u002Finternals\u002Fidentity-interning",[1353,1357,1359,1362,1365,1369],{"id":1354,"difficulty":162,"q":1355,"a":1356},"is-vs-equals","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\n*exact same object* in memory?\" — which is effectively an `id()` comparison. They\nfrequently agree, but conceptually they ask completely different questions.\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, just another name\n```\n\nUse `==` whenever you care about the **value**, which is almost always. Reserve\n`is` for **identity** checks against singletons. Rule of thumb: if you're\ncomparing data, use `==`; if you're checking \"is this literally that object,\" use\n`is`.\n",{"id":889,"difficulty":162,"q":890,"a":1358},"**`id(obj)`** returns a unique integer **identity** for an object that's constant\nfor the object's **lifetime**. In **CPython** it's the object's **memory\naddress**, though that's an implementation detail. Two names with the same `id`\nrefer to the **same object**, and `is` is essentially an `id` comparison.\n\n```python\na = [1, 2]\nb = a\nid(a) == id(b)   # True  — same object\na is b           # True  — equivalent check\n\nc = [1, 2]\nid(a) == id(c)   # False — equal value, different object\n```\n\n`id` is handy for understanding **aliasing** — why mutating through one name\nshows up through another. Don't rely on the actual numeric value (it can be\nreused after an object is garbage-collected); use it only to reason about\nsameness.\n",{"id":893,"difficulty":162,"q":1360,"a":1361},"What is the small-int cache?","CPython **pre-creates and caches integers from −5 to 256** as singletons at\nstartup. Any time a value in that range is needed, the **same cached object** is\nreused — so equal small ints share identity and `is` returns `True`. Outside that\nwindow, equal integers are typically **distinct** objects.\n\n```python\na = 256\nb = 256\na is b      # True  — both point at the cached 256\n\nc = 257\nd = 257\nc is d      # often False — separate objects\nc == d      # True  — always compare values with ==\n```\n\nThis is purely a **memory\u002Fperformance optimization** and a CPython\nimplementation detail — not a language guarantee. It's the single biggest reason\n`is` \"appears\" to work on numbers. Rule of thumb: never use `is` to compare ints,\nuse `==`.\n",{"id":897,"difficulty":162,"q":1363,"a":1364},"What is string interning and sys.intern?","**Interning** stores a **single shared copy** of a string so identical strings\ncan be the **same object**, saving memory and making equality checks a fast\npointer comparison. CPython **auto-interns** short, identifier-like string\nliterals (and compile-time constants); other strings may not be. You can force it\nwith **`sys.intern`**.\n\n```python\na = \"hello\"\nb = \"hello\"\na is b           # True  — auto-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!\")\nf = sys.intern(\"hello world!\")\ne is f           # True — explicitly interned\n```\n\n`sys.intern` is useful when you compare the **same strings repeatedly** (parsing,\ntokenizing, dict keys) and want the speed\u002Fmemory win. Like int caching, which\nstrings auto-intern is an **implementation detail** — never rely on it for\ncorrectness.\n",{"id":1366,"difficulty":150,"q":1367,"a":1368},"is-coincidental","Why does is sometimes \"work\" coincidentally?","`is` sometimes returns `True` for equal values **only because CPython caches or\ninterns those particular objects** — small ints (−5..256) and many short string\nliterals share one object. It's an accident of optimization, not equality, so it\nbreaks the moment you leave the cached range.\n\n```python\nx = 100\nx is 100         # True  — cached small int (deceiving!)\n\ny = 1000\ny is 1000        # often False — outside the cache\ny == 1000        # True  — the correct comparison\n```\n\nThe danger is that code passes during testing with small values and then fails in\nproduction with larger ones. Treat any `is`-on-a-value that works as a **lucky\ncoincidence**. Rule of thumb: if swapping `is` for `==` would change behavior on\n*some* input, you should have used `==`.\n",{"id":1370,"difficulty":212,"q":1371,"a":1372},"correct-use-of-is","What is the correct use of is?","Use `is` to test **identity against singletons** — objects of which there is\nexactly **one** — most commonly **`None`**, but also `True`, `False`, and your own\n**sentinel** objects. For singletons `is` is correct, fast, and can't be fooled by\na custom `__eq__`.\n\n```python\nif x is None:          # idiomatic, recommended by PEP 8\n    ...\n\n_MISSING = object()    # unique sentinel\ndef get(d, key, default=_MISSING):\n    val = d.get(key, _MISSING)\n    if val is _MISSING:    # distinguishes \"absent\" from \"value is None\"\n        return default\n    return val\n```\n\nA sentinel like `object()` is ideal precisely because `is` checks identity — no\nother object can ever match it. Rule of thumb: `is`\u002F`is not` for `None` and\nsentinels; `==`\u002F`!=` for everything else.\n",{"description":148},"Python interview questions on object identity: is vs ==, the id() function, the small-int cache, string interning, and the correct use of is for None and sentinels.","python\u002Finternals\u002Fidentity-interning","Identity, is vs ==, & Interning","Bxm0c6IuOw0YEDIc513jbfs-3-is_vtYIBOUhOkjzzY",{"id":1379,"title":1380,"body":1381,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1385,"navigation":153,"order":29,"path":1386,"questions":1387,"related":181,"seo":1408,"seoDescription":1409,"stem":1410,"subtopic":1411,"topic":37,"topicSlug":38,"updated":186,"__hash__":1412},"qa\u002Fpython\u002Fiteration\u002Fcomprehensions.md","Comprehensions",{"type":145,"value":1382,"toc":1383},[],{"title":148,"searchDepth":29,"depth":29,"links":1384},[],{},"\u002Fpython\u002Fiteration\u002Fcomprehensions",[1388,1392,1396,1400,1404],{"id":1389,"difficulty":212,"q":1390,"a":1391},"list-comp-syntax","What is a list comprehension and why use one?","A **list comprehension** is a concise expression that builds a list in a\nsingle readable line: `[expression for item in iterable]`. It replaces the\ncommon pattern of creating an empty list and `append`-ing in a `for` loop.\n\n```python\n# the verbose way\nsquares = []\nfor n in range(5):\n    squares.append(n * n)\n\n# the comprehension\nsquares = [n * n for n in range(5)]   # [0, 1, 4, 9, 16]\n```\n\nBeyond brevity, comprehensions are usually **faster** than an equivalent\nloop (the iteration runs in optimized C) and they signal intent: \"I'm\ntransforming a sequence into a new list.\" Reach for one whenever you're\nbuilding a list by mapping or filtering another iterable.\n",{"id":1393,"difficulty":212,"q":1394,"a":1395},"comp-filtering","How do you filter and conditionally transform inside a comprehension?","A trailing **`if` clause filters** items — only elements for which it is\ntruthy are kept. A **conditional expression** (`x if cond else y`) goes at\nthe **front**, before the `for`, because it's part of the output expression,\nnot a filter.\n\n```python\nnums = range(6)\n\nevens = [n for n in nums if n % 2 == 0]          # filter: [0, 2, 4]\nlabels = [\"even\" if n % 2 == 0 else \"odd\"        # transform every item\n          for n in nums]                         # ['even','odd',...]\nboth = [n * 2 for n in nums if n > 2]            # filter THEN transform\n```\n\nRemember the position rule: a **filter `if`** comes after the `for`; a\n**`if\u002Felse` expression** comes before it. Mixing them up is a common\nbeginner error.\n",{"id":1397,"difficulty":162,"q":1398,"a":1399},"nested-comp","How do nested comprehensions work?","You can chain multiple `for` clauses to flatten or iterate over nested\ndata. The clauses read **left to right in the same order** as nested\nloops. You can also nest a comprehension *inside* the output expression to\nbuild a list of lists.\n\n```python\nmatrix = [[1, 2], [3, 4]]\n\nflat = [x for row in matrix for x in row]   # [1, 2, 3, 4]\n# equivalent to:\n#   for row in matrix:\n#       for x in row:\n\ngrid = [[r * c for c in range(3)] for r in range(3)]  # list of rows\n```\n\nThe trap is reading the order backwards — the **outer** loop is written\nfirst. Keep nesting shallow; two levels is usually the readability limit\nbefore a plain loop is clearer.\n",{"id":1401,"difficulty":212,"q":1402,"a":1403},"dict-set-comp","What are dict and set comprehensions?","The same syntax works for dicts and sets. A **dict comprehension** uses\n`{key: value for ...}` and a **set comprehension** uses `{expr for ...}`\n(no colon). Sets automatically deduplicate results.\n\n```python\nnames = [\"ada\", \"grace\", \"ada\"]\n\nlengths = {n: len(n) for n in names}     # {'ada': 3, 'grace': 5}\nunique  = {n for n in names}             # {'ada', 'grace'}  (deduped)\nswapped = {v: k for k, v in lengths.items()}  # invert a dict\n```\n\nNote `{}` alone is an empty **dict**, not a set — use `set()` for an empty\nset. Dict and set comprehensions accept the same `if` filters and nested\n`for` clauses as list comprehensions.\n",{"id":1405,"difficulty":162,"q":1406,"a":1407},"comp-vs-map-filter","When should you NOT use a comprehension?","Comprehensions are for **building a collection**. Avoid them when you only\nwant **side effects** (printing, writing to a DB) — that abuse hides intent\nand builds a throwaway list. Use a plain `for` loop instead. Also skip them\nwhen the logic is so complex that the line becomes unreadable.\n\n```python\n# bad: comprehension purely for side effects\n[print(x) for x in items]      # builds a useless list of Nones\n\n# good: a loop says \"do this for each\"\nfor x in items:\n    print(x)\n```\n\nComprehensions overlap with `map`\u002F`filter`, but are usually more readable\nand avoid `lambda`. Prefer a generator expression `(...)` over a list comp\nwhen you only iterate once and don't need to materialize the whole result.\n",{"description":148},"Python interview questions on list, dict, and set comprehensions, filtering with if, conditional expressions, nested comprehensions, and when not to use them.","python\u002Fiteration\u002Fcomprehensions","List, Dict & Set Comprehensions","uGKkZQVAIauWnty3ubl1uoueazHZ0xd5yzGpbq2r5x4",{"id":1414,"title":1415,"body":1416,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1420,"navigation":153,"order":46,"path":1421,"questions":1422,"related":181,"seo":1443,"seoDescription":1444,"stem":1445,"subtopic":1446,"topic":37,"topicSlug":38,"updated":186,"__hash__":1447},"qa\u002Fpython\u002Fiteration\u002Fenumerate-zip.md","Enumerate Zip",{"type":145,"value":1417,"toc":1418},[],{"title":148,"searchDepth":29,"depth":29,"links":1419},[],{},"\u002Fpython\u002Fiteration\u002Fenumerate-zip",[1423,1427,1431,1435,1439],{"id":1424,"difficulty":212,"q":1425,"a":1426},"enumerate-basics","What does enumerate do and how do you set its start?","`enumerate(iterable)` pairs each item with a running **index**, yielding\n`(index, value)` tuples. It saves you from the error-prone manual counter\nor `range(len(...))` pattern. The optional **`start`** argument sets the\nfirst index (default `0`).\n\n```python\ncolors = [\"red\", \"green\", \"blue\"]\n\nfor i, c in enumerate(colors):\n    print(i, c)            # 0 red \u002F 1 green \u002F 2 blue\n\nfor rank, c in enumerate(colors, start=1):\n    print(rank, c)         # 1 red \u002F 2 green \u002F 3 blue\n```\n\nPrefer `enumerate` over `range(len(seq))` whenever you need both the index\nand the item — it's more readable and works on any iterable, not just\nindexable sequences.\n",{"id":1428,"difficulty":212,"q":1429,"a":1430},"zip-basics","What does zip do and how does zip_longest differ?","`zip(a, b, ...)` walks several iterables **in parallel**, yielding tuples of\naligned elements. It stops at the **shortest** input, silently dropping\nextras. `itertools.zip_longest` instead runs to the **longest**, filling\nmissing slots with a `fillvalue`.\n\n```python\nnames = [\"ada\", \"grace\", \"edsger\"]\nages  = [36, 45]\n\nlist(zip(names, ages))     # [('ada', 36), ('grace', 45)]  — 'edsger' dropped\n\nfrom itertools import zip_longest\nlist(zip_longest(names, ages, fillvalue=0))\n# [('ada', 36), ('grace', 45), ('edsger', 0)]\n```\n\nUse plain `zip` when lengths match (or truncation is intended); reach for\n`zip_longest` when you must not lose data from the longer iterable.\n",{"id":1432,"difficulty":162,"q":1433,"a":1434},"unzip-with-star","How do you unzip a list of pairs with zip(*)?","`zip` is its own inverse. Applying `zip(*pairs)` **unpacks** the list of\ntuples as separate arguments, so `zip` re-groups them column-wise —\neffectively transposing rows into columns.\n\n```python\npairs = [(\"ada\", 36), (\"grace\", 45)]\n\nnames, ages = zip(*pairs)\nnames    # ('ada', 'grace')\nages     # (36, 45)\n```\n\nThis also transposes a matrix: `list(zip(*matrix))`. Note the results are\n**tuples**, not lists, and the input is consumed if it's an iterator — wrap\nin `list(...)` if you need list results.\n",{"id":1436,"difficulty":212,"q":1437,"a":1438},"extended-unpacking","How does extended (star) unpacking work?","A starred target in an assignment captures **\"the rest\"** as a list. You can\nplace `*name` at the start, middle, or end, and the non-starred names take\nfixed positions — Python figures out the split.\n\n```python\nfirst, *rest = [1, 2, 3, 4]      # first=1, rest=[2, 3, 4]\n*init, last = [1, 2, 3, 4]       # init=[1, 2, 3], last=4\nhead, *mid, tail = [1, 2, 3, 4]  # head=1, mid=[2, 3], tail=4\n```\n\nExactly **one** starred target is allowed per assignment, and it always\nbecomes a `list` (even if empty). This is cleaner than slicing for grabbing\nthe head\u002Ftail of a sequence.\n",{"id":1440,"difficulty":162,"q":1441,"a":1442},"zip-dict-star-args","How do you build a dict from zip and pass star-args in a call?","Pairing two sequences with `zip` and feeding them to `dict()` is the\nidiomatic way to build a mapping. The `*` and `**` operators also unpack\niterables\u002Fdicts **into function calls** as positional and keyword\narguments.\n\n```python\nkeys = [\"x\", \"y\", \"z\"]\nvals = [1, 2, 3]\nd = dict(zip(keys, vals))        # {'x': 1, 'y': 2, 'z': 3}\n\ndef point(x, y, z): return (x, y, z)\nargs = [1, 2, 3]\npoint(*args)                     # unpack list -> positional\npoint(**d)                       # unpack dict -> keyword args\n```\n\nUse `*` to spread a sequence into positional parameters and `**` to spread a\ndict into keyword parameters — the call-site mirror of `*args`\u002F`**kwargs` in\na function signature.\n",{"description":148},"Python interview questions on enumerate with start, zip and zip_longest, unzipping with zip(*), extended iterable unpacking, and star-args at the call site.","python\u002Fiteration\u002Fenumerate-zip","enumerate, zip & Unpacking","dlhdQ8WjRw-HuDrX9oGDff8YDeOxNvXA1W7WGXQzGkY",{"id":1449,"title":1450,"body":1451,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1455,"navigation":153,"order":13,"path":1456,"questions":1457,"related":181,"seo":1478,"seoDescription":1479,"stem":1480,"subtopic":1481,"topic":37,"topicSlug":38,"updated":186,"__hash__":1482},"qa\u002Fpython\u002Fiteration\u002Fgenerators.md","Generators",{"type":145,"value":1452,"toc":1453},[],{"title":148,"searchDepth":29,"depth":29,"links":1454},[],{},"\u002Fpython\u002Fiteration\u002Fgenerators",[1458,1462,1466,1470,1474],{"id":1459,"difficulty":212,"q":1460,"a":1461},"what-is-generator","What is a generator and what does the yield keyword do?","A **generator** is a function that produces a **lazy sequence** of values one at\na time. Any function containing **`yield`** becomes a generator function: calling\nit doesn't run the body — it returns a **generator object** (an iterator). Each\ntime you call `next()` (or iterate), the body runs until the next `yield`, hands\nback that value, and **pauses**, preserving all local state.\n\n```python\ndef counter():\n    print(\"start\")\n    yield 1\n    yield 2          # execution pauses here between next() calls\n    yield 3\n\ng = counter()        # nothing printed yet — body hasn't run\nnext(g)              # prints \"start\", returns 1\nnext(g)              # returns 2 (resumes after first yield)\n```\n\nWhen the function returns (or falls off the end), a **`StopIteration`** is\nraised to signal exhaustion. Generators are the simplest way to write a custom\n**iterator** without manually implementing `__iter__`\u002F`__next__`.\n",{"id":1463,"difficulty":162,"q":1464,"a":1465},"generator-vs-list-memory","How does a generator save memory compared to a list?","A list **materializes every element in memory at once**, so its footprint grows\nwith the number of items. A generator holds only its **current state** and\ncomputes each value **on demand**, so its memory use is roughly **constant**\nregardless of how many values it ultimately yields.\n\n```python\nimport sys\nnums = [n * n for n in range(1_000_000)]   # ~8 MB list, built eagerly\ngen  = (n * n for n in range(1_000_000))   # lazy — tiny, fixed size\n\nsys.getsizeof(nums)   # large\nsys.getsizeof(gen)    # ~100 bytes, regardless of range\n```\n\nThis makes generators ideal for **large or streaming data** — reading a\nmulti-gigabyte file line by line, or a pipeline of transformations — where you'd\nnever want the whole dataset in RAM. The trade-off: you can only iterate a\ngenerator **once**, and you can't index or `len()` it.\n",{"id":1467,"difficulty":162,"q":1468,"a":1469},"genexp-vs-listcomp","What is the difference between a generator expression and a list comprehension?","They share syntax but differ in their brackets and behaviour. A **list\ncomprehension** uses `[...]` and builds the **entire list eagerly**. A\n**generator expression** uses `(...)` and produces a **lazy iterator** that\nyields values one at a time, computing nothing until consumed.\n\n```python\nlc = [x * 2 for x in range(5)]   # [0, 2, 4, 6, 8] — built now\nge = (x * 2 for x in range(5))   # \u003Cgenerator object> — built on demand\n\n# parentheses are optional when it's the sole argument:\ntotal = sum(x * 2 for x in range(5))   # streams — no temp list\n```\n\nPrefer a **generator expression** when feeding an aggregator like `sum`, `max`,\n`any`, or `join` over a large source — it avoids creating a throwaway list. Use a\n**list comprehension** when you need the full result repeatedly, want to index\nit, or need `len()`.\n",{"id":1471,"difficulty":162,"q":1472,"a":1473},"yield-from","What does yield from do?","**`yield from \u003Citerable>`** **delegates** to a sub-iterator: it yields every\nvalue from the iterable as if you'd written a loop of `yield`s, but also\ntransparently forwards **`send`**, **`throw`**, and the sub-generator's\n**return value**. It's the clean way to **compose** or **flatten** generators.\n\n```python\ndef chain(*iterables):\n    for it in iterables:\n        yield from it          # vs: for x in it: yield x\n\nlist(chain([1, 2], (3, 4)))    # [1, 2, 3, 4]\n\ndef sub():\n    yield 1\n    return 99                  # captured by the delegator\ndef main():\n    result = yield from sub()  # result == 99\n```\n\nBeyond saving a loop, `yield from` is what makes generator **delegation** and\ncoroutine composition possible. Rule of thumb: use it whenever you want one\ngenerator to **fully drain another**.\n",{"id":1475,"difficulty":150,"q":1476,"a":1477},"infinite-generators","How can a generator be infinite, and why doesn't it hang?","Because generators are **lazy**, the body only advances when a value is\nrequested — so an **unbounded loop** is fine: it never tries to produce all\nvalues at once. You control termination from the **consumer** side, by stopping\niteration whenever you've taken enough.\n\n```python\ndef naturals():\n    n = 0\n    while True:        # infinite — but harmless\n        yield n\n        n += 1\n\nfrom itertools import islice\nlist(islice(naturals(), 5))   # [0, 1, 2, 3, 4] — take just 5\n```\n\nThis underpins `itertools.count`, `cycle`, and `repeat`, and lets you model\nstreams elegantly. The danger is forgetting to bound the consumer:\n`list(naturals())` or `for x in naturals(): print(x)` **will** run forever — pair\ninfinite generators with `islice`, a `break`, or `takewhile`.\n",{"description":148},"Python interview questions on generators and yield, lazy evaluation, generator expressions vs list comprehensions, yield from, and infinite generators.","python\u002Fiteration\u002Fgenerators","Generators & yield","8cOk8zKE2hYNCWFPkpf54--NW0VVil4L10uT-XTtKR0",{"id":1484,"title":1485,"body":1486,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1490,"navigation":153,"order":11,"path":1491,"questions":1492,"related":181,"seo":1517,"seoDescription":1518,"stem":1519,"subtopic":1520,"topic":37,"topicSlug":38,"updated":186,"__hash__":1521},"qa\u002Fpython\u002Fiteration\u002Fiterators.md","Iterators",{"type":145,"value":1487,"toc":1488},[],{"title":148,"searchDepth":29,"depth":29,"links":1489},[],{},"\u002Fpython\u002Fiteration\u002Fiterators",[1493,1497,1501,1505,1509,1513],{"id":1494,"difficulty":162,"q":1495,"a":1496},"iterable-vs-iterator","What is the difference between an iterable and an iterator?","An **iterable** is anything you can loop over — it knows how to produce an\niterator via `__iter__`. An **iterator** is the object that actually does\nthe walking: it has `__next__` and yields one value at a time, remembering\nits position. Every iterator is iterable (its `__iter__` returns itself),\nbut not every iterable is an iterator.\n\n```python\nnums = [1, 2, 3]          # list: iterable, NOT an iterator\nit = iter(nums)           # iterator over the list\nnext(it)                  # 1  — iterators track position\nnext(it)                  # 2\n```\n\nThink of the iterable as the *collection* and the iterator as a\n*cursor\u002Fbookmark* into it. You can create many independent iterators from\none iterable.\n",{"id":1498,"difficulty":162,"q":1499,"a":1500},"iter-next-protocol","What methods make up the iterator protocol?","The **iterator protocol** is two methods. `__iter__` must return the\niterator object itself, and `__next__` returns the next value or raises\n**`StopIteration`** when exhausted. That exception is the agreed signal\nthat there are no more items.\n\n```python\nit = iter([10, 20])\nit.__next__()      # 10\nit.__next__()      # 20\nit.__next__()      # raises StopIteration\n```\n\nAn **iterable** only needs `__iter__` (returning a fresh iterator). An\n**iterator** needs both. The `StopIteration` raise is what lets `for` loops\nknow when to stop — they catch it silently.\n",{"id":1502,"difficulty":212,"q":1503,"a":1504},"iter-next-builtins","What do the built-in iter() and next() functions do?","`iter(obj)` calls `obj.__iter__()` to get an iterator; `next(it)` calls\n`it.__next__()` to advance it. `next()` accepts an optional **default** that\nis returned instead of raising `StopIteration` when the iterator is\nexhausted — handy for safe peeking.\n\n```python\nit = iter(\"ab\")\nnext(it)            # 'a'\nnext(it)            # 'b'\nnext(it, \"done\")    # 'done'  — default instead of StopIteration\n\n# iter() also has a two-arg sentinel form:\n# iter(callable, sentinel) calls until it returns sentinel\n```\n\nUse the default argument whenever you want to drain or sample an iterator\nwithout wrapping `next()` in a `try`\u002F`except StopIteration`.\n",{"id":1506,"difficulty":162,"q":1507,"a":1508},"custom-iterator-class","How do you build a custom iterator class?","Implement `__iter__` (return `self`) and `__next__` (return the next value\nor raise `StopIteration`). The instance holds its own state between calls.\n\n```python\nclass Countdown:\n    def __init__(self, start):\n        self.n = start\n    def __iter__(self):\n        return self\n    def __next__(self):\n        if self.n \u003C= 0:\n            raise StopIteration\n        self.n -= 1\n        return self.n + 1\n\nlist(Countdown(3))     # [3, 2, 1]\n```\n\nThis works, but for most cases a **generator function** (using `yield`) is\nfar less boilerplate — it builds the `__iter__`\u002F`__next__`\u002F`StopIteration`\nmachinery for you. Reach for a class only when you need extra methods or\nexplicit state.\n",{"id":1510,"difficulty":150,"q":1511,"a":1512},"for-under-the-hood","How does a for loop work under the hood?","A `for` loop is sugar over the iterator protocol. Python calls `iter()` on\nthe iterable once to get an iterator, then repeatedly calls `next()` on it,\nbinding each result to the loop variable, until `StopIteration` is raised —\nwhich it catches to end the loop.\n\n```python\nfor x in [1, 2, 3]:\n    print(x)\n\n# is roughly equivalent to:\n_it = iter([1, 2, 3])\nwhile True:\n    try:\n        x = next(_it)\n    except StopIteration:\n        break\n    print(x)\n```\n\nThis is why any object implementing the protocol \"just works\" in a `for`\nloop, comprehension, or `*`-unpacking. The `StopIteration` is the hidden\nhandshake that terminates the loop.\n",{"id":1514,"difficulty":162,"q":1515,"a":1516},"iterator-exhaustion","Why can you only iterate an iterator once?","An iterator is **single-use \u002F exhaustible**: once `__next__` has walked to\nthe end and raised `StopIteration`, it stays exhausted — there is no reset.\nRe-iterating yields nothing. This trips people up with generators and\n`zip`\u002F`map` objects.\n\n```python\nit = iter([1, 2, 3])\nlist(it)     # [1, 2, 3]\nlist(it)     # []  — already exhausted!\n\ngen = (x for x in range(3))\nsum(gen)     # 3\nsum(gen)     # 0  — the generator is spent\n```\n\nA **list** (an iterable, not an iterator) can be looped many times because\neach loop calls `iter()` to get a *fresh* iterator. If you need to reuse an\nexhaustible result, materialize it into a list first.\n",{"description":148},"Python interview questions on iterables vs iterators, the iterator protocol, __iter__ and __next__, StopIteration, building custom iterators, and how for loops work under the hood.","python\u002Fiteration\u002Fiterators","Iterators & the Iterator Protocol","MDIPCY-D1tLVqU5RVp-SI3ZbPEO4RflTJJO17wGSu24",{"id":1523,"title":1524,"body":1525,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1529,"navigation":153,"order":13,"path":1530,"questions":1531,"related":181,"seo":1552,"seoDescription":1553,"stem":1554,"subtopic":1555,"topic":81,"topicSlug":83,"updated":186,"__hash__":1556},"qa\u002Fpython\u002Fmodules\u002Fimports.md","Imports",{"type":145,"value":1526,"toc":1527},[],{"title":148,"searchDepth":29,"depth":29,"links":1528},[],{},"\u002Fpython\u002Fmodules\u002Fimports",[1532,1536,1540,1544,1548],{"id":1533,"difficulty":212,"q":1534,"a":1535},"module-vs-package","What is the difference between a module and a package?","A **module** is a single `.py` file — a namespace of functions, classes, and\nvariables you can `import`. A **package** is a **directory of modules**; it\ngroups related modules under one dotted namespace (`mypkg.utils`).\n\nHistorically a package needed an `__init__.py` file to be recognized; that file\nruns when the package is first imported and can expose a curated public API.\nSince Python 3.3, a directory without `__init__.py` can still be a **namespace\npackage**, but a regular package with `__init__.py` is the common, explicit choice.\n\n```python\n# file layout\n# mypkg\u002F\n#   __init__.py      \u003C- makes it a regular package\n#   utils.py         \u003C- a module inside the package\n\nimport mypkg.utils          # import a module from a package\nfrom mypkg import utils     # same module, bound as `utils`\n```\n\nWhy it matters: modules are the **unit of code reuse**, packages are the **unit of\norganization** — both are objects at runtime (`module.__file__`, `package.__path__`).\n",{"id":1537,"difficulty":162,"q":1538,"a":1539},"absolute-vs-relative-imports","What is the difference between absolute and relative imports?","An **absolute import** spells out the full path from a top-level package\n(`from mypkg.utils import helper`). A **relative import** uses leading dots to\nnavigate relative to the **current module's package** — one dot for the current\npackage, two for the parent.\n\n```python\n# inside mypkg\u002Fsub\u002Fthing.py\nfrom mypkg.utils import helper   # absolute — explicit, unambiguous\nfrom ..utils import helper       # relative — '..' = mypkg, then .utils\nfrom . import sibling            # relative — same package\n```\n\nRelative imports only work **inside a package** and only when the module is run as\npart of that package — running the file directly with `python thing.py` breaks them\n(`ImportError: attempted relative import with no known parent package`).\n\nRule of thumb: PEP 8 **prefers absolute imports** for clarity; reach for relative\nimports inside large packages to avoid repeating a long package prefix.\n",{"id":1541,"difficulty":212,"q":1542,"a":1543},"name-main","What does `if __name__ == \"__main__\"` do?","Every module has a `__name__` variable. When a file is **run directly**, Python\nsets its `__name__` to the string `\"__main__\"`. When the same file is **imported**,\n`__name__` is set to the **module's name** instead. The guard therefore runs code\n**only when the file is executed as a script**, not when it's imported.\n\n```python\ndef main():\n    print(\"running as a program\")\n\nif __name__ == \"__main__\":   # True only via `python myfile.py`\n    main()                   # skipped when `import myfile`\n```\n\nThis lets a file double as both an importable library and a runnable program: tests\nand other modules can import its functions without triggering the script logic.\n\nRule of thumb: put **executable entry-point code** under the guard so importing the\nmodule stays free of side effects.\n",{"id":1545,"difficulty":162,"q":1546,"a":1547},"sys-path-resolution","How does Python find the module you import?","On `import x`, Python searches a list of **finders** and, for file-based modules,\nwalks **`sys.path`** — a list of directories — in order, using the **first match**.\n`sys.path` is built from the script's directory (or cwd), the `PYTHONPATH`\nenvironment variable, and the installation's standard library \u002F `site-packages`.\n\n```python\nimport sys\nprint(sys.path)        # ['', '\u002Fusr\u002Flib\u002Fpython3.12', '...\u002Fsite-packages', ...]\n# '' (or the script dir) is searched first — local files can SHADOW stdlib!\n\n# a file named random.py in your folder will hide the real `random` module\n```\n\nBuilt-in modules and frozen modules are found before `sys.path` is consulted, which\nis why you can't shadow `sys` itself.\n\nWhy it matters: a local file named like a stdlib module (`queue.py`, `email.py`)\nsilently **shadows** the real one — a classic, confusing import bug.\n",{"id":1549,"difficulty":150,"q":1550,"a":1551},"import-caching-circular","What is `sys.modules` and how does it relate to circular imports?","`sys.modules` is a **cache** mapping module names to already-imported module\nobjects. The **first** import of a module executes its code top to bottom and stores\nthe result there; every later `import` of the same name just returns the cached\nobject — so module code runs **once** per interpreter session.\n\nA **circular import** is when module A imports B while B imports A. Because the\nimporting module is added to `sys.modules` **before** its body finishes running, the\nsecond import gets a **partially-initialized** module — names defined later in the\nfile aren't there yet.\n\n```python\n# a.py\nimport b                 # starts importing b...\ndef helper(): ...\n\n# b.py\nimport a                 # a is in sys.modules but only HALF-defined\nprint(a.helper)          # AttributeError — helper isn't bound yet\n```\n\nFixes: **move the import inside the function** that needs it (deferred until call\ntime), restructure to remove the cycle, or import the module object rather than\nnames from it. Rule of thumb: circular imports usually signal that two modules\nshould share a third.\n",{"description":148},"Python interview questions on the import system — modules vs packages, absolute vs relative imports, __main__, sys.path module resolution, import caching, and circular imports.","python\u002Fmodules\u002Fimports","The Import System","jtJSf_YAtyPZXIz_7UFkHQ6ZwMS6wa_500IBOxpfjag",{"id":1558,"title":1559,"body":1560,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1564,"navigation":153,"order":29,"path":1565,"questions":1566,"related":181,"seo":1587,"seoDescription":1588,"stem":1589,"subtopic":1590,"topic":81,"topicSlug":83,"updated":186,"__hash__":1591},"qa\u002Fpython\u002Fmodules\u002Fpackages.md","Packages",{"type":145,"value":1561,"toc":1562},[],{"title":148,"searchDepth":29,"depth":29,"links":1563},[],{},"\u002Fpython\u002Fmodules\u002Fpackages",[1567,1571,1575,1579,1583],{"id":1568,"difficulty":212,"q":1569,"a":1570},"package-vs-module","What is the difference between a module and a package, and what does __init__.py do?","A **module** is a single `.py` file. A **package** is a **directory** of modules\nthat you import as a unit, traditionally marked by an **`__init__.py`** file. That\nfile runs when the package is first imported, so it's where you can set the\npackage's public API or do setup; it's often empty, which is fine.\n\n```python\n# myapp\u002F                 \u003C- package\n#   __init__.py          \u003C- marks it a package, runs on import\n#   db.py                \u003C- module\n#   utils\u002F               \u003C- subpackage\n#       __init__.py\n#       text.py\n\nimport myapp.db                 # import a module from the package\nfrom myapp.utils.text import slug\n```\n\nA common use of `__init__.py` is to **re-export** so callers can write\n`from myapp import db` cleanly. Modules organize code into files; packages organize\nmodules into a namespace tree.\n",{"id":1572,"difficulty":162,"q":1573,"a":1574},"python-m-main","What do `python -m` and __main__.py do?","**`python -m pkg.module`** runs a module **as a script** while still locating it via\nthe import system (so relative imports and the package context work). When you point\n`-m` at a **package**, Python runs that package's **`__main__.py`** — that's how a\npackage becomes executable, like `python -m http.server`.\n\n```bash\npython -m http.server 8000     # runs http.server's __main__.py\npython -m myapp                # runs myapp\u002F__main__.py\npython -m pytest               # run an installed tool as a module\n```\n\n```python\n# myapp\u002F__main__.py\nfrom myapp.cli import main\nmain()\n```\n\nUse `-m` to run installed\u002Fpackaged code by its import name rather than a file path,\nwhich avoids the `sys.path` surprises you get from running a file directly.\n",{"id":1576,"difficulty":162,"q":1577,"a":1578},"dunder-all","What does __all__ control?","`__all__` is a list of names that defines a module's (or package's) **public API for\nwildcard imports** — `from module import *` imports exactly those names. Without it,\n`import *` grabs every name not starting with an underscore.\n\n```python\n# mymath.py\n__all__ = [\"add\", \"PI\"]      # only these are exported by *\n\ndef add(a, b): return a + b\ndef _helper(): ...           # private anyway\nPI = 3.14159\n\n# elsewhere\nfrom mymath import *         # gets add and PI only\n```\n\nIt does **not** prevent explicit imports (`from mymath import _helper` still works)\n— it only curates `*` and documents intent. Define `__all__` to keep `import *`\nclean and to signal what's officially public.\n",{"id":1580,"difficulty":150,"q":1581,"a":1582},"namespace-packages","What is a namespace package?","A **namespace package** (PEP 420) is a package with **no `__init__.py`** whose\ncontents can be **split across multiple directories** on `sys.path`. Python merges\nthose directories into one logical package — useful for plugins where different\ndistributions contribute to a shared top-level namespace.\n\n```python\n# path1\u002Facme\u002Ffoo.py\n# path2\u002Facme\u002Fbar.py     (no __init__.py in either acme\u002F)\n# with both paths on sys.path:\nimport acme.foo\nimport acme.bar          # both resolve under the merged 'acme' namespace\n```\n\nSince Python 3.3, a directory **without** `__init__.py` can still be importable as a\nnamespace package. For an ordinary single-location package you usually still want a\nregular package (with `__init__.py`); reserve namespace packages for splitting a\nnamespace across separately-installed parts.\n",{"id":1584,"difficulty":162,"q":1585,"a":1586},"script-vs-import","What is the difference between running a file and importing it?","When you **run** a file (`python foo.py`), Python sets its `__name__` to\n**`\"__main__\"`**. When you **import** it, `__name__` is the **module's name**. The\n`if __name__ == \"__main__\":` guard uses this so a file can act as both a runnable\nscript and an importable library — the guarded code runs only on direct execution.\n\n```python\n# greet.py\ndef hello(name): return f\"Hi {name}\"\n\nif __name__ == \"__main__\":   # runs only via `python greet.py`\n    print(hello(\"world\"))    # NOT run when `import greet`\n```\n\nWithout the guard, your script's top-level side effects (running, printing, parsing\nargs) would fire **every time the module is imported**. Always put script entry\npoints behind the `__main__` guard.\n",{"description":148},"Python interview questions on packages vs modules, the role of __init__.py, python -m and __main__.py, __all__, namespace packages, and running a script versus importing it.","python\u002Fmodules\u002Fpackages","Packages & __main__","UrCSSzH8UD9IVaDM_gcmPtivyFD05XPsmRvHMSFzpps",{"id":1593,"title":1594,"body":1595,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1599,"navigation":153,"order":11,"path":1600,"questions":1601,"related":181,"seo":1626,"seoDescription":1627,"stem":1628,"subtopic":1629,"topic":81,"topicSlug":83,"updated":186,"__hash__":1630},"qa\u002Fpython\u002Fmodules\u002Fvirtual-environments.md","Virtual Environments",{"type":145,"value":1596,"toc":1597},[],{"title":148,"searchDepth":29,"depth":29,"links":1598},[],{},"\u002Fpython\u002Fmodules\u002Fvirtual-environments",[1602,1606,1610,1614,1618,1622],{"id":1603,"difficulty":212,"q":1604,"a":1605},"what-is-venv","What is a virtual environment and why does isolation matter?","A **virtual environment** is a self-contained directory with its own Python\ninterpreter and its own `site-packages`, so each project gets an **isolated** set of\ndependencies. Isolation matters because two projects often need **different\nversions** of the same package — without venvs, installing for one breaks the other,\nand installing globally can even break system tools.\n\n```bash\n# Project A needs Django 3, Project B needs Django 5.\n# Separate venvs let both coexist without conflict.\npython -m venv .venv        # creates an isolated environment\n```\n\nA venv keeps dependencies **per project** and out of the global interpreter, making\nbuilds reproducible and avoiding \"works on my machine\" version clashes. Use one for\nevery project.\n",{"id":1607,"difficulty":212,"q":1608,"a":1609},"create-activate","How do you create and activate a virtual environment?","Create one with the standard-library **`venv`** module, then **activate** it so the\nshell uses the venv's `python` and `pip`. The activation command differs by OS;\n**`deactivate`** returns to the global interpreter.\n\n```bash\npython -m venv .venv            # create (folder named .venv)\n\nsource .venv\u002Fbin\u002Factivate       # activate on macOS \u002F Linux\n.venv\\Scripts\\activate          # activate on Windows\n\nwhich python                    # -> ...\u002F.venv\u002Fbin\u002Fpython while active\ndeactivate                      # leave the venv\n```\n\nOnce active, `pip install` puts packages **inside** the venv only. Add `.venv\u002F` to\n`.gitignore` — you commit the dependency list, not the environment itself.\n",{"id":1611,"difficulty":212,"q":1612,"a":1613},"pip-requirements","How do you install packages and what is requirements.txt?","**`pip install`** fetches packages from PyPI into the active environment. A\n**`requirements.txt`** lists a project's dependencies (often with pinned versions)\nso anyone can recreate the same environment with one command.\n\n```bash\npip install requests            # install latest\npip install \"requests==2.31.0\"  # install a specific version\n\npip install -r requirements.txt # install everything listed in the file\n```\n\n```text\n# requirements.txt\nrequests==2.31.0\nrich>=13.0\n```\n\nCommitting `requirements.txt` makes installs **reproducible** across machines and\nCI. Install into an **activated venv**, never globally with `sudo pip`.\n",{"id":1615,"difficulty":162,"q":1616,"a":1617},"pip-freeze","What does pip freeze do?","`pip freeze` prints every installed package with its **exact pinned version** in\n`requirements.txt` format, so you can capture the current environment. Redirect it\nto a file to snapshot dependencies.\n\n```bash\npip freeze                          # list installed pkgs == versions\npip freeze > requirements.txt       # snapshot the current environment\npip list                            # similar, but human-readable table\n```\n\nA caveat: `pip freeze` records **everything installed, including transitive\ndependencies**, which can make the file noisy. Many teams instead hand-curate\ndirect dependencies (or use a lock-file tool) and keep `pip freeze` for capturing a\nknown-good full snapshot.\n",{"id":1619,"difficulty":162,"q":1620,"a":1621},"editable-install","What is an editable install (`pip install -e`)?","An **editable install** links your project into the environment **in place** instead\nof copying it, so edits to the source take effect **immediately** without\nreinstalling. It's the standard way to work on a package you're developing locally.\n\n```bash\npip install -e .                # install the current project, editable\npip install -e \".[dev]\"         # editable + optional 'dev' extras\n```\n\nBecause the install points at your working tree, changing the code updates the\nimported package right away — no rebuild needed. Use `-e` for **your own package\nunder development**, and a normal `pip install` for third-party dependencies.\n",{"id":1623,"difficulty":162,"q":1624,"a":1625},"pyproject-toml","What is pyproject.toml?","`pyproject.toml` is the **modern, standardized** config file (PEP 518\u002F621) for a\nPython project — it declares the **build system**, project **metadata**, and\n**dependencies** in one place, replacing the older `setup.py`\u002F`setup.cfg` split. Most\nmodern tools (build, pip, linters, formatters) read it.\n\n```toml\n[project]\nname = \"myapp\"\nversion = \"0.1.0\"\ndependencies = [\"requests>=2.31\", \"rich\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n```\n\nWith dependencies declared here, `pip install .` (or `-e .`) reads them directly, so\na separate `requirements.txt` becomes optional. It's the recommended starting point\nfor any new packaged project.\n",{"description":148},"Python interview questions on virtual environments and pip — what a venv is and why isolation matters, creating and activating one, requirements.txt, pip freeze, editable installs, and pyproject.toml.","python\u002Fmodules\u002Fvirtual-environments","Virtual Environments & pip","J9rM7tWYUq73AiW9CqGkW0bpSbKn9qvu5gGCVDpRVAw",{"id":1632,"title":1633,"body":1634,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":1638,"navigation":153,"order":64,"path":1639,"questions":1640,"related":181,"seo":1661,"seoDescription":1662,"stem":1663,"subtopic":1664,"topic":54,"topicSlug":56,"updated":186,"__hash__":1665},"qa\u002Fpython\u002Foop\u002Fabc-protocols.md","Abc Protocols",{"type":145,"value":1635,"toc":1636},[],{"title":148,"searchDepth":29,"depth":29,"links":1637},[],{},"\u002Fpython\u002Foop\u002Fabc-protocols",[1641,1645,1649,1653,1657],{"id":1642,"difficulty":162,"q":1643,"a":1644},"what-is-abc","What is an abstract base class and what does @abstractmethod do?","An **abstract base class (ABC)** is a class that **can't be instantiated\ndirectly** and defines a set of methods subclasses **must implement**. You build\none by subclassing `abc.ABC` and decorating required methods with\n**`@abstractmethod`**. Python refuses to instantiate any subclass that leaves an\nabstract method unimplemented.\n\n```python\nfrom abc import ABC, abstractmethod\n\nclass Shape(ABC):\n    @abstractmethod\n    def area(self):            # subclasses MUST implement this\n        ...\n\nShape()                        # TypeError — can't instantiate abstract class\n\nclass Circle(Shape):\n    def __init__(self, r):\n        self.r = r\n    def area(self):            # concrete implementation\n        return 3.14159 * self.r ** 2\n\nCircle(2).area()               # 12.566... — works\n```\n\n`@abstractmethod` turns the \"must implement\" contract into an **enforced,\nload-time check** rather than a runtime `NotImplementedError` later. It defines\nan interface that subclasses are required to fulfill.\n",{"id":1646,"difficulty":162,"q":1647,"a":1648},"why-use-abcs","Why use ABCs instead of just regular classes?","ABCs **enforce an interface** — they guarantee at instantiation time that\nsubclasses provide the required methods, catching mistakes **early** instead of\nblowing up deep in your code. They also document intent clearly and integrate\nwith `isinstance` checks, so callers can verify capabilities.\n\n```python\nfrom abc import ABC, abstractmethod\n\nclass Storage(ABC):\n    @abstractmethod\n    def save(self, key, value): ...\n    @abstractmethod\n    def load(self, key): ...\n\nclass FileStorage(Storage):\n    def save(self, key, value): ...\n    # forgot load() ...\n\nFileStorage()      # TypeError — flags the missing method immediately\nisinstance(FileStorage, type) and issubclass(FileStorage, Storage)  # True\n```\n\nUse an ABC when you have a **family of classes that must share a contract** and\nyou want that contract enforced. For looser, optional interfaces, plain duck\ntyping or a `Protocol` may fit better.\n",{"id":1650,"difficulty":150,"q":1651,"a":1652},"collections-abc","What is collections.abc and how is it useful?","`collections.abc` provides the **standard ABCs for Python's container\nprotocols** — `Iterable`, `Iterator`, `Sequence`, `Mapping`, `Hashable`, and\nmore. You use them two ways: as a **base class** to inherit mixin methods, and\nin **`isinstance` checks** to test whether an object supports a protocol.\n\n```python\nfrom collections.abc import Iterable, Sequence, Mapping\n\nisinstance([1, 2], Iterable)   # True\nisinstance(\"abc\", Sequence)    # True\nisinstance({}, Mapping)        # True\n\nclass MyList(Sequence):        # inherit the protocol's mixin methods\n    def __init__(self, data):\n        self._data = data\n    def __getitem__(self, i):\n        return self._data[i]\n    def __len__(self):\n        return len(self._data)\n    # get __contains__, __iter__, __reversed__, index, count for FREE\n```\n\nSubclassing `Sequence` and implementing just `__getitem__` + `__len__` gives you\na fully functional sequence. Prefer these standard ABCs over inventing your own\nfor container-like types.\n",{"id":1654,"difficulty":162,"q":1655,"a":1656},"duck-typing-vs-abcs","What is the difference between duck typing and ABCs?","**Duck typing** is \"if it walks like a duck and quacks like a duck, it's a\nduck\" — Python doesn't check the type, it just **tries the operation** and works\nif the needed methods exist. **ABCs add an explicit, enforced contract** on top,\nletting you assert membership and catch missing methods up front.\n\n```python\n# Duck typing — no declared interface, just call and hope:\ndef make_it_quack(thing):\n    thing.quack()          # works for ANYTHING with a quack() method\n\nclass Dog:\n    def quack(self): return \"woof-quack\"\nmake_it_quack(Dog())       # works — Dog \"is\" a duck here\n\n# ABC — explicit, checkable contract:\nfrom abc import ABC, abstractmethod\nclass Duck(ABC):\n    @abstractmethod\n    def quack(self): ...\n```\n\nDuck typing is flexible and Pythonic but defers errors to runtime; ABCs trade\nsome flexibility for **early enforcement and clear interfaces**. Use duck typing\nfor loose coupling, ABCs when you want guarantees.\n",{"id":1658,"difficulty":150,"q":1659,"a":1660},"typing-protocol","What is typing.Protocol and how does it relate to duck typing?","`typing.Protocol` enables **structural typing** (\"static duck typing\"): a class\nsatisfies a protocol simply by **having the right methods\u002Fattributes** — no\nexplicit inheritance needed. Type checkers verify the structure **statically**,\ngiving you duck typing's flexibility **with** static safety. Add\n**`@runtime_checkable`** to also allow `isinstance` checks at runtime.\n\n```python\nfrom typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass Drawable(Protocol):\n    def draw(self) -> str: ...     # required shape, not inheritance\n\nclass Button:                      # does NOT inherit Drawable\n    def draw(self) -> str:\n        return \"[Button]\"\n\ndef render(item: Drawable) -> str: # type checker accepts Button\n    return item.draw()\n\nrender(Button())                   # works — structural match\nisinstance(Button(), Drawable)     # True — thanks to @runtime_checkable\n```\n\nUnlike ABCs, classes don't register or subclass anything — matching the\n**structure is enough**. Note `@runtime_checkable` only checks method\n**names\u002Fexistence**, not signatures. Use Protocols for flexible, statically\nverified interfaces; use ABCs when you need shared implementation or explicit\nregistration.\n",{"description":148},"Python interview questions on abstract base classes and @abstractmethod, why use ABCs, collections.abc, duck typing vs ABCs, and typing.Protocol for structural\u002Fstatic duck typing with runtime_checkable.","python\u002Foop\u002Fabc-protocols","Abstract Base Classes & Protocols","ND9wrQMm980ybmmy5dud6WKKaNzHF-LWIzfyLRcQJ5Q",{"id":1667,"title":1668,"body":1669,"description":148,"difficulty":212,"extension":151,"framework":10,"frameworkSlug":8,"meta":1673,"navigation":153,"order":29,"path":1674,"questions":1675,"related":181,"seo":1700,"seoDescription":1701,"stem":1702,"subtopic":1703,"topic":54,"topicSlug":56,"updated":186,"__hash__":1704},"qa\u002Fpython\u002Foop\u002Fclasses.md","Classes",{"type":145,"value":1670,"toc":1671},[],{"title":148,"searchDepth":29,"depth":29,"links":1672},[],{},"\u002Fpython\u002Foop\u002Fclasses",[1676,1680,1684,1688,1692,1696],{"id":1677,"difficulty":212,"q":1678,"a":1679},"class-vs-instance","What is the difference between a class and an instance?","A **class** is a **blueprint** — it defines the attributes and methods that\nobjects of that type will have. An **instance** is a concrete **object built\nfrom that blueprint**, with its own state. You write the class once and create\nmany instances from it.\n\n```python\nclass Dog:                 # the blueprint\n    def __init__(self, name):\n        self.name = name   # per-instance state\n\nrex = Dog(\"Rex\")           # an instance\nfido = Dog(\"Fido\")         # a separate instance\nrex.name                   # 'Rex'\nfido.name                  # 'Fido' — independent state\ntype(rex)                  # \u003Cclass '__main__.Dog'>\n```\n\nThe class itself is also an object (of type `type`). Each instance carries its\nown data but shares the class's methods. Think of the class as the cookie\ncutter and the instances as the cookies.\n",{"id":1681,"difficulty":162,"q":1682,"a":1683},"init-vs-new","What is the difference between __init__ and __new__?","`__new__` **creates and returns the new object**; `__init__` **initializes**\nthat already-created object. `__new__` runs first and is a static method that\nreceives the **class**; `__init__` runs second and receives the **instance**\n(`self`) it should configure. `__init__` must return `None`.\n\n```python\nclass Widget:\n    def __new__(cls, *args):\n        print(\"__new__ — allocating\")\n        return super().__new__(cls)   # returns the instance\n    def __init__(self, size):\n        print(\"__init__ — configuring\")\n        self.size = size              # sets state on self\n\nw = Widget(10)   # prints __new__ then __init__\n```\n\nYou rarely override `__new__` — it's mainly for **immutable types** (subclassing\n`int`\u002F`str`\u002F`tuple`), singletons, or metaclass tricks. For everyday classes,\njust use `__init__`.\n",{"id":1685,"difficulty":212,"q":1686,"a":1687},"what-is-self","What is `self` in Python?","`self` is the **instance the method was called on** — it's how a method accesses\nthat object's attributes and other methods. It isn't a keyword; it's just the\nconventional **name of the first parameter**. Python passes the instance\nautomatically when you call `obj.method()`.\n\n```python\nclass Counter:\n    def __init__(self):\n        self.count = 0\n    def increment(self):\n        self.count += 1        # self refers to this instance\n\nc = Counter()\nc.increment()                  # Python passes c as self\nCounter.increment(c)           # exactly equivalent — self is explicit here\n```\n\nSo `c.increment()` is sugar for `Counter.increment(c)`. The explicitness is\ndeliberate — Python makes the instance visible rather than hiding it like\n`this` in other languages.\n",{"id":1689,"difficulty":162,"q":1690,"a":1691},"instance-vs-class-attributes","What is the difference between instance and class attributes?","A **class attribute** is defined in the class body and **shared by every\ninstance**; an **instance attribute** is set on `self` (usually in `__init__`)\nand is **unique per object**. Attribute lookup checks the instance first, then\nfalls back to the class.\n\n```python\nclass Dog:\n    species = \"Canis familiaris\"   # class attribute — shared\n    def __init__(self, name):\n        self.name = name           # instance attribute — per-object\n\na, b = Dog(\"Rex\"), Dog(\"Fido\")\na.species                # 'Canis familiaris' (from the class)\na.name, b.name           # 'Rex', 'Fido' (independent)\na.species = \"wolf\"       # creates an instance attr that SHADOWS the class one\nb.species                # still 'Canis familiaris'\n```\n\nWatch the classic trap: a **mutable** class attribute (like `[]`) is shared and\nwill leak state between instances — initialize mutable state in `__init__`.\n",{"id":1693,"difficulty":162,"q":1694,"a":1695},"repr-vs-str","What is the difference between __repr__ and __str__?","`__repr__` is the **unambiguous, developer-facing** representation — ideally\nsomething that could recreate the object — and is what you see in the REPL and\nin containers. `__str__` is the **readable, user-facing** string used by\n`print()` and `str()`. If `__str__` is missing, Python **falls back to\n`__repr__`**.\n\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n    def __repr__(self):\n        return f\"Point(x={self.x}, y={self.y})\"   # for developers\n    def __str__(self):\n        return f\"({self.x}, {self.y})\"            # for users\n\np = Point(1, 2)\nprint(p)     # (1, 2)        — __str__\nrepr(p)      # 'Point(x=1, y=2)'  — __repr__\n[p]          # [Point(x=1, y=2)]  — containers use __repr__\n```\n\nRule of thumb: always define `__repr__`; add `__str__` only when you need a\ndistinct friendly form.\n",{"id":1697,"difficulty":162,"q":1698,"a":1699},"object-creation-flow","What happens, step by step, when you call ClassName()?","Calling `ClassName(args)` invokes the class's metaclass `__call__`, which\norchestrates two steps: it calls **`__new__(cls, args)`** to allocate the\nobject, then — if `__new__` returned an instance of `cls` — calls\n**`__init__(instance, args)`** to initialize it, and finally returns the\ninstance.\n\n```python\nclass Demo:\n    def __new__(cls, *a):\n        print(\"1. __new__\")\n        return super().__new__(cls)\n    def __init__(self, *a):\n        print(\"2. __init__\")\n\nd = Demo()       # prints: 1. __new__  then  2. __init__\n# 3. d is now bound to the fully initialized instance\n```\n\nKey subtlety: if `__new__` returns an object that is **not** an instance of the\nclass, `__init__` is **skipped entirely**. For normal classes you never see\nthis machinery — you just call the class and get back a ready object.\n",{"description":148},"Python interview questions on classes vs instances, __init__ vs __new__, the self parameter, instance vs class attributes, __repr__ vs __str__, and the object creation flow.","python\u002Foop\u002Fclasses","Classes, Instances & __init__","ouDK0ESDWwDR7Ofpyzn1mO8dgBG_yOsNKMgJuy_V_Uo",{"id":1706,"title":1707,"body":1708,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1712,"navigation":153,"order":55,"path":1713,"questions":1714,"related":181,"seo":1738,"seoDescription":1739,"stem":1740,"subtopic":1741,"topic":54,"topicSlug":56,"updated":186,"__hash__":1742},"qa\u002Fpython\u002Foop\u002Fdataclasses-slots.md","Dataclasses Slots",{"type":145,"value":1709,"toc":1710},[],{"title":148,"searchDepth":29,"depth":29,"links":1711},[],{},"\u002Fpython\u002Foop\u002Fdataclasses-slots",[1715,1719,1722,1726,1730,1734],{"id":1716,"difficulty":212,"q":1717,"a":1718},"what-dataclass-generates","What does the @dataclass decorator generate for you?","`@dataclass` auto-generates the **boilerplate dunder methods** from the\nclass's annotated fields — primarily **`__init__`**, **`__repr__`**, and\n**`__eq__`**. You declare fields with type hints (and optional defaults) and\nskip writing the constructor by hand.\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass Point:\n    x: int\n    y: int = 0           # field with a default\n\np = Point(1, 2)\np                        # Point(x=1, y=2)        — generated __repr__\np == Point(1, 2)         # True                   — generated __eq__\n# __init__(self, x, y=0) was generated automatically\n```\n\nYou can opt into more (`order=True` for comparison operators, `frozen=True` for\nimmutability). It removes the repetitive plumbing while leaving normal methods\nup to you.\n",{"id":925,"difficulty":162,"q":1720,"a":1721},"What does frozen=True do on a dataclass?","`@dataclass(frozen=True)` makes instances **immutable** — assigning to a field\nafter creation raises **`FrozenInstanceError`**. As a bonus, frozen dataclasses\nget a generated **`__hash__`**, so they're usable as **dict keys and set\nmembers**.\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 — immutable\n{p: \"origin\"}      # hashable — works as a dict key\n{Point(1, 2), Point(1, 2)}   # one element — equal and hashable\n```\n\nFrozen dataclasses are the concise, modern way to define **immutable value\nobjects**. Use them whenever an object represents a value that shouldn't change\nafter creation.\n",{"id":1723,"difficulty":150,"q":1724,"a":1725},"default-factory","Why must mutable dataclass defaults use field(default_factory=...)?","A bare mutable default (`tags: list = []`) would be **shared across all\ninstances** — the same default-argument trap as in regular functions — so\ndataclasses **forbid it and raise `ValueError`**. Instead, pass\n**`field(default_factory=list)`**, a zero-arg callable that creates a **fresh\nobject per instance**.\n\n```python\nfrom dataclasses import dataclass, field\n\n@dataclass\nclass Article:\n    title: str\n    tags: list = field(default_factory=list)   # new list each instance\n    # tags: list = []   # would raise ValueError at class-definition time\n\na, b = Article(\"A\"), Article(\"B\")\na.tags.append(\"python\")\nb.tags             # []  — independent, no shared state\n```\n\nUse `default_factory` for any mutable default (`list`, `dict`, `set`) or for a\nvalue that must be computed at construction time. Plain immutable defaults\n(numbers, strings, tuples) are fine inline.\n",{"id":1727,"difficulty":150,"q":1728,"a":1729},"slots","What is __slots__ and what does it buy you?","`__slots__` declares a **fixed set of allowed attributes**, so instances store\nthem in a compact array instead of a per-instance **`__dict__`**. This **saves\nsignificant memory** (no dict per object) and gives **faster attribute access**.\nThe trade-off: you **can't add new attributes** not listed in slots.\n\n```python\nclass Point:\n    __slots__ = (\"x\", \"y\")     # no per-instance __dict__\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\np = Point(1, 2)\np.x               # 2 — fast access\np.z = 3           # AttributeError — 'z' not in __slots__\n# p.__dict__      # also AttributeError — there is no dict\n\nfrom dataclasses import dataclass\n@dataclass(slots=True)        # dataclasses can generate slots (3.10+)\nclass Fast:\n    x: int\n    y: int\n```\n\nReach for `__slots__` when you create **huge numbers of small objects** and\nmemory matters. For ordinary classes the flexibility of `__dict__` is usually\nworth more than the savings.\n",{"id":1731,"difficulty":162,"q":1732,"a":1733},"dataclass-vs-namedtuple","When would you choose a dataclass over a namedtuple or NamedTuple?","A **`namedtuple`**\u002F**`typing.NamedTuple`** is an **immutable tuple** subclass —\nlightweight, hashable, iterable, and index-accessible, but values can't change.\nA **`@dataclass`** is a regular class — **mutable by default**, supports methods,\ninheritance, and `default_factory`, but isn't a tuple (no unpacking\u002Findexing\nunless you add it).\n\n```python\nfrom typing import NamedTuple\nfrom dataclasses import dataclass\n\nclass PointNT(NamedTuple):     # immutable, tuple-like\n    x: int\n    y: int\nx, y = PointNT(1, 2)           # unpacks like a tuple\n\n@dataclass\nclass PointDC:                 # mutable, full class\n    x: int\n    y: int\n    def shift(self, dx):       # can hold real methods\n        self.x += dx\n\nPointNT(1, 2)[0]   # 1 — index access (tuple)\nPointDC(1, 2).shift(5)         # mutate in place\n```\n\nChoose a **NamedTuple** for small immutable records and tuple semantics; choose a\n**dataclass** when you need mutability, methods, or richer field control. Use\n`frozen=True` dataclasses for immutable value objects that still want class\nfeatures.\n",{"id":1735,"difficulty":162,"q":1736,"a":1737},"post-init","What is __post_init__ used for?","`__post_init__` runs **automatically right after the generated `__init__`** —\nit's the hook for **validation and derived fields** that depend on the\nconstructor arguments. It pairs with `field(init=False)` to declare attributes\nthat aren't constructor parameters but are computed afterward.\n\n```python\nfrom dataclasses import dataclass, field\n\n@dataclass\nclass Rectangle:\n    width: float\n    height: float\n    area: float = field(init=False)     # not a constructor arg\n\n# filled in after __init__:\n    def __post_init__(self):\n        if self.width \u003C= 0 or self.height \u003C= 0:\n            raise ValueError(\"dimensions must be positive\")   # validation\n        self.area = self.width * self.height                  # derived field\n\nr = Rectangle(3, 4)\nr.area            # 12 — computed in __post_init__\nRectangle(-1, 4)  # ValueError\n```\n\nUse `__post_init__` whenever a dataclass needs logic the auto-generated\n`__init__` can't express — validation, normalization, or fields derived from\nothers.\n",{"description":148},"Python interview questions on @dataclass — generated methods, frozen=True, field(default_factory=...) for mutable defaults, __slots__ for memory and speed, dataclass vs namedtuple vs NamedTuple, and __post_init__.","python\u002Foop\u002Fdataclasses-slots","Dataclasses & __slots__","rbgsaK-ivBYw25DXEMFBiwSKrF17EhRciQ-ctqZKl34",{"id":1744,"title":1745,"body":1746,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":1750,"navigation":153,"order":11,"path":1751,"questions":1752,"related":181,"seo":1781,"seoDescription":1782,"stem":1783,"subtopic":1784,"topic":54,"topicSlug":56,"updated":186,"__hash__":1785},"qa\u002Fpython\u002Foop\u002Fdunder-methods.md","Dunder Methods",{"type":145,"value":1747,"toc":1748},[],{"title":148,"searchDepth":29,"depth":29,"links":1749},[],{},"\u002Fpython\u002Foop\u002Fdunder-methods",[1753,1757,1761,1765,1769,1773,1777],{"id":1754,"difficulty":162,"q":1755,"a":1756},"what-are-dunder-methods","What are dunder (magic) methods?","**Dunder methods** (\"double underscore\", e.g. `__init__`, `__len__`) are special\nhooks Python calls **implicitly** to make your objects work with built-in syntax\nand functions. They are how Python implements **operator overloading and\nprotocols** — `len(x)` calls `x.__len__()`, `x + y` calls `x.__add__(y)`,\n`x[0]` calls `x.__getitem__(0)`.\n\n```python\nclass Box:\n    def __init__(self, items):\n        self.items = items\n    def __len__(self):\n        return len(self.items)   # makes len(box) work\n\nb = Box([1, 2, 3])\nlen(b)        # 3 — Python calls b.__len__()\n```\n\nThey're sometimes called \"magic methods\" but there's no magic — they're a\ndocumented protocol. Implementing the right dunders makes your objects feel like\nnative Python types.\n",{"id":1758,"difficulty":162,"q":1759,"a":1760},"repr-str-dunder","How do __repr__ and __str__ control how an object prints?","`__repr__` defines the **unambiguous developer representation** (REPL output,\ncontainers, `repr()`); `__str__` defines the **human-readable** form (`print()`,\n`str()`). When `__str__` is absent, Python **falls back to `__repr__`** — so\n`__repr__` is the one to always implement.\n\n```python\nclass Temperature:\n    def __init__(self, c):\n        self.c = c\n    def __repr__(self):\n        return f\"Temperature({self.c})\"     # eval-friendly\n    def __str__(self):\n        return f\"{self.c}°C\"                 # friendly\n\nt = Temperature(20)\nstr(t)    # '20°C'\nrepr(t)   # 'Temperature(20)'\n```\n\nA good `__repr__` ideally lets `eval(repr(obj))` reconstruct the object. Always\ndefine `__repr__`; add `__str__` only when a separate user-facing string helps.\n",{"id":1762,"difficulty":150,"q":1763,"a":1764},"eq-and-hash","How do __eq__ and __hash__ work together?","`__eq__` defines **value equality** (`==`); `__hash__` returns the integer used\nby **dicts and sets**. They have a **contract**: if `a == b` then\n`hash(a) == hash(b)`. Defining `__eq__` **alone** sets `__hash__ = None`, making\ninstances **unhashable** — so define both, derived from the **same immutable\nfields**.\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))    # same fields as __eq__\n\n{Point(1, 2), Point(1, 2)}   # one element — treated as equal\n```\n\nOnly hash on fields that never change after creation. If you break the contract,\nobjects you store in a set\u002Fdict become unfindable.\n",{"id":1766,"difficulty":150,"q":1767,"a":1768},"operator-overloading","How do you overload operators like + and *?","Implement the matching **arithmetic dunder**: `__add__` for `+`, `__sub__` for\n`-`, `__mul__` for `*`, `__lt__` for `\u003C`, and so on. Python calls them when the\noperator is used. Return a **new object** (or `NotImplemented` if you can't\nhandle the other operand, so Python can try the reflected method like\n`__radd__`).\n\n```python\nclass Vector:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n    def __add__(self, other):\n        return Vector(self.x + other.x, self.y + other.y)   # v1 + v2\n    def __mul__(self, k):\n        return Vector(self.x * k, self.y * k)               # v * 3\n    def __repr__(self):\n        return f\"Vector({self.x}, {self.y})\"\n\nVector(1, 2) + Vector(3, 4)   # Vector(4, 6)\nVector(1, 2) * 3              # Vector(3, 6)\n```\n\nReturn `NotImplemented` (not raise) for unsupported types so Python can fall\nback gracefully. Don't overload operators in surprising ways — keep semantics\nintuitive.\n",{"id":1770,"difficulty":150,"q":1771,"a":1772},"sequence-protocol","How do __len__ and __getitem__ make an object behave like a sequence?","Implementing `__len__` makes `len(obj)` work, and `__getitem__` makes\n**indexing, slicing, and iteration** work — Python can iterate by calling\n`__getitem__(0)`, `__getitem__(1)`, ... until `IndexError`, even without an\n`__iter__`. Together they form the **sequence protocol**.\n\n```python\nclass Playlist:\n    def __init__(self, songs):\n        self.songs = songs\n    def __len__(self):\n        return len(self.songs)         # len(pl)\n    def __getitem__(self, i):\n        return self.songs[i]           # pl[0], pl[1:3], and iteration\n\npl = Playlist([\"a\", \"b\", \"c\"])\nlen(pl)          # 3\npl[1]            # 'b'\nfor s in pl:     # iterates via __getitem__\n    print(s)\n```\n\nAdd `__contains__` for `in` and `__setitem__` for assignment. This duck-typed\nprotocol is why custom containers feel like lists.\n",{"id":1774,"difficulty":162,"q":1775,"a":1776},"call-dunder","What does __call__ do?","`__call__` makes an **instance itself callable** like a function — `obj()`\ninvokes `obj.__call__()`. This lets objects **carry state between calls**, which\na plain function can't do as cleanly. It's the basis of function objects and\nmany decorators.\n\n```python\nclass Multiplier:\n    def __init__(self, factor):\n        self.factor = factor       # remembered state\n    def __call__(self, x):\n        return x * self.factor     # obj(x)\n\ndouble = Multiplier(2)\ndouble(5)         # 10 — calling the instance\ncallable(double)  # True\n```\n\nUse it for **stateful callables** — configurable functions, accumulators, or\nclass-based decorators. If you just need behavior without state, a closure or\nplain function is simpler.\n",{"id":1778,"difficulty":162,"q":1779,"a":1780},"total-ordering","What does functools.total_ordering do?","`@functools.total_ordering` is a class decorator that **fills in the missing\ncomparison operators** from the ones you define. You provide `__eq__` plus **one**\nof `__lt__`, `__le__`, `__gt__`, `__ge__`, and it generates the rest — saving you\nfrom writing all six.\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass Version:\n    def __init__(self, n):\n        self.n = n\n    def __eq__(self, other):\n        return self.n == other.n\n    def __lt__(self, other):\n        return self.n \u003C other.n     # only this + __eq__ needed\n\nVersion(1) \u003C Version(2)    # True\nVersion(2) >= Version(1)   # True — generated by total_ordering\nsorted([Version(3), Version(1), Version(2)])  # works\n```\n\nThe trade-off is a small performance cost from derived comparisons; for\nhot-path code, define all operators explicitly. Otherwise it's a clean way to\nget full ordering with minimal boilerplate.\n",{"description":148},"Python interview questions on dunder\u002Fmagic methods — __repr__\u002F__str__, __eq__ and __hash__, operator overloading with __add__, the sequence protocol (__len__\u002F__getitem__), __call__, and functools.total_ordering.","python\u002Foop\u002Fdunder-methods","Dunder \u002F Magic Methods","ncqznv9sUn2_SLebvm5cJygSM3_p-P2icZsoSaPIlxo",{"id":1787,"title":1788,"body":1789,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":1793,"navigation":153,"order":13,"path":1794,"questions":1795,"related":181,"seo":1816,"seoDescription":1817,"stem":1818,"subtopic":1819,"topic":54,"topicSlug":56,"updated":186,"__hash__":1820},"qa\u002Fpython\u002Foop\u002Finheritance.md","Inheritance",{"type":145,"value":1790,"toc":1791},[],{"title":148,"searchDepth":29,"depth":29,"links":1792},[],{},"\u002Fpython\u002Foop\u002Finheritance",[1796,1800,1804,1808,1812],{"id":1797,"difficulty":212,"q":1798,"a":1799},"single-vs-multiple","What is the difference between single and multiple inheritance?","**Single inheritance** means a class derives from exactly one parent; **multiple\ninheritance** means it lists more than one base class. Python supports both —\nmultiple inheritance is what lets you compose behaviour from several sources at\nonce.\n\n```python\nclass Animal:\n    def eat(self): print(\"eating\")\n\nclass Dog(Animal):          # single inheritance\n    def bark(self): print(\"woof\")\n\nclass Swimmer: ...\nclass Flyer: ...\nclass Duck(Animal, Swimmer, Flyer):  # multiple inheritance\n    pass\n```\n\nMultiple inheritance is powerful but can create ambiguity about *which* parent's\nmethod wins — that ambiguity is resolved by the **MRO**. Rule of thumb: prefer\nsingle inheritance plus small **mixins** over deep, wide hierarchies.\n",{"id":1801,"difficulty":150,"q":1802,"a":1803},"what-is-mro","What is the MRO and how is it computed?","The **MRO (Method Resolution Order)** is the linear, ordered list of classes\nPython searches when looking up an attribute or method on an instance. CPython\nbuilds it with the **C3 linearization** algorithm, which guarantees a consistent\norder that respects each class's own order of bases and never places a parent\nbefore its child.\n\n```python\nclass A: ...\nclass B(A): ...\nclass C(A): ...\nclass D(B, C): ...\n\nD.__mro__            # (D, B, C, A, object)\nD.mro()             # same, as a list\n```\n\nC3 produces a single deterministic order (or raises `TypeError` if no consistent\norder exists). Attribute lookup walks this list left to right and stops at the\nfirst match. Rule of thumb: read `Cls.__mro__` whenever multiple inheritance\nsurprises you — it tells you exactly who wins.\n",{"id":1805,"difficulty":150,"q":1806,"a":1807},"how-super-works","How does super() actually work?","`super()` does **not** simply call \"the parent class\" — it calls the **next class\nin the instance's MRO**, starting after the current class. That cooperative\nbehaviour is what makes multiple inheritance work correctly: each class delegates\nto whatever comes next, regardless of the static hierarchy.\n\n```python\nclass A:\n    def greet(self): print(\"A\")\nclass B(A):\n    def greet(self): print(\"B\"); super().greet()\nclass C(A):\n    def greet(self): print(\"C\"); super().greet()\nclass D(B, C):\n    def greet(self): print(\"D\"); super().greet()\n\nD().greet()   # D, B, C, A  — follows D.__mro__, not B's parent\n```\n\nNote `super().greet()` inside `B` calls `C`, not `A`, because the MRO of a `D`\ninstance puts `C` after `B`. Rule of thumb: in a cooperative hierarchy every\noverride should call `super()` so the whole chain runs exactly once.\n",{"id":1809,"difficulty":150,"q":1810,"a":1811},"diamond-problem","What is the diamond problem and how does Python solve it?","The **diamond problem** arises when two classes (`B`, `C`) inherit from a common\nbase (`A`), and a fourth class (`D`) inherits from both. The question is: when\n`D` calls an inherited method, is `A`'s code run **once or twice**? Naive\nlanguages run it twice; Python's **C3 MRO** guarantees `A` appears **exactly\nonce**, so cooperative `super()` calls run it a single time.\n\n```python\nclass A:\n    def __init__(self): print(\"A\"); \nclass B(A):\n    def __init__(self): print(\"B\"); super().__init__()\nclass C(A):\n    def __init__(self): print(\"C\"); super().__init__()\nclass D(B, C):\n    def __init__(self): print(\"D\"); super().__init__()\n\nD()   # D, B, C, A  — A's __init__ runs ONCE\n```\n\nThe MRO `(D, B, C, A, object)` linearizes the diamond into a clean chain. Rule of\nthumb: the diamond is only safe when every class in it uses `super()` consistently\n— mixing `super()` with hard-coded `Base.__init__(self)` calls breaks the\nguarantee.\n",{"id":1813,"difficulty":162,"q":1814,"a":1815},"mixins-and-abc","What are mixins and abstract base classes?","A **mixin** is a small class that provides a focused slice of behaviour meant to be\ncombined into other classes via multiple inheritance — it isn't useful on its own\nand usually has no `__init__`. An **abstract base class (ABC)**, from the `abc`\nmodule, defines an interface with `@abstractmethod`s and **cannot be instantiated**\nuntil every abstract method is overridden.\n\n```python\nfrom abc import ABC, abstractmethod\n\nclass JsonMixin:                 # mixin: adds one capability\n    def to_json(self): import json; return json.dumps(self.__dict__)\n\nclass Shape(ABC):                # abstract base: defines a contract\n    @abstractmethod\n    def area(self): ...\n\nclass Circle(Shape, JsonMixin):\n    def __init__(self, r): self.r = r\n    def area(self): return 3.14159 * self.r ** 2\n\nShape()    # TypeError: can't instantiate abstract class\n```\n\nUse mixins to share reusable behaviour and ABCs to enforce that subclasses\nimplement a required interface (`isinstance` checks also work against ABCs). Rule\nof thumb: mixins say \"you *can* do this\", ABCs say \"you *must* do this\".\n",{"description":148},"Python interview questions on single vs multiple inheritance, the MRO and C3 linearization, super(), the diamond problem, mixins, and abstract base classes.","python\u002Foop\u002Finheritance","Inheritance & the MRO","lr7N1CknUWOM5mCsCVypmlwzPFk9qinVKOEqgWAED8Y",{"id":1822,"title":1823,"body":1824,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1828,"navigation":153,"order":46,"path":1829,"questions":1830,"related":181,"seo":1851,"seoDescription":1852,"stem":1853,"subtopic":1854,"topic":54,"topicSlug":56,"updated":186,"__hash__":1855},"qa\u002Fpython\u002Foop\u002Fmethods-properties.md","Methods Properties",{"type":145,"value":1825,"toc":1826},[],{"title":148,"searchDepth":29,"depth":29,"links":1827},[],{},"\u002Fpython\u002Foop\u002Fmethods-properties",[1831,1835,1839,1843,1847],{"id":1832,"difficulty":162,"q":1833,"a":1834},"instance-class-static-methods","What is the difference between an instance method, @classmethod, and @staticmethod?","An **instance method** takes `self` and operates on a specific object. A\n**`@classmethod`** takes `cls` (the class, not an instance) and works on\n**class-level state** or builds instances. A **`@staticmethod`** takes\n**neither** — it's a plain function namespaced inside the class, with no access\nto instance or class state.\n\n```python\nclass Pizza:\n    base_price = 10\n    def __init__(self, toppings):\n        self.toppings = toppings\n    def total(self):                    # instance method — uses self\n        return self.base_price + len(self.toppings)\n    @classmethod\n    def margherita(cls):                # classmethod — uses cls\n        return cls([\"mozzarella\", \"basil\"])\n    @staticmethod\n    def is_valid_topping(name):         # staticmethod — no self\u002Fcls\n        return name.isalpha()\n\nPizza.is_valid_topping(\"ham\")   # True — no instance needed\nPizza.margherita().total()      # 12\n```\n\nUse an instance method for per-object behavior, a classmethod for\nclass-aware logic (e.g. alternative constructors), and a staticmethod for a\nrelated helper that happens to live on the class.\n",{"id":1836,"difficulty":162,"q":1837,"a":1838},"classmethod-constructor","How do you use a classmethod as an alternative constructor?","A classmethod receives `cls`, so it can **build and return a new instance** from\na different input format — giving you multiple named constructors beyond\n`__init__`. Using `cls` (not the hard-coded class name) means **subclasses get\nthe right type** automatically.\n\n```python\nclass Date:\n    def __init__(self, year, month, day):\n        self.year, self.month, self.day = year, month, day\n    @classmethod\n    def from_string(cls, text):\n        y, m, d = map(int, text.split(\"-\"))\n        return cls(y, m, d)             # returns the (sub)class instance\n    @classmethod\n    def today(cls):\n        import datetime\n        t = datetime.date.today()\n        return cls(t.year, t.month, t.day)\n\nDate.from_string(\"2026-06-18\")   # alternative constructor\nDate.today()\n```\n\nThis is the idiomatic Python pattern (`dict.fromkeys`, `datetime.fromtimestamp`)\nfor \"make one of these, but from X\" — clearer than overloading `__init__` with\nflags.\n",{"id":1840,"difficulty":162,"q":1841,"a":1842},"property-getter-setter","How does @property work with a getter and setter?","`@property` turns a method into a **managed attribute** — you access it like\n`obj.x` (no parentheses), but a method runs behind the scenes. The matching\n`@x.setter` runs on assignment, letting you add **validation** without changing\nthe public interface.\n\n```python\nclass Account:\n    def __init__(self, balance):\n        self._balance = balance         # \"private\" backing field\n    @property\n    def balance(self):                  # getter — runs on read\n        return self._balance\n    @balance.setter\n    def balance(self, value):           # setter — runs on write\n        if value \u003C 0:\n            raise ValueError(\"balance cannot be negative\")\n        self._balance = value\n\nacc = Account(100)\nacc.balance          # 100 — looks like an attribute, calls the getter\nacc.balance = 50     # calls the setter (validated)\nacc.balance = -1     # ValueError\n```\n\nThe convention is a `_name` backing attribute behind a public property. Omit the\nsetter to make the property read-only.\n",{"id":1844,"difficulty":162,"q":1845,"a":1846},"why-properties-over-getters","Why do properties beat Java-style getter\u002Fsetter methods?","In Python you **start with a plain attribute** and only convert it to a\n`@property` later **if** you need validation or computation — **without changing\nthe call sites**. So you avoid the Java habit of pre-emptively writing\n`getX()`\u002F`setX()` \"just in case\". The public API stays `obj.x`.\n\n```python\n# Start simple — public attribute:\nclass Circle:\n    def __init__(self, radius):\n        self.radius = radius\n\nc = Circle(5)\nc.radius            # direct access — no ceremony\n\n# Later, add validation transparently — callers don't change:\nclass Circle:\n    def __init__(self, radius):\n        self.radius = radius\n    @property\n    def radius(self):\n        return self._radius\n    @radius.setter\n    def radius(self, value):\n        if value \u003C= 0:\n            raise ValueError(\"radius must be positive\")\n        self._radius = value\n```\n\nThis is the \"uniform access principle\": callers can't tell whether `obj.x` is a\nstored value or computed. Don't write getters\u002Fsetters upfront — reach for a\nproperty only when behavior is needed.\n",{"id":1848,"difficulty":212,"q":1849,"a":1850},"computed-readonly-properties","How do you create a computed or read-only property?","A property with **only a getter** (no setter) is **read-only** — assigning to it\nraises `AttributeError`. A **computed property** derives its value from other\nattributes on each access, so it stays in sync automatically rather than being\nstored.\n\n```python\nclass Rectangle:\n    def __init__(self, width, height):\n        self.width = width\n        self.height = height\n    @property\n    def area(self):                 # computed — derived on each read\n        return self.width * self.height\n\nr = Rectangle(3, 4)\nr.area           # 12 — computed\nr.width = 5\nr.area           # 20 — automatically reflects the change\nr.area = 99      # AttributeError — read-only (no setter)\n```\n\nFor an expensive computation you only want to run once, use\n`functools.cached_property` instead, which caches the result on first access.\nUse a plain read-only property for cheap derived values.\n",{"description":148},"Python interview questions on instance vs classmethod vs staticmethod, classmethods as alternative constructors, the @property getter\u002Fsetter, why properties beat Java-style accessors, and computed\u002Fread-only properties.","python\u002Foop\u002Fmethods-properties","Methods & Properties","EZTMeTHDuvwapkor3EW7aAeDQlUzCDXBSwLIpBJ6zww",{"id":1857,"title":1858,"body":1859,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1863,"navigation":153,"order":11,"path":1864,"questions":1865,"related":181,"seo":1886,"seoDescription":1887,"stem":1888,"subtopic":1889,"topic":117,"topicSlug":119,"updated":186,"__hash__":1890},"qa\u002Fpython\u002Fstdlib\u002Fdatetime.md","Datetime",{"type":145,"value":1860,"toc":1861},[],{"title":148,"searchDepth":29,"depth":29,"links":1862},[],{},"\u002Fpython\u002Fstdlib\u002Fdatetime",[1866,1870,1874,1878,1882],{"id":1867,"difficulty":212,"q":1868,"a":1869},"datetime-date-time","What is the difference between datetime, date, and time?","The `datetime` module provides three core types. **`date`** holds a calendar date\n(year, month, day) with **no time**. **`time`** holds a time of day (hour, minute,\nsecond, microsecond) with **no date**. **`datetime`** combines **both** into a\nsingle timestamp.\n\n```python\nfrom datetime import date, time, datetime\n\nd = date(2026, 6, 18)               # just the date\nt = time(14, 30, 0)                 # just the time of day\ndt = datetime(2026, 6, 18, 14, 30)  # date + time together\n\ndt.date()                           # -> date(2026, 6, 18)\ndt.time()                           # -> time(14, 30)\ndate.today()                        # current date\n```\n\nUse `date` for things like birthdays or due dates where time is irrelevant, `time`\nfor a recurring clock time, and `datetime` for actual events\u002Ftimestamps. Most\nreal-world work uses `datetime`.\n",{"id":1871,"difficulty":150,"q":1872,"a":1873},"naive-aware-zoneinfo","What is the difference between naive and timezone-aware datetimes?","A **naive** datetime has **no timezone info** (`tzinfo` is `None`) — it's just wall\nclock numbers with no reference point, so it's ambiguous. An **aware** datetime\ncarries a `tzinfo`, pinning it to an actual instant. Use the stdlib **`zoneinfo`**\nmodule (Python 3.9+) to attach real IANA timezones.\n\n```python\nfrom datetime import datetime\nfrom zoneinfo import ZoneInfo\n\nnaive = datetime(2026, 6, 18, 14, 30)          # ambiguous — no tz\naware = datetime(2026, 6, 18, 14, 30,\n                 tzinfo=ZoneInfo(\"America\u002FNew_York\"))\n\nutc = aware.astimezone(ZoneInfo(\"UTC\"))        # convert between zones\n```\n\nYou **can't compare or subtract** a naive and an aware datetime — it raises\n`TypeError`. Best practice: store and compute in **UTC-aware** datetimes, and convert\nto local zones only for display.\n",{"id":1875,"difficulty":162,"q":1876,"a":1877},"strftime-strptime","What is the difference between strftime and strptime?","They are inverses. **`strftime`** (\"string **f**rom time\") **formats** a datetime\n**into** a string using format codes. **`strptime`** (\"string **p**arse time\")\n**parses** a string **into** a datetime using a matching format.\n\n```python\nfrom datetime import datetime\n\ndt = datetime(2026, 6, 18, 14, 30)\ns = dt.strftime(\"%Y-%m-%d %H:%M\")       # datetime -> \"2026-06-18 14:30\"\n\nparsed = datetime.strptime(\"2026-06-18 14:30\",\n                           \"%Y-%m-%d %H:%M\")  # str -> datetime\n```\n\nCommon codes: `%Y` (4-digit year), `%m` (month), `%d` (day), `%H` (24-hour),\n`%M` (minute), `%S` (second). To remember: **f** = format (out), **p** = parse (in).\nFor standard ISO strings, `datetime.fromisoformat()` \u002F `.isoformat()` are simpler.\n",{"id":1879,"difficulty":162,"q":1880,"a":1881},"timedelta-arithmetic","How does timedelta arithmetic work?","A **`timedelta`** represents a **duration** — a difference between two points in time.\nSubtracting two datetimes yields a `timedelta`; adding a `timedelta` to a datetime\nshifts it. A `timedelta` stores days, seconds, and microseconds.\n\n```python\nfrom datetime import datetime, timedelta\n\nstart = datetime(2026, 6, 18, 9, 0)\nend   = datetime(2026, 6, 18, 17, 30)\n\nworked = end - start              # timedelta(seconds=30600)\nworked.total_seconds()            # 30600.0\nworked.seconds \u002F\u002F 3600            # 8 (hours portion)\n\ntomorrow = start + timedelta(days=1)      # shift forward\nweek_ago = start - timedelta(weeks=1)     # shift back\n```\n\nUse `total_seconds()` to get the whole duration as a number (the `.seconds`\nattribute is only the sub-day part). `timedelta` makes date math safe — it correctly\nrolls over months and years.\n",{"id":1883,"difficulty":150,"q":1884,"a":1885},"now-vs-utcnow","What is the pitfall with datetime.now() vs utcnow()?","The big trap: **both `datetime.now()` and the old `datetime.utcnow()` return *naive*\ndatetimes**. `now()` gives local wall time, `utcnow()` gives the UTC wall time — but\n**neither attaches a tzinfo**, so a `utcnow()` value silently *looks* like local\ntime and corrupts later conversions. `utcnow()` is **deprecated** in modern Python.\n\n```python\nfrom datetime import datetime\nfrom zoneinfo import ZoneInfo\n\ndatetime.now()                       # naive, local time — ambiguous\ndatetime.utcnow()                    # naive, but labelled nothing! (deprecated)\n\n# correct: an AWARE UTC timestamp\nnow_utc = datetime.now(ZoneInfo(\"UTC\"))\nlocal = datetime.now(ZoneInfo(\"America\u002FNew_York\"))\n```\n\nRule of thumb: **always pass a timezone to `now()`** to get an aware datetime, and\navoid `utcnow()` entirely. Store timestamps as UTC-aware and convert for display.\n",{"description":148},"Python interview questions on datetime vs date vs time, naive vs timezone-aware datetimes with zoneinfo, strftime\u002Fstrptime, timedelta arithmetic, and the now() vs utcnow() pitfall.","python\u002Fstdlib\u002Fdatetime","datetime","5GeL0A_Cp3gxfR8_dMsYRrPzndiAo9KQrpPZqTy-QDE",{"id":1892,"title":1893,"body":1894,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1898,"navigation":153,"order":29,"path":1899,"questions":1900,"related":181,"seo":1921,"seoDescription":1922,"stem":1923,"subtopic":1924,"topic":117,"topicSlug":119,"updated":186,"__hash__":1925},"qa\u002Fpython\u002Fstdlib\u002Ffiles-pathlib.md","Files Pathlib",{"type":145,"value":1895,"toc":1896},[],{"title":148,"searchDepth":29,"depth":29,"links":1897},[],{},"\u002Fpython\u002Fstdlib\u002Ffiles-pathlib",[1901,1905,1909,1913,1917],{"id":1902,"difficulty":212,"q":1903,"a":1904},"with-open","Why open files with the `with` statement?","`with open(...)` uses the file as a **context manager**, which **guarantees the file\nis closed** when the block exits — even if an exception is raised. Without it you\nmust remember to call `.close()` manually, and a crash mid-block leaks the handle.\n\n```python\nwith open(\"data.txt\") as f:      # f.close() is automatic\n    contents = f.read()\n# file is closed here, even on error\n\nf = open(\"data.txt\")             # manual style — fragile\ntry:\n    contents = f.read()\nfinally:\n    f.close()\n```\n\nLeaked handles can exhaust OS file descriptors and leave buffered writes unflushed.\nAlways prefer `with` for any resource that needs cleanup (files, locks, sockets).\n",{"id":1906,"difficulty":162,"q":1907,"a":1908},"lazy-iteration","What is the difference between iterating a file and read()\u002Freadlines()?","A file object is its own **iterator**, yielding **one line at a time** and holding\nonly that line in memory. `read()` loads the **entire file** into a single string,\nand `readlines()` loads **all lines into a list** — both can blow up memory on large\nfiles.\n\n```python\nwith open(\"huge.log\") as f:\n    for line in f:               # lazy — one line in memory at a time\n        process(line)\n\nwith open(\"huge.log\") as f:\n    data = f.read()              # whole file as one string\n    lines = f.readlines()        # whole file as a list of strings\n```\n\nIterating is the idiomatic, memory-safe way to process big files line by line.\nReserve `read()`\u002F`readlines()` for small files where you genuinely need the whole\ncontent at once.\n",{"id":1910,"difficulty":162,"q":1911,"a":1912},"text-binary-mode","What is the difference between text and binary mode, and why specify encoding?","**Text mode** (`\"r\"`, the default) decodes bytes into `str` using an **encoding** and\nnormalizes newlines. **Binary mode** (`\"rb\"`\u002F`\"wb\"`) reads and writes raw `bytes`\nwith no decoding — required for images, archives, or any non-text data. In text\nmode you should pass **`encoding=`** explicitly, because the default is\nplatform-dependent.\n\n```python\nwith open(\"notes.txt\", \"r\", encoding=\"utf-8\") as f:\n    text: str = f.read()         # decoded to str\n\nwith open(\"photo.jpg\", \"rb\") as f:\n    raw: bytes = f.read()        # raw bytes, no decoding\n```\n\nRelying on the default encoding is a classic cross-platform bug (UTF-8 on Linux\u002FMac,\noften a legacy codepage on Windows). Always specify `encoding=\"utf-8\"` for text, and\nuse binary mode for everything that isn't text.\n",{"id":1914,"difficulty":162,"q":1915,"a":1916},"pathlib-vs-os-path","What is pathlib.Path and how does it compare to os.path?","**`pathlib.Path`** is the modern, object-oriented way to handle filesystem paths. A\n`Path` is an object with **methods and operators**, whereas the older **`os.path`**\nmodule is a collection of **string-based functions**. Path's `\u002F` operator joins\nsegments cleanly and works across operating systems.\n\n```python\nfrom pathlib import Path\n\np = Path(\"data\") \u002F \"logs\" \u002F \"app.log\"   # join with \u002F\np.exists()\np.suffix                                 # \".log\"\np.stem                                   # \"app\"\np.read_text(encoding=\"utf-8\")            # one-liner read\n\nimport os.path\nold = os.path.join(\"data\", \"logs\", \"app.log\")\nos.path.exists(old)\n```\n\n`pathlib` is generally preferred for new code: it's more readable and bundles\ncommon operations (`read_text`, `mkdir`, `glob`) as methods. Use `os.path` mainly\nwhen working with existing string-based APIs.\n",{"id":1918,"difficulty":162,"q":1919,"a":1920},"globbing-path-methods","How do you find files with globbing using pathlib?","Use **`Path.glob(pattern)`** for matches in one directory and **`Path.rglob(pattern)`**\n(or `glob(\"**\u002F...\")`) to recurse into subdirectories. Both return a **lazy generator**\nof `Path` objects, where `*` matches any characters and `**` matches directories\nrecursively.\n\n```python\nfrom pathlib import Path\n\nroot = Path(\"project\")\nfor py in root.glob(\"*.py\"):         # top level only\n    print(py.name)\n\nfor py in root.rglob(\"*.py\"):        # all subdirectories too\n    print(py)\n\nroot.mkdir(parents=True, exist_ok=True)  # create dirs safely\n[p.name for p in root.iterdir()]         # list directory contents\n```\n\nOther handy `Path` methods: `iterdir()` (list a directory), `is_file()`\u002F`is_dir()`,\n`mkdir()`, `unlink()` (delete), and `with_suffix()`. Globbing returns generators, so\nwrap in `list(...)` if you need a concrete collection.\n",{"description":148},"Python interview questions on open() and the with statement, lazy file iteration, text vs binary mode and encoding, pathlib.Path vs os.path, and globbing.","python\u002Fstdlib\u002Ffiles-pathlib","Files, pathlib & os","NoZp_VMdm_N8l_uZ9iKq2ZIl7xuq4gVgtZ6dt2hEatE",{"id":1927,"title":1928,"body":1929,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1933,"navigation":153,"order":13,"path":1934,"questions":1935,"related":181,"seo":1956,"seoDescription":1957,"stem":1958,"subtopic":1959,"topic":117,"topicSlug":119,"updated":186,"__hash__":1960},"qa\u002Fpython\u002Fstdlib\u002Fregex.md","Regex",{"type":145,"value":1930,"toc":1931},[],{"title":148,"searchDepth":29,"depth":29,"links":1932},[],{},"\u002Fpython\u002Fstdlib\u002Fregex",[1936,1940,1944,1948,1952],{"id":1937,"difficulty":212,"q":1938,"a":1939},"match-search-fullmatch","What is the difference between re.match, re.search, and re.fullmatch?","They differ in **where** the pattern must match. `re.match` anchors at the\n**start** of the string (but not the end). `re.search` scans for the pattern\n**anywhere** in the string. `re.fullmatch` requires the pattern to match the\n**entire** string. All return a `Match` object on success or `None` on failure.\n\n```python\nimport re\nre.match(\"ab\", \"abcd\")      # match — starts with 'ab'\nre.match(\"cd\", \"abcd\")      # None  — not at the start\nre.search(\"cd\", \"abcd\")     # match — found anywhere\nre.fullmatch(\"ab\", \"abcd\")  # None  — must match the whole string\nre.fullmatch(\"abcd\", \"abcd\")# match\n```\n\nA common bug is using `match` expecting whole-string validation — it only\nanchors the start. Rule of thumb: use `search` to find, `fullmatch` to\nvalidate, and `match` only when you specifically mean \"begins with\".\n",{"id":1941,"difficulty":162,"q":1942,"a":1943},"groups-named-groups","How do capture groups and named groups work?","Parentheses `( )` create a **capture group** you retrieve by **number**\n(1-based; group 0 is the whole match). `(?P\u003Cname>...)` creates a **named\ngroup** you retrieve by name — far more readable. `(?:...)` groups **without\ncapturing** when you only need it for grouping\u002Falternation.\n\n```python\nimport re\nm = re.search(r\"(\\d{4})-(\\d{2})\", \"2026-06\")\nm.group(0)   # '2026-06'  — whole match\nm.group(1)   # '2026'     — first group\nm.groups()   # ('2026', '06')\n\nm = re.search(r\"(?P\u003Cyear>\\d{4})-(?P\u003Cmonth>\\d{2})\", \"2026-06\")\nm.group(\"year\")   # '2026'\nm.groupdict()     # {'year': '2026', 'month': '06'}\n```\n\nNamed groups make patterns self-documenting and resilient to reordering.\nRule of thumb: use `(?P\u003Cname>...)` for anything you'll extract, and\n`(?:...)` when grouping is structural only.\n",{"id":1945,"difficulty":162,"q":1946,"a":1947},"re-compile-why","What does re.compile do, and why would you use it?","`re.compile(pattern)` builds a **reusable pattern object** once, then you call\nmethods (`.search`, `.match`, `.findall`, `.sub`) on it. The module-level\nfunctions actually compile internally and **cache** recent patterns, so the\nmain win is **clarity and reuse** — plus a small speedup when a pattern is\nused **many times in a loop**.\n\n```python\nimport re\nDATE = re.compile(r\"(?P\u003Cyear>\\d{4})-(?P\u003Cmonth>\\d{2})\")  # compile once\n\nfor line in lines:\n    m = DATE.search(line)   # reuse the compiled object\n    if m:\n        print(m.group(\"year\"))\n```\n\nIt also lets you attach **flags** (e.g. `re.IGNORECASE`, `re.VERBOSE`) in one\nplace. Rule of thumb: compile patterns used repeatedly or shared across a\nmodule; for one-off use the module functions are fine.\n",{"id":1949,"difficulty":150,"q":1950,"a":1951},"greedy-vs-lazy","What is the difference between greedy and non-greedy matching?","By default quantifiers (`*`, `+`, `?`, `{m,n}`) are **greedy** — they match as\n**much** as possible, then backtrack. Adding a trailing `?` makes them\n**non-greedy** (lazy) — they match as **little** as possible. This matters\nhugely when a delimiter can appear multiple times.\n\n```python\nimport re\ntext = \"\u003Ca>\u003Cb>\"\nre.search(r\"\u003C.*>\", text).group()    # '\u003Ca>\u003Cb>'  — greedy, grabs everything\nre.search(r\"\u003C.*?>\", text).group()   # '\u003Ca>'     — lazy, stops at first '>'\n```\n\nGreedy patterns over-matching is a classic \"regex ate too much\" bug. Rule of\nthumb: when matching content **between delimiters**, reach for the lazy `*?`\n\u002F `+?` (or a negated character class like `[^>]*`).\n",{"id":1953,"difficulty":162,"q":1954,"a":1955},"re-sub-raw-strings","How does re.sub work, and why use raw strings for patterns?","`re.sub(pattern, repl, string)` returns a **new** string with all matches\nreplaced. The replacement can reference captured groups with `\\1` or\n`\\g\u003Cname>`, or be a **function** that receives each `Match` for dynamic\nreplacement. You should write patterns as **raw strings** (`r\"...\"`) so that\nbackslash escapes like `\\d` and `\\b` reach the regex engine instead of being\ninterpreted by Python first.\n\n```python\nimport re\nre.sub(r\"\\s+\", \" \", \"a   b\\tc\")          # 'a b c'  — collapse whitespace\nre.sub(r\"(\\d{4})-(\\d{2})\", r\"\\2\u002F\\1\", \"2026-06\")  # '06\u002F2026' — reorder groups\nre.sub(r\"\\d+\", lambda m: f\"[{m.group()}]\", \"x9\")  # 'x[9]' — function repl\n\n\"\\d\"     # in a normal string this is an invalid escape (warns)\nr\"\\d\"    # raw string — passes \\d straight to the engine\n```\n\nWithout `r\"\"`, `\"\\b\"` becomes a backspace character, not a word boundary —\na subtle, hard-to-spot bug. Rule of thumb: **always** prefix regex patterns\nwith `r`.\n",{"description":148},"Python interview questions on the re module — match vs search vs fullmatch, capture and named groups, re.compile, greedy vs non-greedy, re.sub, and raw strings.","python\u002Fstdlib\u002Fregex","Regular Expressions","tGHa2xgx5-BCvqtnwBZ867ytagB6ZPf257IKaZ2rmW0",{"id":1962,"title":1963,"body":1964,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":1968,"navigation":153,"order":46,"path":1969,"questions":1970,"related":181,"seo":1991,"seoDescription":1992,"stem":1993,"subtopic":1994,"topic":117,"topicSlug":119,"updated":186,"__hash__":1995},"qa\u002Fpython\u002Fstdlib\u002Fserialization.md","Serialization",{"type":145,"value":1965,"toc":1966},[],{"title":148,"searchDepth":29,"depth":29,"links":1967},[],{},"\u002Fpython\u002Fstdlib\u002Fserialization",[1971,1975,1979,1983,1987],{"id":1972,"difficulty":212,"q":1973,"a":1974},"json-dumps-loads","How do json.dumps and json.loads work, and how do Python types map to JSON?","**`json.dumps`** serializes a Python object **to** a JSON string; **`json.loads`**\nparses a JSON string **back** into Python objects. The `dump`\u002F`load` variants\n(no `s`) work with file objects instead. The conversion follows a fixed type\nmapping.\n\n```python\nimport json\n\ndata = {\"name\": \"Ada\", \"age\": 36, \"tags\": [\"math\"], \"active\": True}\ns = json.dumps(data)            # dict -> JSON string\nback = json.loads(s)            # JSON string -> dict\n\nwith open(\"out.json\", \"w\") as f:\n    json.dump(data, f)          # write to file\n```\n\nThe mapping: `dict`->object, `list`\u002F`tuple`->array, `str`->string, `int`\u002F`float`\n->number, `True`\u002F`False`->`true`\u002F`false`, `None`->`null`. Note **tuples become\narrays** (you get a list back), and **dict keys are coerced to strings**. JSON has\nno native date, set, or bytes type.\n",{"id":1976,"difficulty":162,"q":1977,"a":1978},"json-custom-encoding","How do you serialize an object JSON doesn't support by default?","Unsupported types raise `TypeError: ... is not JSON serializable`. The two standard\nfixes are the **`default=` callback** (a function called for any unserializable\nobject, returning a JSON-friendly substitute) or a custom **`JSONEncoder` subclass**\npassed via **`cls=`**.\n\n```python\nimport json\nfrom datetime import datetime\n\ndef encode(obj):\n    if isinstance(obj, datetime):\n        return obj.isoformat()        # turn it into a string\n    raise TypeError(f\"not serializable: {type(obj)}\")\n\njson.dumps({\"when\": datetime.now()}, default=encode)\n\nclass MyEncoder(json.JSONEncoder):    # the class-based alternative\n    def default(self, obj):\n        if isinstance(obj, set):\n            return list(obj)\n        return super().default(obj)\njson.dumps({1, 2, 3}, cls=MyEncoder)\n```\n\nUse `default=` for a quick one-off; subclass `JSONEncoder` when you want reusable\nencoding logic. On the way back, use `object_hook` in `loads` to reconstruct custom\ntypes.\n",{"id":1980,"difficulty":162,"q":1981,"a":1982},"csv-reader-dictreader","What is the difference between csv.reader and csv.DictReader?","Both read CSV files, but the row format differs. **`csv.reader`** yields each row as\na **list of strings** indexed by position. **`csv.DictReader`** treats the first row\nas **headers** and yields each row as a **dict** keyed by column name — far more\nreadable and robust to column reordering.\n\n```python\nimport csv\n\nwith open(\"people.csv\", newline=\"\") as f:\n    for row in csv.reader(f):\n        print(row[0], row[1])         # positional — brittle\n\nwith open(\"people.csv\", newline=\"\") as f:\n    for row in csv.DictReader(f):\n        print(row[\"name\"], row[\"age\"])  # by header — clear\n```\n\nAlways open CSV files with **`newline=\"\"`** to let the `csv` module handle line\nendings correctly. Prefer `DictReader`\u002F`DictWriter` for named-column access; use the\nplain `reader` for headerless or purely positional data.\n",{"id":1984,"difficulty":162,"q":1985,"a":1986},"pickle-vs-json","What is the difference between pickle and json, and what is the security warning?","**`json`** is a **text**, language-independent format for **simple data** (dicts,\nlists, numbers, strings). **`pickle`** is a **binary**, Python-specific format that\ncan serialize **almost any Python object** (custom classes, functions references,\nnested objects). The crucial caveat: **never unpickle untrusted data**.\n\n```python\nimport json, pickle\n\njson.dumps({\"x\": 1})              # \"{\\\"x\\\": 1}\" — readable, portable\npickle.dumps({\"x\": 1})            # b'\\x80\\x04...' — binary, Python-only\n\n# DANGER: unpickling runs arbitrary code embedded in the data\npickle.loads(untrusted_bytes)     # can execute malicious payloads!\n```\n\n`pickle.loads` can **execute arbitrary code** during deserialization, so it's a\nremote-code-execution risk on attacker-controlled input. Rule of thumb: use **json**\nfor config, APIs, and anything crossing a trust boundary; use **pickle** only for\ntrusted, internal Python-to-Python data (e.g. caches you wrote yourself).\n",{"id":1988,"difficulty":162,"q":1989,"a":1990},"serializing-datetimes","How do you serialize datetimes to JSON and back?","JSON has **no date type**, so a `datetime` must be converted to a **string** — the\nISO 8601 format via **`.isoformat()`** is the standard choice because it's\nunambiguous and parseable. On the way out use `default=`; on the way back parse the\nstring with `datetime.fromisoformat()`.\n\n```python\nimport json\nfrom datetime import datetime\n\nevent = {\"name\": \"launch\", \"at\": datetime(2026, 6, 18, 14, 30)}\n\ntext = json.dumps(event, default=lambda o: o.isoformat())\n# '{\"name\": \"launch\", \"at\": \"2026-06-18T14:30:00\"}'\n\nraw = json.loads(text)\nraw[\"at\"] = datetime.fromisoformat(raw[\"at\"])   # back to datetime\n```\n\nJSON can't tell a date-string from an ordinary string, so you must know which fields\nto re-parse (or use `object_hook`). Prefer ISO strings in **UTC** for portability,\nand convert to local time only at display.\n",{"description":148},"Python interview questions on json.dumps\u002Floads and type mapping, custom JSON encoding, csv reader vs DictReader, pickle vs json and pickle security, and serializing datetimes.","python\u002Fstdlib\u002Fserialization","JSON, CSV & pickle","viTtNO-ARfmO9ag13muRGsYBkN9JO5C_NMhcGlPbP1U",{"id":1997,"title":1998,"body":1999,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":2003,"navigation":153,"order":29,"path":2004,"questions":2005,"related":181,"seo":2030,"seoDescription":2031,"stem":2032,"subtopic":2033,"topic":126,"topicSlug":128,"updated":186,"__hash__":2034},"qa\u002Fpython\u002Ftesting\u002Fmocking.md","Mocking",{"type":145,"value":2000,"toc":2001},[],{"title":148,"searchDepth":29,"depth":29,"links":2002},[],{},"\u002Fpython\u002Ftesting\u002Fmocking",[2006,2010,2014,2018,2022,2026],{"id":2007,"difficulty":212,"q":2008,"a":2009},"what-is-mocking","What is mocking and why do you use it?","**Mocking** replaces a real dependency with a **fake stand-in object** that records\nhow it was called and returns whatever you tell it to. You use it to **isolate the\ncode under test** from slow, unreliable, or side-effecting collaborators — network\ncalls, databases, the clock, third-party APIs.\n\n```python\nfrom unittest.mock import Mock\n\nservice = Mock()\nservice.fetch.return_value = {\"id\": 1}    # canned response\n\nresult = service.fetch(\"\u002Fusers\u002F1\")        # no real network call\nresult                                    # {\"id\": 1}\nservice.fetch.assert_called_once()        # verify it happened\n```\n\nMocking makes tests **fast, deterministic, and focused** on your logic rather than\nthe dependency's. Rule of thumb: mock at the **boundaries** of your system (I\u002FO,\nexternal services), not your own pure functions.\n",{"id":2011,"difficulty":162,"q":2012,"a":2013},"mock-vs-magicmock","What is the difference between Mock and MagicMock?","Both auto-create attributes and methods on access. The difference: **`MagicMock`**\nadditionally supports **magic (dunder) methods** — `__len__`, `__iter__`,\n`__getitem__`, `__enter__`\u002F`__exit__`, etc. — so it can stand in for objects used\nwith `len()`, iteration, indexing, or `with`. A plain **`Mock`** raises on dunder\naccess.\n\n```python\nfrom unittest.mock import Mock, MagicMock\n\nm = Mock()\nlen(m)                  # TypeError — Mock has no __len__\n\nmm = MagicMock()\nmm.__len__.return_value = 3\nlen(mm)                 # 3 — magic methods supported\nlist(mm)                # works — __iter__ is mocked too\n```\n\n`patch()` uses `MagicMock` by default, which is why patched objects \"just work\" in\nmost cases. Use `MagicMock` when the dependency relies on protocols\u002Fdunders; `Mock`\nis fine for plain method calls.\n",{"id":2015,"difficulty":150,"q":2016,"a":2017},"patch-where-used","How does unittest.mock.patch work, and what does \"patch where it's used\" mean?","**`patch`** temporarily replaces an object with a mock for the duration of a test,\nas a **decorator** or a **context manager**, restoring the original afterward. The\ncritical rule is **\"patch where it's looked up, not where it's defined\"** — you patch\nthe name in the **module that imports and uses it**.\n\n```python\n# app.py\nfrom time import time\ndef stamp(): return time()\n\n# test.py — patch the reference INSIDE app, not 'time.time'\nfrom unittest.mock import patch\n\n@patch(\"app.time\")                 # where it's USED\ndef test_stamp(mock_time):\n    mock_time.return_value = 123\n    assert stamp() == 123\n\nwith patch(\"app.time\") as mock_time:   # context-manager form\n    mock_time.return_value = 123\n```\n\nPatching `\"time.time\"` here would fail, because `app` already bound its own `time`\nname at import. Always target the **importing module's namespace** — this is the\nsingle most common mocking mistake.\n",{"id":2019,"difficulty":162,"q":2020,"a":2021},"return-value-side-effect","What is the difference between return_value and side_effect?","**`return_value`** sets a **single fixed value** the mock returns on every call.\n**`side_effect`** is more powerful: assign a **function** (called with the same args),\nan **exception** (which gets raised), or an **iterable** (returning a different value\nper successive call).\n\n```python\nfrom unittest.mock import Mock\n\nm = Mock(return_value=42)\nm(); m()                       # 42, 42 — always the same\n\nm.side_effect = [1, 2, 3]      # one per call\nm(); m()                       # 1, then 2\n\nm.side_effect = ValueError(\"boom\")\nm()                            # raises ValueError\n\nm.side_effect = lambda x: x * 2\nm(10)                          # 20 — computed from the arg\n```\n\nUse `return_value` for a constant stub, and `side_effect` to **raise errors**,\n**vary results across calls**, or **compute** based on arguments. If both are set,\n`side_effect` wins (unless it returns the sentinel `DEFAULT`).\n",{"id":2023,"difficulty":162,"q":2024,"a":2025},"call-assertions","How do you assert a mock was called correctly?","Mocks **record every call**, so you verify interactions with the `assert_called*`\nfamily. Check **whether\u002Fhow many times** it was called and **with what arguments**,\nand inspect history via `call_args` \u002F `call_args_list`.\n\n```python\nfrom unittest.mock import Mock, call\n\nm = Mock()\nm(1, 2)\nm(3, key=\"v\")\n\nm.assert_called()                       # at least once\nm.assert_called_once()                  # exactly once -> would FAIL here\nm.assert_called_with(3, key=\"v\")        # the MOST RECENT call\nm.assert_any_call(1, 2)                 # any call matched\nm.assert_has_calls([call(1, 2), call(3, key=\"v\")])\nm.call_count                            # 2\n```\n\nNote `assert_called_with` checks only the **last** call — use `assert_any_call` or\n`assert_has_calls` for earlier ones. Beware typos: a misspelled assertion (e.g.\n`assert_called_once_with` -> `assert_called_onced_with`) silently passes, so spell\nthese carefully.\n",{"id":2027,"difficulty":162,"q":2028,"a":2029},"autospec","What is autospec and why is it useful?","A normal mock accepts **any** attribute access or call signature, so it can hide bugs\n— a test passes even if you call a method that doesn't exist or with wrong arguments.\n**Autospec** (`autospec=True`, or `create_autospec`) builds the mock to **match the\nreal object's API**, so it rejects nonexistent attributes and mismatched signatures.\n\n```python\nfrom unittest.mock import patch, create_autospec\n\nclass Api:\n    def fetch(self, url): ...\n\n@patch(\"app.Api\", autospec=True)\ndef test_it(MockApi):\n    api = MockApi()\n    api.fetch(\"\u002Fx\")          # OK — matches real signature\n    api.fetch()              # TypeError — missing 'url'!\n    api.delete()             # AttributeError — no such method\n```\n\nAutospec makes mocks **stay in sync with the real interface**, catching drift when\nthe real API changes. The tradeoff is a small overhead, but it's strongly\nrecommended for non-trivial dependencies.\n",{"description":148},"Python interview questions on mocking and why it matters, Mock vs MagicMock, unittest.mock.patch and patching where it's used, return_value vs side_effect, call assertions, and autospec.","python\u002Ftesting\u002Fmocking","Mocking & Patching","BuZRYss6gdlr3knpMQwWz6MR4-fuMtldIPIpMqBQI_4",{"id":2036,"title":2037,"body":2038,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":2042,"navigation":153,"order":13,"path":2043,"questions":2044,"related":181,"seo":2069,"seoDescription":2070,"stem":2071,"subtopic":2072,"topic":126,"topicSlug":128,"updated":186,"__hash__":2073},"qa\u002Fpython\u002Ftesting\u002Fpytest.md","Pytest",{"type":145,"value":2039,"toc":2040},[],{"title":148,"searchDepth":29,"depth":29,"links":2041},[],{},"\u002Fpython\u002Ftesting\u002Fpytest",[2045,2049,2053,2057,2061,2065],{"id":2046,"difficulty":212,"q":2047,"a":2048},"pytest-vs-unittest","What is the difference between pytest and unittest?","**`unittest`** is the **standard-library** framework, modeled on xUnit: tests\nare **methods on a `TestCase` subclass** and you use `self.assertEqual`,\n`self.assertTrue`, etc. **`pytest`** is a **third-party** framework that runs\nplain functions using the bare `assert` statement, with rich failure\nintrospection, fixtures, and `parametrize`.\n\n```python\n# unittest\nimport unittest\nclass TestMath(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(1 + 1, 2)\n\n# pytest — just a function and assert\ndef test_add():\n    assert 1 + 1 == 2\n```\n\npytest can also **run existing unittest tests**, so adopting it is low-risk.\nRule of thumb: prefer pytest for new code — less boilerplate and better\noutput — while unittest is fine when you must avoid dependencies.\n",{"id":2050,"difficulty":162,"q":2051,"a":2052},"fixtures-scope","What are pytest fixtures, and what does scope control?","A **fixture** is a function decorated with `@pytest.fixture` that provides\n**setup (and teardown) for tests**. A test requests it simply by naming it as\na **parameter**, and pytest injects the return value. Using `yield` lets code\nafter the `yield` run as **teardown**. The **`scope`** controls how often the\nfixture is created: `function` (default), `class`, `module`, or `session`.\n\n```python\nimport pytest\n\n@pytest.fixture(scope=\"module\")   # created once per module\ndef db():\n    conn = connect()\n    yield conn                    # value handed to tests\n    conn.close()                  # teardown after tests finish\n\ndef test_query(db):               # db injected by name\n    assert db.ping()\n```\n\nWider scopes share expensive resources (DB connections, servers) across many\ntests for speed; narrower scopes give better **isolation**. Rule of thumb:\ndefault to `function` scope and only widen when setup is costly.\n",{"id":2054,"difficulty":162,"q":2055,"a":2056},"parametrize","How does @pytest.mark.parametrize work?","`@pytest.mark.parametrize` runs the **same test function multiple times** with\ndifferent arguments, generating **one separate test case per row**. Each case\npasses or fails independently, so a single bad input doesn't hide the others —\nfar cleaner than a loop inside one test.\n\n```python\nimport pytest\n\n@pytest.mark.parametrize(\"value, expected\", [\n    (2, 4),\n    (3, 9),\n    (4, 16),\n])\ndef test_square(value, expected):\n    assert value ** 2 == expected\n```\n\npytest reports each as `test_square[2-4]`, `test_square[3-9]`, etc., and you\ncan stack decorators to get the **cross product** of inputs. Rule of thumb:\nuse `parametrize` for the same logic across many inputs instead of copy-pasted\ntests or in-test loops.\n",{"id":2058,"difficulty":150,"q":2059,"a":2060},"mocking-monkeypatch","How do you mock with monkeypatch versus unittest.mock?","The built-in **`monkeypatch`** fixture **replaces attributes, dict items, or\nenv vars** for the duration of a test and **auto-restores** them afterward —\nideal for swapping out a function or setting `os.environ`. **`unittest.mock`**\n(`Mock`, `patch`) creates **mock objects** that record calls and let you set\nreturn values or side effects — best when you need to **assert how** a\ndependency was called.\n\n```python\nimport requests, mymodule\n\ndef test_with_monkeypatch(monkeypatch):\n    monkeypatch.setattr(requests, \"get\", lambda url: {\"ok\": True})\n    assert mymodule.fetch() == {\"ok\": True}\n\nfrom unittest.mock import patch\ndef test_with_mock():\n    with patch(\"mymodule.requests.get\") as mock_get:\n        mock_get.return_value = {\"ok\": True}\n        mymodule.fetch()\n        mock_get.assert_called_once()   # verify the interaction\n```\n\nA key gotcha: **patch where the name is looked up** (`mymodule.requests.get`),\nnot where it's defined. Rule of thumb: `monkeypatch` for simple replacements,\n`unittest.mock` when you need to **inspect calls**.\n",{"id":2062,"difficulty":162,"q":2063,"a":2064},"pytest-raises","How do you assert that code raises an exception?","Use the **`pytest.raises`** context manager: the test **passes only if** the\nexpected exception is raised inside the `with` block, and **fails** if no\nexception (or the wrong one) occurs. You can capture the exception via\n`as excinfo` to assert on its message, and use `match=` for a regex check.\n\n```python\nimport pytest\n\ndef test_divide_by_zero():\n    with pytest.raises(ZeroDivisionError):\n        1 \u002F 0\n\ndef test_message():\n    with pytest.raises(ValueError, match=\"invalid\"):\n        int(\"not a number\")   # ValueError: invalid literal...\n    # or: assert \"invalid\" in str(excinfo.value)\n```\n\nIt cleanly replaces a `try\u002Fexcept\u002Fpytest.fail` dance. Rule of thumb: always\nassert on the **specific** exception type (and ideally the message) so the\ntest can't pass for the wrong reason.\n",{"id":2066,"difficulty":162,"q":2067,"a":2068},"conftest","What is conftest.py used for?","**`conftest.py`** is a special pytest file for **shared fixtures and hooks**.\npytest **auto-discovers** it — no import needed — and any fixture defined\nthere is **available to every test** in that directory and its subdirectories.\nIt's the standard place to put fixtures used across multiple test files.\n\n```python\n# tests\u002Fconftest.py\nimport pytest\n\n@pytest.fixture\ndef client():\n    return create_test_client()\n\n# tests\u002Ftest_users.py — no import required\ndef test_login(client):           # 'client' resolved from conftest\n    assert client.login(\"ada\")\n```\n\nYou can also register plugins, define hooks like `pytest_addoption`, and place\na `conftest.py` at multiple levels for **scoped** sharing. Rule of thumb: put\na fixture in `conftest.py` once more than one test file needs it, instead of\nimporting it around.\n",{"description":148},"Python interview questions on pytest — pytest vs unittest, fixtures and scope, parametrize, mocking with monkeypatch and unittest.mock, pytest.raises, and conftest.py.","python\u002Ftesting\u002Fpytest","pytest Essentials","k9bgHlANacbZ5kAs3vq6F-kv1RDc9uTZZ6pHwnijVHI",{"id":2075,"title":2076,"body":2077,"description":148,"difficulty":150,"extension":151,"framework":10,"frameworkSlug":8,"meta":2081,"navigation":153,"order":29,"path":2082,"questions":2083,"related":181,"seo":2108,"seoDescription":2109,"stem":2110,"subtopic":2111,"topic":108,"topicSlug":110,"updated":186,"__hash__":2112},"qa\u002Fpython\u002Ftyping\u002Fgenerics-protocols.md","Generics Protocols",{"type":145,"value":2078,"toc":2079},[],{"title":148,"searchDepth":29,"depth":29,"links":2080},[],{},"\u002Fpython\u002Ftyping\u002Fgenerics-protocols",[2084,2088,2092,2096,2100,2104],{"id":2085,"difficulty":150,"q":2086,"a":2087},"typevar-generic","What are TypeVar and Generic, and how do you write a generic class?","A **`TypeVar`** is a type variable — a placeholder that lets a function or class\nwork with **any type while preserving it** across inputs and outputs. Subclassing\n**`Generic[T]`** turns a class into a generic container parameterized by that\nvariable, so a `Stack[int]` is known to hold and return `int`s.\n\n```python\nfrom typing import TypeVar, Generic\n\nT = TypeVar(\"T\")               # one placeholder type\n\ndef first(items: list[T]) -> T:  # in and out share T\n    return items[0]\n\nclass Stack(Generic[T]):       # generic class\n    def __init__(self) -> None:\n        self._items: list[T] = []\n    def push(self, x: T) -> None:\n        self._items.append(x)\n    def pop(self) -> T:\n        return self._items.pop()\n\ns: Stack[int] = Stack()\ns.push(1)\nn = s.pop()                    # type checker knows n is int\n```\n\nUse a `TypeVar` whenever a relationship between argument and return types must be\ncaptured — `def first(items: list) -> object` loses that, but `list[T] -> T`\nkeeps it. In Python 3.12+ you can write `def first[T](items: list[T]) -> T`\nwithout the explicit `TypeVar`.\n",{"id":2089,"difficulty":150,"q":2090,"a":2091},"bounded-typevar","What is a bounded or constrained TypeVar?","A plain `TypeVar` accepts **any** type. A **bound** (`bound=...`) restricts it to a\ntype **and its subclasses**, while **constraints** (`TypeVar(\"T\", int, str)`)\nrestrict it to a fixed set of specific types. Both let the body safely use the\ncapabilities implied by the bound.\n\n```python\nfrom typing import TypeVar\n\nclass Animal:\n    def speak(self) -> str: ...\n\nA = TypeVar(\"A\", bound=Animal)     # A must be Animal or a subclass\n\ndef loudest(animals: list[A]) -> A:\n    for a in animals:\n        a.speak()                  # OK — bound guarantees this method\n    return animals[0]\n\nNum = TypeVar(\"Num\", int, float)   # constrained: ONLY int or float\ndef double(x: Num) -> Num:\n    return x * 2\n```\n\nReach for a **bound** when \"any subtype of X\" is acceptable and the body needs X's\ninterface; use **constraints** when only a handful of unrelated concrete types\nshould be allowed.\n",{"id":2093,"difficulty":150,"q":2094,"a":2095},"protocol-structural","What is typing.Protocol and how does it enable structural typing?","A **`Protocol`** defines an interface by **shape** rather than inheritance: any\nobject that has the right methods\u002Fattributes is accepted, even if it never\nexplicitly subclasses the protocol. This is **structural typing** (\"duck typing\")\nchecked statically — \"if it walks like a duck.\"\n\n```python\nfrom typing import Protocol\n\nclass SupportsClose(Protocol):\n    def close(self) -> None: ...\n\ndef shutdown(resource: SupportsClose) -> None:\n    resource.close()\n\nclass File:                # never inherits SupportsClose...\n    def close(self) -> None: ...\n\nshutdown(File())           # ...but accepted: it has close()\n```\n\nContrast with **nominal typing** (the usual `class B(A)`), where you must declare the\nrelationship. Protocols decouple the consumer from concrete classes — great for\ntyping third-party objects you can't modify.\n",{"id":2097,"difficulty":162,"q":2098,"a":2099},"runtime-checkable","What does @runtime_checkable do for a Protocol?","By default a `Protocol` exists only for **static** checkers — `isinstance()` against\nit raises `TypeError`. Decorating it with **`@runtime_checkable`** allows\n`isinstance()` \u002F `issubclass()` checks at runtime, but only for the **presence of\nthe named methods**, not their signatures or return types.\n\n```python\nfrom typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass Sized(Protocol):\n    def __len__(self) -> int: ...\n\nisinstance([1, 2, 3], Sized)   # True  — list has __len__\nisinstance(42, Sized)          # False — int has no __len__\n```\n\nIt's a convenience, not a guarantee: the check confirms a method *exists*, not that\nit takes the right arguments. Prefer static checking; use `@runtime_checkable` only\nwhen you genuinely need a runtime branch.\n",{"id":2101,"difficulty":162,"q":2102,"a":2103},"callable-types","How do you type a function passed as an argument?","Use **`Callable[[ArgTypes], ReturnType]`** from `typing` (or the built-in\n`collections.abc.Callable`). The first element is the **list of parameter types**,\nthe second is the **return type**. Use `...` for the parameters when you want to\naccept any signature.\n\n```python\nfrom collections.abc import Callable\n\ndef apply(fn: Callable[[int, int], int], a: int, b: int) -> int:\n    return fn(a, b)\n\napply(lambda x, y: x + y, 2, 3)        # 5\n\nhandler: Callable[..., None]           # any args, returns None\nno_args: Callable[[], str]             # takes nothing, returns str\n```\n\nFor more precise signatures (preserving exact parameters of a wrapped function),\n`ParamSpec` exists, but `Callable[[...], R]` covers the common cases. Type your\ncallbacks so the checker catches mismatched handlers.\n",{"id":2105,"difficulty":150,"q":2106,"a":2107},"covariance-invariance","What is the difference between covariance and invariance in generics?","Variance describes whether `Container[Subtype]` is usable where `Container[Supertype]`\nis expected. **Invariant** (the default, e.g. `list[T]`): `list[int]` is **not** a\n`list[str]` *or* a `list[object]`. **Covariant**: `Tuple[int]` is acceptable as\n`Tuple[object]`. The intuition: **mutable** containers must be invariant for safety;\n**read-only** ones can be covariant.\n\n```python\ndef total(nums: list[float]) -> float: ...\nints: list[int] = [1, 2]\ntotal(ints)        # type ERROR — list is invariant\n\nfrom collections.abc import Sequence\ndef total2(nums: Sequence[float]) -> float: ...\ntotal2(ints)       # OK — Sequence is covariant (read-only)\n```\n\nWhy mutables are invariant: if `list[int]` were a `list[object]`, a function could\nappend a `str` to it, corrupting the original `list[int]`. Rule of thumb: accept\n**`Sequence`\u002F`Iterable`** (covariant, read-only) in parameters to be flexible; reserve\n`list`\u002F`dict` for when you truly need to mutate.\n",{"description":148},"Python interview questions on TypeVar and Generic classes, bounded type variables, typing.Protocol structural typing, Callable types, and covariance vs invariance.","python\u002Ftyping\u002Fgenerics-protocols","Generics & Protocols","tqB3wOAZgFxlWwfX7xtXC5pu1C_ZvRqITJYDLjrlBbc",{"id":2114,"title":2115,"body":2116,"description":148,"difficulty":162,"extension":151,"framework":10,"frameworkSlug":8,"meta":2120,"navigation":153,"order":13,"path":2121,"questions":2122,"related":181,"seo":2143,"seoDescription":2144,"stem":2145,"subtopic":2146,"topic":108,"topicSlug":110,"updated":186,"__hash__":2147},"qa\u002Fpython\u002Ftyping\u002Ftype-hints.md","Type Hints",{"type":145,"value":2117,"toc":2118},[],{"title":148,"searchDepth":29,"depth":29,"links":2119},[],{},"\u002Fpython\u002Ftyping\u002Ftype-hints",[2123,2127,2131,2135,2139],{"id":2124,"difficulty":212,"q":2125,"a":2126},"hints-runtime-enforced","Are type hints enforced at runtime?","**No.** Type hints are **annotations**, not constraints — the interpreter\nstores them (in `__annotations__`) but **never checks them**. You can pass\na `str` where an `int` is annotated and Python runs it happily; enforcement\nis the job of an **external static type checker** like **mypy** or **pyright**.\n\n```python\ndef double(n: int) -> int:\n    return n * 2\n\ndouble(\"ab\")          # runs fine -> \"abab\", no TypeError\ndouble.__annotations__  # {'n': \u003Cclass 'int'>, 'return': \u003Cclass 'int'>}\n```\n\nIf you want runtime validation you opt in explicitly — e.g. **pydantic**,\n`typing.get_type_hints`, or manual `isinstance` checks. Rule of thumb: hints\ndocument and enable tooling; they are **not** a runtime guard.\n",{"id":2128,"difficulty":162,"q":2129,"a":2130},"optional-union","What is the difference between Optional, Union, and the `|` operator?","`Union[A, B]` means \"**A or B**\". `Optional[X]` is just shorthand for\n`Union[X, None]` — a value that may be `X` **or** `None`. It does **not**\nmean \"optional argument\"; it means \"could be None\". Since Python 3.10 you\ncan write unions with the **`|` operator** instead of importing from `typing`.\n\n```python\nfrom typing import Optional, Union\n\ndef find(id: int) -> Optional[str]: ...     # str or None\ndef parse(x: Union[int, str]) -> int: ...   # int or str\n\n# Python 3.10+ equivalent, no imports:\ndef find(id: int) -> str | None: ...\ndef parse(x: int | str) -> int: ...\n```\n\nPrefer the modern `X | None` syntax on 3.10+. Reach for `Optional`\u002F`Union`\nfrom `typing` only when supporting older versions. Rule of thumb: `Optional`\nis about **nullability**, never about whether a parameter has a default.\n",{"id":2132,"difficulty":162,"q":2133,"a":2134},"list-vs-generics","What is the difference between `list` and `List`, and how do generics work?","Both annotate a list, but **`List` comes from `typing`** while **`list`** is\nthe built-in. Since **Python 3.9** the built-in containers are themselves\n**subscriptable** (`list[int]`, `dict[str, int]`), so `typing.List`,\n`typing.Dict`, etc. are **deprecated** — use the lowercase built-ins. A bare\n`list` means \"list of anything\"; the **generic** form pins the element type.\n\n```python\nfrom typing import List          # legacy\nnames: List[str] = []\n\nnames: list[str] = []            # modern (3.9+), preferred\nscores: dict[str, int] = {}\npair: tuple[int, str] = (1, \"a\")\n```\n\nGenerics let a checker verify element access and method calls. Rule of thumb:\non 3.9+ always parameterize the **built-in** (`list[str]`), and only import\nfrom `typing` for things with no built-in equivalent (e.g. `Callable`).\n",{"id":2136,"difficulty":150,"q":2137,"a":2138},"any-vs-object","What is the difference between `typing.Any` and `object`?","Both accept any value, but they are **opposites to a type checker**. `object`\nis the real **base of every class** — you can assign anything to it, but you\ncan only do `object`-level operations on it. `Any` is an **escape hatch**: it\nis compatible with **everything in both directions**, so the checker stops\nchecking — any attribute or call is allowed.\n\n```python\ndef f(x: object) -> None:\n    x.upper()        # type error: object has no 'upper'\n\ndef g(x: Any) -> None:\n    x.upper()        # OK — Any disables checking\n    x + 1            # also OK, no complaints\n```\n\nUse `object` when you genuinely accept anything but want to **keep type\nsafety** (forcing you to narrow with `isinstance` first). Use `Any` only to\ndeliberately **opt out** of checking. Rule of thumb: `Any` is contagious and\nhides bugs — prefer `object` or a precise type.\n",{"id":2140,"difficulty":162,"q":2141,"a":2142},"what-mypy-does","What does mypy do, and how is it different from Protocol-based typing?","**mypy** is a **static type checker**: it reads your annotations and flags\ntype mismatches **before you run the code** — no execution, no runtime cost.\nBy default it checks types **nominally** (by inheritance). `typing.Protocol`\nadds **structural typing** (a.k.a. duck typing): a class matches a Protocol\nif it has the right **methods\u002Fattributes**, even without inheriting from it.\n\n```python\nfrom typing import Protocol\n\nclass Closable(Protocol):\n    def close(self) -> None: ...\n\ndef shutdown(r: Closable) -> None:\n    r.close()\n\nclass File:                 # never imports\u002Finherits Closable\n    def close(self) -> None: ...\n\nshutdown(File())            # OK — File structurally matches\n```\n\nSo mypy verifies correctness, and `Protocol` lets it accept **anything with\nthe right shape** rather than a specific base class. Rule of thumb: use\nProtocols to type \"**anything that behaves like X**\" without forcing a\ncommon base class.\n",{"description":148},"Python interview questions on type hints, Optional and Union, generics with list vs List, typing.Any vs object, mypy, and Protocol structural typing.","python\u002Ftyping\u002Ftype-hints","Type Hints & Annotations","-Huf_PnQ17R6ZD2DTYZJptZVH8wQAKCHGmNxduo3rC4",1781808672507]