Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkart

Iterator & Composite Pattern - Traversal Abstractions

Choose your preparation mode3 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Create Leaf Components

Leaf components FileA, FileB, and FileC are instantiated with their names.

💡 Leaves represent end nodes in the composite structure and will yield themselves once when iterated.
Line:class Leaf(Component): def create_iterator(self): return LeafIterator(self)
💡 Leaves have a simple iterator that returns themselves once, enabling uniform traversal.
📊
Iterator & Composite Pattern - Traversal Abstractions - Watch the Algorithm Execute, Step by Step
Watching each iterator creation and traversal step reveals how the composite and iterator patterns work together to abstract tree traversal without type checks.
Step 1/18
·Active fillAnswer cell
Leaf class encapsulates simple components with a single-element iterator.
Leaf
+name: string
+create_iterator()
Composite class manages children and provides recursive iterators.
Composite
+name: string
children: List<Component>
+add()
+create_iterator()
Composite aggregates children components, demonstrating composition.
Composite
+name: string
children: List<Component>
+add()
+create_iterator()
Leaf
+name: string
+create_iterator()
Composite ComponentLeaf Component
Root composite aggregates top-level folders, enabling unified traversal.
Composite
+name: string
children: List<Component>
+add()
+create_iterator()
Leaf
+name: string
+create_iterator()
Composite ComponentLeaf Component
CompositeIterator encapsulates recursive traversal logic using a stack.
CompositeIterator
stack: deque
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
+next()
CompositeIterator Iterator
Lazy initialization of child iterators optimizes traversal.
CompositeIterator
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
Composite
+name: string
children: List<Component>
+create_iterator()
CompositeIterator IteratorComposite Component
Recursive iterator creation enables depth-first traversal.
CompositeIterator
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
Composite
+name: string
children: List<Component>
+create_iterator()
CompositeIterator IteratorComposite Component
LeafIterator yields the leaf component once.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
Composite
+name: string
LeafIterator Iterator
LeafIterator encapsulates single-element traversal.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
LeafIterator Iterator
LeafIterator marks leaf as done after yielding.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
LeafIterator Iterator
CompositeIterator handles iterator transitions between children.
CompositeIterator
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
CompositeIterator Iterator
LeafIterator yields leaf and marks done.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
LeafIterator Iterator
CompositeIterator recursively traverses nested composites.
CompositeIterator
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
Composite
+name: string
children: List<Component>
+create_iterator()
CompositeIterator IteratorComposite Component
LeafIterator yields composite node as leaf for traversal.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
Composite
+name: string
LeafIterator Iterator
LeafIterator encapsulates single-element traversal.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
LeafIterator Iterator
LeafIterator marks leaf as done after yielding.
LeafIterator
leaf: Leaf
done: bool
+hasNext()
+next()
LeafIterator Iterator
hasNext signals traversal completion when no children remain.
CompositeIterator
children: List<Component>
index: int
current_iterator: Iterator
+hasNext()
CompositeIterator Iterator
Composite and Iterator patterns unify traversal abstraction.
«abstract»Component
+name: string
+create_iterator()
Composite ComponentLeaf ComponentCompositeIterator IteratorLeafIterator Iterator

Key Takeaways

The composite and iterator patterns combine to provide a unified traversal interface for tree structures.

This insight is hard to see from code alone because the recursive iterator creation and delegation are subtle and abstract.

Traversal order is depth-first, visiting composite nodes before their children, and leaves exactly once.

Visualizing each iterator creation and next call clarifies the traversal sequence clearly.

The iterator lazily initializes child iterators only when needed, optimizing traversal and simplifying client code.

Seeing hasNext manage iterator transitions step-by-step reveals this lazy evaluation behavior.

Practice

(1/5)
1. What is a common trade-off when using getters and setters for every private field in a class?
medium
A. They can increase code verbosity and reduce encapsulation if used indiscriminately
B. They always improve performance by reducing method calls
C. They eliminate the need for access modifiers entirely
D. They guarantee thread safety without additional synchronization

Solution

  1. Step 1: Understand getters/setters purpose

    They provide controlled access but can clutter code if overused.
  2. Step 2: Analyze trade-offs

    Excessive getters/setters expose internal structure, reducing encapsulation benefits and increasing verbosity.
  3. Step 3: Evaluate incorrect options

    They always improve performance by reducing method calls is false; method calls add overhead. They eliminate the need for access modifiers entirely is wrong; access modifiers remain essential. They guarantee thread safety without additional synchronization is incorrect; thread safety requires explicit handling.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Use getters/setters judiciously to balance encapsulation and code clarity.
