Implement a Lazy Union Iterator Over Two Strictly Sorted Iterators

Read the full interview experience this question came from →

Quick Overview

Implement a UnionIterator that lazily merges two strictly sorted iterators of integers or strings into one strictly sorted iterator without duplicates, using constant extra memory. Tests iterator protocol design, one-element lookahead, cross-input deduplication, end-of-stream behavior and edge cases such as empty inputs.

Implement a Lazy Union Iterator Over Two Strictly Sorted Iterators

Company: MongoDB

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

Implement a `UnionIterator` class that takes two iterators, each producing its elements in strictly increasing order, and is itself an iterator with these properties: - It produces the union of the elements of both inputs. An element that appears in both inputs is produced only once. - Its output is also strictly increasing. - It does not use much memory, even when the inputs contain a very large number of elements. The input iterators follow this interface. `UnionIterator` takes the two iterators in its constructor and implements the same two methods: ```text hasNext() -> bool # true if the iteration has more elements next() -> element # returns the next element in the iteration (an int or a string) ``` ### Examples | Iterator 1 | Iterator 2 | Output of `UnionIterator` | | --- | --- | --- | | 2, 4, 6, 8, 10 | 1, 2, 3, 5, 8, 13 | 1, 2, 3, 4, 5, 6, 8, 10, 13 | | apple, banana, orange, tree | apple, tree, zoo | apple, banana, orange, tree, zoo | | -10, -9, -5, -1, 0, 2, 4 | -9, -5, -3, -2, 0, 1, 2 | -10, -9, -5, -3, -2, -1, 0, 1, 2, 4 | | 1, 2, 3 | (empty) | 1, 2, 3 | ```hint An iterator is not a linked list With a linked list you can look at a node's value and decide later whether to move past it. An iterator's next() hands you the value and moves on in a single step. Decide what that means when you need to compare the fronts of both inputs. ``` ```hint Check the equal case and the end Trace the first example by hand at 2 and at 8, where both inputs offer the same value, and again after one input runs out. Make sure hasNext() gives the right answer at each of those points. ``` ### Constraints and Clarifications - Each input is strictly increasing, so neither input repeats an element on its own. - Within one `UnionIterator`, both inputs hold the same element type: all integers or all strings. Strings compare in ordinary lexicographic order. - Either input, or both, may be empty. - The inputs may be too long to hold in memory, or produced lazily. Do not copy them into a list or set: extra memory must not grow with the length of the inputs. - As with any iterator, `hasNext()` may be called any number of times between calls to `next()` and must not change what `next()` returns. ### Clarifying Questions - What should `next()` do when there are no more elements: raise an exception, or return a sentinel? - Can an input contain a null element? - Should the class trust the strictly increasing guarantee, or detect and report an input that violates it? - Will a single thread use the iterator, or must it be safe to share? ### What a Strong Answer Covers - A correct merge that removes cross-input duplicates, checked against all four examples including the empty input - Constant extra memory and lazy consumption of the inputs - A `hasNext()` that is side-effect free from the caller's point of view, and defined behavior for `next()` at the end - How the lookahead is represented without confusing "no element" with a legitimate value - Code that is generic over comparable element types - Edge-case tests and the time cost per call ### Follow-up Questions - Generalize to k sorted iterators. What is the time per `next()` and the extra memory? - Implement an `IntersectionIterator` that produces only the elements present in both inputs. - The inputs are only non-decreasing, so each may repeat elements, but the output must still be strictly increasing. What changes? - Is your iterator safe if two threads call `next()` concurrently? What would you change to make it safe?

Overview: Implement a UnionIterator that lazily merges two strictly sorted iterators of integers or strings into one strictly sorted iterator without duplicates, using constant extra memory. Tests iterator protocol design, one-element lookahead, cross-input deduplication, end-of-stream behavior and edge cases such as empty inputs.

Read the full MongoDB Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/MongoDB
MongoDB logo
MongoDB
Sep 27, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

Implement a UnionIterator class that takes two iterators, each producing its elements in strictly increasing order, and is itself an iterator with these properties:

  • It produces the union of the elements of both inputs. An element that appears in both inputs is produced only once.
  • Its output is also strictly increasing.
  • It does not use much memory, even when the inputs contain a very large number of elements.

The input iterators follow this interface. UnionIterator takes the two iterators in its constructor and implements the same two methods:

hasNext() -> bool      # true if the iteration has more elements
next()    -> element   # returns the next element in the iteration (an int or a string)

Examples

Iterator 1Iterator 2Output of UnionIterator
2, 4, 6, 8, 101, 2, 3, 5, 8, 131, 2, 3, 4, 5, 6, 8, 10, 13
apple, banana, orange, treeapple, tree, zooapple, banana, orange, tree, zoo
-10, -9, -5, -1, 0, 2, 4-9, -5, -3, -2, 0, 1, 2-10, -9, -5, -3, -2, -1, 0, 1, 2, 4
1, 2, 3(empty)1, 2, 3

Constraints and Clarifications

  • Each input is strictly increasing, so neither input repeats an element on its own.
  • Within one UnionIterator , both inputs hold the same element type: all integers or all strings. Strings compare in ordinary lexicographic order.
  • Either input, or both, may be empty.
  • The inputs may be too long to hold in memory, or produced lazily. Do not copy them into a list or set: extra memory must not grow with the length of the inputs.
  • As with any iterator, hasNext() may be called any number of times between calls to next() and must not change what next() returns.

Clarifying Questions Guidance

  • What should next() do when there are no more elements: raise an exception, or return a sentinel?
  • Can an input contain a null element?
  • Should the class trust the strictly increasing guarantee, or detect and report an input that violates it?
  • Will a single thread use the iterator, or must it be safe to share?

What a Strong Answer Covers Guidance

  • A correct merge that removes cross-input duplicates, checked against all four examples including the empty input
  • Constant extra memory and lazy consumption of the inputs
  • A hasNext() that is side-effect free from the caller's point of view, and defined behavior for next() at the end
  • How the lookahead is represented without confusing "no element" with a legitimate value
  • Code that is generic over comparable element types
  • Edge-case tests and the time cost per call

Follow-up Questions Guidance

  • Generalize to k sorted iterators. What is the time per next() and the extra memory?
  • Implement an IntersectionIterator that produces only the elements present in both inputs.
  • The inputs are only non-decreasing, so each may repeat elements, but the output must still be strictly increasing. What changes?
  • Is your iterator safe if two threads call next() concurrently? What would you change to make it safe?