7 min read
Comparing two approaches
The same job, two ways to do it
Once you know that Big-O measures the shape of growth, you can use it to settle a very ordinary argument: given two programs that both do the job, which one should you keep? "It works" is not the answer, because both work. The useful question is how each one behaves when the input stops being small.
Picture a community event. You have a guest list of everyone who was invited, and as the evening goes on people show up at the door one by one. For each arrival you want a quick yes or no: is this person on the list? We will write two versions and then reason about them.
Approach one: scan the list every time
The most direct idea is to keep the guest list as a plain Python list and, for every arrival, look through it.
def check_arrivals_slow(guests, arrivals):
admitted = []
for person in arrivals:
if person in guests: # this scans the whole list
admitted.append(person)
return admitted
The in test on a list is not free. Python has to walk through the names until it finds a match or reaches the end. If the guest list has n names and there are m arrivals, each arrival may cost up to n comparisons, so the whole thing is about m * n work. When the two lists grow together, that is O(n²) territory.
Approach two: build a set first
Now do one bit of preparation. Before the doors open, pour the guest list into a set, which is built to answer "is this in here?" almost instantly.
def check_arrivals_fast(guests, arrivals):
guest_set = set(guests) # one pass to build it
admitted = []
for person in arrivals:
if person in guest_set: # roughly one step
admitted.append(person)
return admitted
Building the set is one pass over the guest list, so n steps. After that, each of the m arrivals costs roughly one step instead of n. The total is about n + m, which is O(n) when the lists grow together.
Reasoning about which one scales
Both functions return the same admitted list, so testing them on ten names tells you nothing. The difference only shows up at size. Say the guest list and the arrivals are each 500 people. The first approach does roughly 500 × 500 = 250,000 comparisons. The second does roughly 500 + 500 = 1,000 steps. Same answer, and one is done before the other has warmed up.
Tip
When you have two approaches, do not race them once on a tiny example. Ask instead how the work grows: write down each one's Big-O, then imagine the input ten times larger. The approach whose growth is a lower shape almost always wins once the data is real.
The second version does pay a small price: the set is a separate copy of the names, so it uses extra memory. For a guest list that is a bargain. That trade, a little more memory to change n² work into n work, is one you will make again and again, and Big-O is what lets you see it clearly before you commit.
Before moving on, look back at any loop you have written that runs an in test on a list: would turning that list into a set change its Big-O, and what would that speed cost you in memory?
Check yourself
- Both functions produce exactly the same list of admitted guests. Why is that not enough to decide which one to keep, and what question do you ask instead?
- In the first approach, where does the hidden cost come from, and why does it turn a single loop over the arrivals into roughly
n²work overall? - The fast version builds a set, which uses extra memory. Describe how you would explain to someone why that memory is worth spending here.
Where this lesson comes from
Built from
- Data Structures and Algorithms
alphaPlan courses are built from taught programmes rather than invented for the web. Where a claim rests on an outside standard or a reported case, it is named above so you can check it rather than take our word for it.
This course was developed by alphaPlan Center from programs delivered in partnership with the American Corners network.
Found something unclear, outdated or improvable? Suggest an improvement