Layer 6 Session 39 Architecture

The Prop Drilling Problem

We add a small "favorites" feature and deliberately experience the pain of threading a value through several layers of objects just to reach where it is actually needed.

AppNavMenuExplorera value threaded through layers that do not use it

A value threaded through layers that have nothing to do with it.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Add a feature that requires passing a value down through multiple layers of composed objects
  • Experience firsthand how threading a value through unrelated intermediate layers adds friction
  • Explain why every intermediate layer having to know about a value it doesn't use is a code smell
  • Recognise this problem without necessarily solving it fully โ€” awareness is this session's explicit goal
  • Connect this pattern to the same-named problem in the original React course this Python course is modeled on

2. Pre-Coding Quiz

Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ€” that is the point. You need 4/5 to proceed to the lab.

Question 1 of 5

You want a "favorites" feature: CountryExplorer needs to track which countries a user has favorited. A new outer class, App, needs to trigger toggling a favorite, but the actual state lives on CountryExplorer, two layers down. What is the direct, "just make it work" approach?

This is the direct but painful approach: threading a reference (or a value) through layers that do not themselves use it, purely so it can reach a deeper layer that does โ€” exactly the friction this session is designed to have you feel firsthand.
Question 2 of 5

If a new intermediate class, MenuSection, is added between App and CountryExplorer, and it must accept and forward an explorer reference purely so App can eventually reach it, what problem does this illustrate?

This is the essence of the "prop drilling" problem: an unrelated intermediate layer is forced to know about and forward something purely for the benefit of a much deeper layer โ€” adding coupling and noise to a class that otherwise has nothing to do with favorites.
Question 3 of 5

Why is a growing chain of "pass this reference down another level, just in case something deeper needs it" considered a code smell, even if it technically works?

This directly threatens the "focused, single-responsibility classes" principle from Sessions 16, 28, and 36 โ€” intermediate classes end up cluttered with forwarding logic for values they conceptually have nothing to do with, purely as plumbing.
Question 4 of 5

Why does this session deliberately NOT fully solve the prop drilling problem, only make you experience and name it?

This mirrors the source React course's Session 39 exactly โ€” deliberately experiencing the pain of a real architecture problem, by name, is what makes any future solution (whether a shared context object, dependency injection, or something else) make genuine sense later, instead of being memorized syntax for a problem never truly felt.
Question 5 of 5

How does this Python session's "prop drilling" problem relate to the concept of the same name in the original React course this curriculum is modeled on?

This is a deliberate, direct parallel: the original React course's Session 39 covers "prop drilling" through nested components; this session recreates the identical underlying architecture problem using composed Python classes, since the root cause (a value needed deep in a structure, threaded through uninvolved intermediate layers) is language-independent.

3. The Concept โ€” Experiencing the Prop Drilling Problem

APPneeds explorerMENUSECTIONforwards it,unused itselfCOUNTRYEXPLORERactually uses it

The explorer reference must be threaded through MenuSection, which has no actual use for it itself, purely so App can reach two layers deeper.

Adding a feature that needs a deep reference

We want to toggle a country as a "favorite." The natural place for that state is on CountryExplorer (Session 16), but the trigger โ€” a user action โ€” originates from an outer App class, two composition layers up.

class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries
        self.favorites = set()

    def toggle_favorite(self, country_name):
        if country_name in self.favorites:
            self.favorites.discard(country_name)
        else:
            self.favorites.add(country_name)


class MenuSection:
    def __init__(self, explorer):
        self.explorer = explorer   # MenuSection doesn't otherwise care about explorer at all


class App:
    def __init__(self, menu_section):
        self.menu_section = menu_section

    def handle_favorite_click(self, country_name):
        # App must reach two layers DOWN just to trigger this one action
        self.menu_section.explorer.toggle_favorite(country_name)

The pain gets worse as more layers are added

Add one more layer, and the forwarding chain grows โ€” every intermediate class picks up a parameter and a line of code purely for something it does not itself use.

class NavigationPanel:
    def __init__(self, menu_section):
        self.menu_section = menu_section  # also forwarding, also unrelated to its own job


class App:
    def __init__(self, navigation_panel):
        self.navigation_panel = navigation_panel

    def handle_favorite_click(self, country_name):
        # now THREE layers deep, through TWO classes that have nothing to do with favorites
        self.navigation_panel.menu_section.explorer.toggle_favorite(country_name)

Naming the problem

This is "prop drilling" โ€” a value or reference has to be threaded through several layers of a composed structure purely so it can reach a much deeper layer that actually needs it, coupling every intermediate layer to something conceptually unrelated to its own job. This exact problem, and exact name, appears in the original React course this curriculum is modeled on, because the underlying issue is not specific to any one language or framework.

This session deliberately does not fully solve it

Recognising the problem clearly โ€” feeling the friction of App.navigation_panel.menu_section.explorer.toggle_favorite(...) firsthand โ€” is the point. A future project might solve this with a shared state object, dependency injection, or another pattern, but reaching for a fix before understanding the actual problem tends to produce cargo-culted, poorly-understood code.


4. Lab

Lab objective: Build the layered App / MenuSection / CountryExplorer structure, deliberately experience the deep-reference threading problem, and document it clearly.

What you will build

A file called prop_drilling_lab.py.

Step-by-step instructions

1

Create the file with CountryExplorer including a favorites feature

# prop_drilling_lab.py
class Country:
    def __init__(self, name, region, population):
        self.name = name
        self.region = region
        self.population = population


