Exercise 1: A Table With No Foreign Keys At All — Possible Solution ==================================================================== THE TEST ------------------------------ cat = Catalog(tmp) cat.create_table('logs', [('id', 'INTEGER'), ('message', 'TEXT')]) # no foreign_keys arg cat.insert('logs', [1, 'hello']) cat.insert('logs', [2, 'world']) _, logs_table = cat.tables['logs'] print(list(logs_table.scan())) print(cat.foreign_keys['logs']) RESULT ------------------------------ rows in a table with zero foreign keys: [[1, 'hello'], [2, 'world']] Both rows insert exactly as they would under Course 1's own plain HeapTable.insert() -- no rejection, no extra behavior. And cat.foreign_keys['logs'] is [], an empty list, not None and not a missing key. WHY THE EMPTY LIST MATTERS, NOT JUST "NO FOREIGN KEYS" ------------------------------ create_table()'s own signature defaults foreign_keys to None, but immediately normalizes it: foreign_keys = foreign_keys or []. This means cat.foreign_keys[name] is ALWAYS a real list, never None, regardless of whether the caller passed anything. insert()'s own constraint-checking loop is: for fk in self.foreign_keys.get(table_name, []): ... Iterating over an empty list runs the loop body zero times -- there's no special "if there are no foreign keys, skip the whole check" branch anywhere in the code. The loop naturally does nothing when there's nothing to check, which is exactly the same code path a table WITH foreign keys uses, just with zero iterations instead of one or more. WHY THIS DESIGN CHOICE MATTERS ------------------------------ If foreign_keys had been left as None for tables with no constraints, every single call site touching self.foreign_keys[table_name] would need its own None-check before it could safely iterate or inspect it -- a real, avoidable source of a None-vs-empty-list bug the moment any new code (a future DELETE operation, a schema-introspection tool) needed to look at a table's own foreign keys. Normalizing to an empty list at the one place a table gets created means every OTHER piece of code touching foreign_keys can assume it's always safely iterable. WHY THIS WORKS AS AN ANSWER ------------------------------ Confirming BOTH that ordinary inserts work unmodified AND that the internal representation is a real empty list (not None, not a missing dict key) verifies the "no foreign keys" case isn't handled by a separate code path at all -- it's the same code, naturally doing nothing, which is a stronger and simpler guarantee than a special case would be.