Hint: More getters/setters ≠ better encapsulation or performance [OK]
Common Mistakes:
  • Assuming getters/setters improve performance
  • Believing they replace access modifiers
  • Thinking they ensure thread safety automatically
2. What is the time complexity of performing a deep copy on an object with nested collections of total size n, assuming the deep copy recursively duplicates all nested objects?
medium
A. O(1) because only references are copied, no nested duplication
B. O(n log n) because copying nested objects requires sorting references
C. O(n) because each nested object must be duplicated once
D. O(n^2) because each nested object is copied multiple times due to recursion

Solution

  1. Step 1: Identify what deep copy does

    Deep copy recursively duplicates each nested object exactly once, traversing the entire object graph.
  2. Step 2: Analyze complexity

    Since each of the n nested objects is copied once, the time complexity is linear in n. The recursion does not cause repeated copying of the same object due to memoization.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Deep copy visits each nested object once -> O(n) time [OK]
Hint: Deep copy time scales linearly with total nested objects [OK]
Common Mistakes:
  • Thinking recursion causes quadratic copying
3. Which of the following statements about the Single Responsibility Principle is INCORRECT?
medium
A. SRP means a class should only have one method to ensure simplicity.
B. Applying SRP improves cohesion and reduces coupling.
C. A class should have only one reason to change, which means it should have only one responsibility.
D. Violating SRP can lead to fragile code that breaks when unrelated changes occur.

Solution

  1. Step 1: Analyze each statement

    A class should have only one reason to change, which means it should have only one responsibility. correctly states the core SRP definition.
  2. Step 2: Evaluate SRP means a class should only have one method to ensure simplicity.

    SRP is about reasons to change, not the number of methods; a class can have many methods if they serve one responsibility.
  3. Step 3: Confirm options A, B, and D

    Options A, B, and D are true: SRP improves cohesion, reduces coupling, and prevents fragile code.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    SRP ≠ one method per class; it's about one reason to change.
Hint: SRP is about reasons to change, not method count.
Common Mistakes:
  • Confusing responsibility with method count.
  • Assuming fewer methods always means better design.
  • Ignoring cohesion and coupling effects.
4. If you want to reuse parts already built in a complex object (e.g., reuse walls and roof when building multiple houses), which modification to the Builder pattern is most appropriate?
hard
A. Modify the Builder to reset the product after each get_result call to avoid reuse.
B. Use Abstract Factory instead, as it naturally supports part reuse.
C. Cache built parts inside the Builder and reuse them when building new objects.
D. Switch to Factory pattern for simpler object creation and reuse.

Solution

  1. Step 1: Understand reuse requirement

    Reusing parts means the Builder should keep built parts cached and reuse them instead of rebuilding.
  2. Step 2: Evaluate options

    Cache built parts inside the Builder and reuse them when building new objects. caches parts inside Builder, enabling reuse. Modify the Builder to reset the product after each get_result call to avoid reuse. resets product, preventing reuse. Abstract Factory and Factory do not handle stepwise reuse well.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Caching parts in Builder enables reuse efficiently [OK]
Hint: Cache parts in Builder to enable reuse [OK]
Common Mistakes:
  • Resetting product disables reuse
  • Confusing Abstract Factory with Builder reuse
5. If a class overloads a method and also overrides it in a subclass, which method call resolution scenario can cause unexpected behavior, and why?
hard
A. Calling an overloaded method with parameters matching the superclass signature may invoke the superclass version even if the subclass overrides another variant
B. Overloaded methods are resolved at runtime, so the most specific subclass method is always called
C. Calling the overloaded method via a superclass reference always invokes the subclass's overridden method, ignoring overloading
D. Overriding disables overloading in subclasses, so only one method version is callable

Solution

  1. Step 1: Recall overloading vs overriding resolution

    Overloading is resolved at compile time based on reference type and parameter list; overriding is resolved at runtime based on object type.
  2. Step 2: Analyze call with superclass reference

    If the method call matches an overloaded method signature declared in the superclass, that method is chosen at compile time.
  3. Step 3: Consider overridden methods

    If the subclass overrides a different method variant, it won't affect calls resolved to the superclass's overloaded method signature.
  4. Step 4: Identify unexpected behavior

    This can cause the superclass method to be called unexpectedly, even if the subclass has an overridden variant with a different signature.
  5. Final Answer:

    Option A -> Option A
  6. Quick Check:

    Overloading resolution depends on reference type and parameters, potentially bypassing subclass overrides.
Hint: Overloading resolved by reference type; overriding by object type
Common Mistakes:
  • Assuming overloaded methods are dynamically dispatched
  • Believing overriding disables overloading
  • Thinking subclass overrides affect all overloaded variants