[{"data":1,"prerenderedAt":179},["ShallowReactive",2],{"topic-python-iteration":3},{"framework":4,"topic":15,"subtopics":23},{"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",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":11,"slug":20,"stem":21,"__hash__":22},"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",[24,69,104,143],{"id":25,"title":26,"body":27,"description":31,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":36,"navigation":37,"order":13,"path":38,"questions":39,"related":62,"seo":63,"seoDescription":64,"stem":65,"subtopic":66,"topic":19,"topicSlug":20,"updated":67,"__hash__":68},"qa\u002Fpython\u002Fiteration\u002Fgenerators.md","Generators",{"type":28,"value":29,"toc":30},"minimark",[],{"title":31,"searchDepth":32,"depth":32,"links":33},"",2,[],"medium","md",{},true,"\u002Fpython\u002Fiteration\u002Fgenerators",[40,45,49,53,57],{"id":41,"difficulty":42,"q":43,"a":44},"what-is-generator","easy","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":46,"difficulty":34,"q":47,"a":48},"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":50,"difficulty":34,"q":51,"a":52},"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":54,"difficulty":34,"q":55,"a":56},"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":58,"difficulty":59,"q":60,"a":61},"infinite-generators","hard","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",null,{"description":31},"Python interview questions on generators and yield, lazy evaluation, generator expressions vs list comprehensions, yield from, and infinite generators.","python\u002Fiteration\u002Fgenerators","Generators & yield","2026-06-18","8cOk8zKE2hYNCWFPkpf54--NW0VVil4L10uT-XTtKR0",{"id":70,"title":71,"body":72,"description":31,"difficulty":42,"extension":35,"framework":10,"frameworkSlug":8,"meta":76,"navigation":37,"order":32,"path":77,"questions":78,"related":62,"seo":99,"seoDescription":100,"stem":101,"subtopic":102,"topic":19,"topicSlug":20,"updated":67,"__hash__":103},"qa\u002Fpython\u002Fiteration\u002Fcomprehensions.md","Comprehensions",{"type":28,"value":73,"toc":74},[],{"title":31,"searchDepth":32,"depth":32,"links":75},[],{},"\u002Fpython\u002Fiteration\u002Fcomprehensions",[79,83,87,91,95],{"id":80,"difficulty":42,"q":81,"a":82},"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":84,"difficulty":42,"q":85,"a":86},"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":88,"difficulty":34,"q":89,"a":90},"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":92,"difficulty":42,"q":93,"a":94},"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":96,"difficulty":34,"q":97,"a":98},"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":31},"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":105,"title":106,"body":107,"description":31,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":111,"navigation":37,"order":11,"path":112,"questions":113,"related":62,"seo":138,"seoDescription":139,"stem":140,"subtopic":141,"topic":19,"topicSlug":20,"updated":67,"__hash__":142},"qa\u002Fpython\u002Fiteration\u002Fiterators.md","Iterators",{"type":28,"value":108,"toc":109},[],{"title":31,"searchDepth":32,"depth":32,"links":110},[],{},"\u002Fpython\u002Fiteration\u002Fiterators",[114,118,122,126,130,134],{"id":115,"difficulty":34,"q":116,"a":117},"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":119,"difficulty":34,"q":120,"a":121},"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":123,"difficulty":42,"q":124,"a":125},"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":127,"difficulty":34,"q":128,"a":129},"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":131,"difficulty":59,"q":132,"a":133},"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":135,"difficulty":34,"q":136,"a":137},"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":31},"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":144,"title":145,"body":146,"description":31,"difficulty":42,"extension":35,"framework":10,"frameworkSlug":8,"meta":150,"navigation":37,"order":151,"path":152,"questions":153,"related":62,"seo":174,"seoDescription":175,"stem":176,"subtopic":177,"topic":19,"topicSlug":20,"updated":67,"__hash__":178},"qa\u002Fpython\u002Fiteration\u002Fenumerate-zip.md","Enumerate Zip",{"type":28,"value":147,"toc":148},[],{"title":31,"searchDepth":32,"depth":32,"links":149},[],{},4,"\u002Fpython\u002Fiteration\u002Fenumerate-zip",[154,158,162,166,170],{"id":155,"difficulty":42,"q":156,"a":157},"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":159,"difficulty":42,"q":160,"a":161},"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":163,"difficulty":34,"q":164,"a":165},"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":167,"difficulty":42,"q":168,"a":169},"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":171,"difficulty":34,"q":172,"a":173},"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":31},"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",1781808674806]