Lazy Evaluation

Lazy evaluation (called short-circuit evaluation in compiled languages) is a strategy some programming languages use to save work for the last minute or avoid unnecessary work altogether. For example, suppose we had a conditional like this:

if it_is_friday and it_is_raining: print "board games at my place!"

Suppose it_is_friday was false. Because of the Python interpreter's lazy evaluation strategy, it wouldn't bother checking the value of it_is_raining—it knows that either way the result of our and will be false, so we won't print the invitation to board game night.

We can use this to our advantage. For example, suppose we have a check like this:

if friends['Becky'].is_free_this_friday(): invite_to_board_game_night(friends['Becky'])

What happens if 'Becky' isn't in our friends dictionary? In Python, we'll get a KeyError (Java would similarly raise a NullPointerException, but Ruby and JavaScript would just give us a null object).

Instead, we could first confirm that 'Becky' and I are still on good terms:

if 'Becky' in friends and friends['Becky'].is_free_this_friday(): invite_to_board_game_night(friends['Becky'])

This way, if 'Becky' isn't in friends, Python will lazily ignore the rest of the conditional and avoid throwing the KeyError!

This is all hypothetical, of course. It's not like things with Becky are weird or anything. We're totally cool. She's still in my friends dictionary for sure and I hope I'm still in hers and Becky if you're reading this I just want you to know you're still in my friends dicitionary.

Python's generators are also an example of lazy evaluation. For example, the function range in Python generates a list of numbers in a specific range:

print range(1, 11) # prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # (the first argument to range() # is inclusive, and the second is exclusive)

This is commonly used for looping. For example, if we wanted to count to some_high_number, we could do this:

for i in range(1, some_high_number + 1): print "I've eaten " + i + " cakes"

But this will generate a list in memory whose size is order of some_high_number! That could be a lot of space.

So instead, we could use a generator. It behaves like a list in that we can loop through it, but instead of building up all of its contents at once, it simply generates the next element right when it's needed (lazily)!

There's a generator version of range in Python: xrange:

# much more memory efficient! for i in xrange(1, some_high_number + 1): print "I've eaten " + i + " cakes"

In Python 3 they went ahead and made range a generator, so there is no xrange.

We can also take a lazy approach in system design. For example, suppose we had a class for tracking temperatures:

class TempTracker: recorded_temps = [] def record(self, temp): self.recorded_temps.append(temp)

Suppose we wanted to add a feature for getting the the highest temperature we've seen so far. We could "eagerly" keep the max up to date whenever we insert a new temperature:

class TempTrackerEager: recorded_temps = [] max_temp = None def record(self, temp): self.recorded_temps.append(temp) if temp > self.max_temp: self.max_temp = temp def get_max(self): return self.max_temp

Or we could lazily (or "just in time") calculate the max whenever it's requested:

class TempTrackerLazy: recorded_temps = [] def record(self, temp): self.recorded_temps.append(temp) def get_max(self): return max(self.recorded_temps)

The best choice depends on how often you expect to run get_max!

Becky, I haven't hosted another board game night since the incident. I know we both said things we didn't really mean and anyway Becky just if you're reading this please know that I've been cake free for 3 whole days now and it's hard but I'm doing it for you PLEASE Becky. Please.

What's next?

If you're ready to start applying these concepts to some problems, check out our mock coding interview questions.

They mimic a real interview by offering hints when you're stuck or you're missing an optimization.

Try some questions now

Psst. Pass it on.

. . .