Range + Category Lookup Store
- Recognize the lookup store, queried by category and year range, that SIG reskins the most
- Build the two indexes that answer both queries without scanning the whole collection
- Know why the interviewer waves off segment trees and wants a plain hashmap
- Optimize the phone-screen version from a naive O(N) scan to indexed lookups and O(1) range counts
If you sit a Susquehanna software round, expect one problem in some costume. It is a store of records, each carrying a category and a year, that has to answer two questions fast. Give me everything in a category. Give me everything whose year falls in a range. It shows up as a "movie database" in the onsite and as a CSV of movies to speed up in the phone screen. In finance clothes, it becomes an instrument store keyed by sector and maturity. Same problem every time.
It looks harder than it is. The word "range" makes strong candidates reach for a segment tree or a balanced BST, and then they burn the round building one. The interviewer is watching for the opposite instinct: index the data up front so each query touches only the records it needs, never the whole collection. The tool is a hashmap, sometimes two. This lesson builds the store the way SIG wants it built, then handles the phone-screen twist where you are handed a slow version and told to make it fast.
How SIG frames it
The classic statement is almost verbatim across candidate reports. You are given a list of movies, each an object with a name, a genre, and a year. Write a class that returns all movies in a given genre in O(1), and all movies between two years, say 2011 to 2014, also in O(1). Candidates who have seen range queries before immediately think segment tree, and every report ends the same way: the interviewer says it is simpler than that and points at a hashmap.
Take the "O(1)" as shorthand, not a literal bound. What the interviewer means is do not scan all N records on every query. A category lookup genuinely is O(1): store one list per category and hand back the list. A year range cannot be truly O(1) for an arbitrary span, but it can be made independent of N. Years live in a tiny, bounded domain (a few decades of releases, a fixed set of maturities). Bucketing by year and walking only the requested span costs on the order of the span plus the results returned. The number of records held never enters the cost. That distinction, cost tied to the answer rather than to the whole store, is the signal they are grading.
The escalation SIG likes is reading naive code and speeding it up, not a blank page. The phone screen hands you a CSV of movies and a search that loops over every row on each call, then gives you about thirty minutes to make it fast. The fix is always the same: pay once to build an index, then let every later query ride it. Reach for a segment tree here and you have answered a question they did not ask.
The two indexes to build
Everything in this problem comes down to building two dictionaries as the records load, and then answering each query straight out of them. One maps a category to the records in it. One maps a single year to the records from that year. Nothing more exotic is needed.
by_category = {} # category -> [record, ...]by_year = {} # single year -> [record, ...]for name, category, year in records: by_category.setdefault(category, []).append(name) # O(1) insert by_year.setdefault(year, []).append(name) # O(1) insert# category query: hand back the list, O(1)by_category.get("Action", [])# year-range query: touch only the years in the span, not all N records[name for y in range(low, high + 1) for name in by_year.get(y, [])]The category query is a single dictionary read. The range query walks the years from low to high and concatenates each year's bucket, so its cost is the width of the span plus the number of names it returns. Release years span a few decades, so that width is a small constant in practice. A store with a million records answers a five-year range in the same handful of steps as a store with a thousand. That is the whole trick, and it is why the interviewer keeps steering you back to the hashmap.
Asked at Susquehanna
Build the store first, exactly as the onsite asks it. Then do the phone-screen version, where the queries combine a category and a year range and the starting code is a naive scan you have to speed up. Get both automatic and you have covered the way this problem gets asked.
Range + Category Lookup Store: Two Hashmaps
Problem
[name, category, year]. Build a class that answers two kinds of query as fast as possible:-
by_category(category) returns the names of all records in that category.-
by_year_range(low_year, high_year) returns the names of all records whose year is between low_year and high_year, inclusive of both ends.Neither query may scan the full list of records. A category lookup should be O(1); a range lookup should cost on the order of the span of years plus the number of names it returns, independent of how many records the store holds.
Return the category query's names in the order the records were inserted. Return the range query's names in ascending year order, and within a single year in insertion order.
Input: store built from 8 movies; by_category("Action"), then by_year_range(2011, 2014)
Output: ["Drive","John Wick","Baby Driver"], then ["Drive","Bridesmaids","Whiplash","John Wick"]
The category read returns the Action list directly. The range read walks years 2011..2014: 2011 holds Drive then Bridesmaids, 2014 holds Whiplash then John Wick.
Build two dictionaries as the records load: category -> names and year -> names. Answer a category query with one dictionary read, and a range query by walking only the years in the span.
Range + Category Lookup Store: Two Hashmaps
Click "Run" to execute your code
Test Cases
[["RangeLookupStore", [[["Michael Clayton", "Drama", 2007], ["Superbad", "Comedy", 2007], ["Drive", "Action", 2011], ["Bridesmaids", "Comedy", 2011], ["Whiplash", "Drama", 2014], ["John Wick", "Action", 2014], ["La La Land", "Drama", 2016], ["Baby Driver", "Action", 2017]]]], ["by_category", ["Action"]], ["by_category", ["Comedy"]], ["by_year_range", [2011, 2014]], ["by_year_range", [2016, 2020]], ["by_category", ["Horror"]], ["by_year_range", [1990, 2000]]]Expected: [null, ["Drive", "John Wick", "Baby Driver"], ["Superbad", "Bridesmaids"], ["Drive", "Bridesmaids", "Whiplash", "John Wick"], ["La La Land", "Baby Driver"], [], []][["RangeLookupStore", [[["Alpha", "Action", 2010], ["Bravo", "Action", 2010], ["Cielo", "Drama", 2012]]]], ["by_year_range", [2010, 2010]], ["by_category", ["Action"]], ["by_year_range", [2010, 2012]]]Expected: [null, ["Alpha", "Bravo"], ["Alpha", "Bravo"], ["Alpha", "Bravo", "Cielo"]][["RangeLookupStore", [[["Solo", "Action", 2005]]]], ["by_category", ["Action"]], ["by_year_range", [2005, 2005]], ["by_year_range", [2006, 2010]]]Expected: [null, ["Solo"], ["Solo"], []]Category + Year Range at Scale: Index Once, Binary-Search Each Query
Problem
[name, category, year], and a batch of queries, each [category, low_year, high_year]. The starting search loops over every record for every query, checking the category and the year window. With many records and many queries that is O(Q x N), and you are asked to make it fast.For each query, return the names of the records that match the category and fall in the inclusive year range. Return them in ascending year order, and within a year in the order the records were given.
Return one list of names per query, in the same order as the queries.
Input: 6 records; query ["Action", 2011, 2015]
Output: ["Drive","John Wick"]
Of the Action records (2011, 2014, 2017), only 2011 and 2014 fall in [2011, 2015]. Baby Driver at 2017 is outside the window.
Build the category index once, but store each category's records sorted by year. Then a query is a binary search for the year window inside that one category's list, not a scan of everything.
Category + Year Range at Scale: Index Once, Binary-Search Each Query
Click "Run" to execute your code
Test Cases
[[["Drive", "Action", 2011], ["John Wick", "Action", 2014], ["Baby Driver", "Action", 2017], ["Superbad", "Comedy", 2007], ["Bridesmaids", "Comedy", 2011], ["Whiplash", "Drama", 2014]], [["Action", 2011, 2015], ["Comedy", 2010, 2025], ["Drama", 2000, 2010], ["Action", 2017, 2017], ["Sci-Fi", 2000, 2020]]]Expected: [["Drive", "John Wick"], ["Bridesmaids"], [], ["Baby Driver"], []][[["Zed", "Tech", 2000], ["Ann", "Tech", 2000], ["Moe", "Tech", 2001]], [["Tech", 2000, 2000], ["Tech", 2000, 2001], ["Tech", 2002, 2005]]]Expected: [["Zed", "Ann"], ["Zed", "Ann", "Moe"], []]Key Takeaways
- ✓SIG reskins one lookup problem more than any other: a store queried by category and by a year range. Recognize it under the movie, CSV, and instrument-store costumes.
- ✓Build two indexes once at load time: category -> names for O(1) category reads, and year -> names so a range walks only its span and stays independent of N.
- ✓The "O(1)" prompt is a nudge to index, not a literal bound, and reaching for a segment tree is the trap. Name the read-heavy, bounded-domain reasoning out loud.
- ✓The phone screen hands you a naive O(Q x N) scan to speed up: index once, sort each category by year, and binary-search the window. Push it to O(1) range counts with a prefix sum when asked.
Susquehanna interview guideKnow what Susquehanna asks before you walk in.
8 coding · 1 system design · 26 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.