class CountryExplorer:
    def __init__(self, countries):
        self.countries = countries
        self.favorites = set()

    def toggle_favorite(self, country_name):
        if country_name in self.favorites:
            self.favorites.discard(country_name)
        else:
            self.favorites.add(country_name)
2

Add MenuSection and NavigationPanel, each forwarding a reference they do not otherwise use

class MenuSection:
    def __init__(self, explorer):
        self.explorer = explorer  # only exists to forward this reference downstream


class NavigationPanel:
    def __init__(self, menu_section):
        self.menu_section = menu_section  # same problem, one layer higher
3

Add App and trigger a favorite toggle through the whole chain

class App:
    def __init__(self, navigation_panel):
        self.navigation_panel = navigation_panel

    def handle_favorite_click(self, country_name):
        self.navigation_panel.menu_section.explorer.toggle_favorite(country_name)


explorer = CountryExplorer(countries=[Country(name="Kenya", region="Africa", population=54000000)])
menu = MenuSection(explorer)
nav = NavigationPanel(menu)
app = App(nav)

app.handle_favorite_click("Kenya")
print(explorer.favorites)  # {'Kenya'} โ€” it worked, but look at the path it took to get there
4

Count and print how many layers had to know about explorer just to forward it

print("Layers forced to hold an explorer-related reference:", 3)
# App -> NavigationPanel -> MenuSection -> CountryExplorer
5

Write a clear written explanation of the problem, in your own words

This is the most important step. Describe what MenuSection and NavigationPanel have to do with "favorites" conceptually (nothing), and why that is a problem as the project grows.


5. Expected Files Changed

FileActionWhy
prop_drilling_lab.py Created Deliberately recreates and documents the prop-drilling problem using composed classes.
docs/sessions/session-39/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 2 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add prop_drilling_lab.py docs/sessions/session-39/index.html
git commit -m "session-39: deliberately experience and document the prop drilling problem"
Do not commit until you can answer out loud: "What do MenuSection and NavigationPanel actually have to do with the favorites feature, conceptually?"

7. Code Review Checklist

Go through your code line by line and check each item:


8. Post-Coding Quiz

Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.

Question 1 of 5

You want a "favorites" feature: CountryExplorer needs to track which countries a user has favorited. A new outer class, App, needs to trigger toggling a favorite, but the actual state lives on CountryExplorer, two layers down. What is the direct, "just make it work" approach?

This is the direct but painful approach: threading a reference (or a value) through layers that do not themselves use it, purely so it can reach a deeper layer that does โ€” exactly the friction this session is designed to have you feel firsthand.
Question 2 of 5

If a new intermediate class, MenuSection, is added between App and CountryExplorer, and it must accept and forward an explorer reference purely so App can eventually reach it, what problem does this illustrate?

This is the essence of the "prop drilling" problem: an unrelated intermediate layer is forced to know about and forward something purely for the benefit of a much deeper layer โ€” adding coupling and noise to a class that otherwise has nothing to do with favorites.
Question 3 of 5

Why is a growing chain of "pass this reference down another level, just in case something deeper needs it" considered a code smell, even if it technically works?

This directly threatens the "focused, single-responsibility classes" principle from Sessions 16, 28, and 36 โ€” intermediate classes end up cluttered with forwarding logic for values they conceptually have nothing to do with, purely as plumbing.
Question 4 of 5

Why does this session deliberately NOT fully solve the prop drilling problem, only make you experience and name it?

This mirrors the source React course's Session 39 exactly โ€” deliberately experiencing the pain of a real architecture problem, by name, is what makes any future solution (whether a shared context object, dependency injection, or something else) make genuine sense later, instead of being memorized syntax for a problem never truly felt.
Question 5 of 5

How does this Python session's "prop drilling" problem relate to the concept of the same name in the original React course this curriculum is modeled on?

This is a deliberate, direct parallel: the original React course's Session 39 covers "prop drilling" through nested components; this session recreates the identical underlying architecture problem using composed Python classes, since the root cause (a value needed deep in a structure, threaded through uninvolved intermediate layers) is language-independent.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. If a FOURTH layer were added between App and NavigationPanel, what would have to change, and in how many places?
  2. Why does this problem specifically hurt the "single responsibility" principle established since Session 16, even though the code technically works correctly?
  3. Can you think of a real Python project structure (not necessarily this one) where you might have already experienced something like this?
  4. Without fully implementing a fix, sketch in words what a "shared reference every layer can reach directly" solution might look like, and what new problem THAT might introduce.

10. What Breaks If This Knowledge Is Missing?

  • Coupling that resists change: Every intermediate layer forced to forward an unrelated reference becomes harder to change independently โ€” modifying MenuSection's constructor signature now risks breaking the entire favorites feature, even though MenuSection has nothing conceptually to do with favorites.
  • Architecture review (Session 40): This deliberately-felt problem becomes one of the concrete case studies documented in the next session's full architecture review โ€” a real, working example of a design tension worth recording and reasoning about.
  • Recognizing this pattern in real projects: Having genuinely experienced this friction firsthand means you will recognize it immediately in a future real project, rather than accumulating unrelated forwarded parameters without noticing the underlying pattern.

11. What We Learned

Python concept mastered: The prop drilling problem โ€” threading a value or reference through composition layers that do not themselves use it, and why that couples unrelated classes together.

Unlocks: You can now recognize this specific architecture smell by name and explain exactly why it is a problem, setting up an informed architecture review next session.

Next session: Session 40 โ€” Architecture Review. Layer 6 gate. We conduct a full architecture review of everything built so far, documenting every major structural decision and its tradeoffs.