Interview Tips: a Fizzy Case Study
Everyone, from students with no experience whatsoever to senior developers looking to change their workplace, asks around for interview tips and tricks. Will they hire me if I dress to impress? Should I review algorithmics as much as Design Patterns? So… maybe read a puzzles and riddles book instead? Should I arrive early? … but, like, not too early?
First of all, take a deep breath. In this article, I will give you some interview tips and tricks, as applied to a concrete case study. We’ll start with a simple problem and the most immediate solution, then work our way through applying design principles and impressing interviewers in no time. Ready?
The problem: FizzBuzz
FizzBuzz is a classical albeit small programming problem. It was first designed to help filter out the 99% of interviewees who literally aren’t capable of coding. The task is to write a program that generates the string representation of numbers from 1 to n. However, for multiples of 3 it should instead generate “Fizz”. For multiples of 5 it should generate “Buzz”. And, you guessed it, for multiples of both 3 and 5 it should generate “FizzBuzz”. So, here is an example:
n = 20 output: ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz", 16, 17, "Fizz", 19, "Buzz"]
You can test any solution for this problem online, using LeetCode Online Judge, a great application for trying out your problem-solving skills. By the way, the following article contains snippets tested using the LeetCode Python interpreter, but the examples can be applied in any programming language of your choice and under any environment, as needed. If you’re a Python enthusiast, be sure to check our other Python articles.
The first step of your interview is to demonstrate you can get things done. The interviewer will not expect anything fancy from you from the very first start. In fact, it is recommended that you obviously state that your first draft is just that: a first draft. Be confident, but humble and straightforward. You can even say: “Well, I would like to start with a basic solution and improve it along the way…” and it will do wonders.
Rule no. 95, kid: Concentrate!
So let’s first break down the requirement. We obviously need to read n and iterate through the [1, n] interval and somehow transform each number. After that, each transformed number should be appended to a list. Let’s start from that:
class Solution(object): def transform(self, number): # TODO; def fizzBuzz(self, n): """ :type n: int :rtype: List[str] """ list = []; for number in range(1,n+1): list.append(self.transform(number)); return list
It’s beginning to look like a solution. Now, let’s concentrate on the transformation itself. Pay attention that we need the number’s string representation and not the number itself, so let’s write just that in the transform function:
def transform(self, number): return str(number);
This solution works just fine for n=1 or n=2. But, starting from n=3 we need to take into account the “Fizz” transformation as well. Consequently, we will need to proceed with “Buzz” for any n>=5. Let us transform numbers divisible by 3 into “Fizz”, and those divisible by 5 into “Buzz”.
def transform(self, number): if not (number % 3): return "Fizz"; if not (number % 5): return "Buzz"; return str(number);
This will work great until we get to 15, which will be transformed to “Fizz” instead of the necessary “FizzBuzz”. So, for our final trick, let’s wrap this up with some basic math: a number divisible by 3 and 5 is divisible by 15, since 3 and 5 have no common denominators. This makes our final task to transform any number divisible by 15 into “FizzBuzz”. Final transform function:
def transform(self, number): if not (number % 15): return "FizzBuzz"; if not (number % 3): return "Fizz"; if not (number % 5): return "Buzz"; return str(number);
Talk the talk (that’s why it’s called an interview)
You might ask why I haven’t given you the complete solution from the start, rather tormenting you with intermediary steps. Well, the answer is simple: that’s not what the interviewer wants. They do not want you to come up with a solution from thin air. On the contrary, they want to follow your thought process and observe how you breakdown and tackle even the simplest problem. Remember to think out loud while solving the tasks: describe the way you’re digesting the problem statement and each particular step towards your final goal of reaching a solution.
Now, since you have done so well up until now, the interviewer might tell you that they expect something more. But don’t worry, they will often give you hints. If it is a highly algorithmic interview, they will ask you about the complexity of your solution and possible ways to improve it. Using our example, let’s assume the interviewer wants to test your ability to apply high-level OOP. In order to give you a hint, they will say something like:
- “I want to add a new rule. e.g. for numbers divisible by 7, transform to “Bizz”. Can you code this so that, if someone wants to add any rule, they don’t need to modify the Solution class?”
Or, even more mysterious:
- “Do you think the Solution class respects all SOLID principles? If so, how? If not, improve on its compliance.”
Get your OOP game on
The following SOLID version of the FizzBuzz solution is inspired from Matthias Noback’s book, “Principles of Package Design”, which I recommend for a good review of SOLID principles and generally best practices on class and package design. We talk more about book recommendations for programmers in a previous article. For now, I would like to add that the book uses a PHP example, but since I am a proud programming language agnostic, I will continue in Python.
So, how are we on SOLID? Well, not that great. Unfortunately, our Solution class doesn’t respect the “Single Responsibility Principle”, since it handles both number generation and number transformation. It is also not “Open/Closed” since we need to change that list of if statements inside the transform function to add any other processing rule. “Liskov Substitution”, “Interface Segregation” and “Dependency Inversion” are not applicable, since we do everything in one class… but they just might be useful in the future!
Abstract away!
The following step is to observe that these rules are quite similar, and still we handle the responsability in a repetitive manner. What if we could give some other classes the responsibility for:
- determining whether a number corresponds to a rule
- transforming the number according to that rule
So, let’s say, something like this:
class FizzRule: def matchesRule(self, number): return not (number % 3); def getReplacement(self): return "Fizz";
We still need to register and run FizzRule, BuzzRule and FizzBuzzRule, but let’s go with this intermediary (but functional) solution for now. Remember that it’s better to go step by step and show your work, rather than blurt out everything all at once! Here is the solution up to now:
class FizzRule: def matchesRule(self, number): return not (number % 3); def getReplacement(self): return "Fizz"; class BuzzRule: def matchesRule(self, number): return not (number % 5); def getReplacement(self): return "Buzz"; class FizzBuzzRule: def matchesRule(self, number): return not (number % 15); def getReplacement(self): return "FizzBuzz"; class Solution(object): def transform(self, number): fizzBuzzRule = FizzBuzzRule(); fizzRule = FizzRule(); buzzRule = BuzzRule(); rules = [fizzBuzzRule, fizzRule, buzzRule]; for rule in rules: if (rule.matchesRule(number)): return rule.getReplacement(); return str(number); def fizzBuzz(self, n): """ :type n: int :rtype: List[str] """ list = []; for number in range(1,n+1): list.append(self.transform(number)); return list
Although we have done some decoupling, we still need to modify the Solution class if we need to add any WizzRule we might come up with.
Decouple
So let’s make Solution an even simpler class. It should only handle the configuration of the rules, and delegate the generation task to a FizzBuzz class, configured with all the bells and whistles necessary.
class FizzBuzz(): rules = []; def addRule(self, rule): self.rules.append(rule) def generateList(self, n): list = []; for number in range(1,n+1): list.append(self.transform(number)); return list def transform(self, number): for rule in self.rules: if (rule.matchesRule(number)): return rule.getReplacement(); return str(number);
Indeed this class doesn’t have do deal with the responsibility of rule management, instead it only applies the rules set on it. An important note: the addRule function must enforce a check on the rule, either with type hinting or actual typing, which is not something particular to Python itself. In the gist I include as the final solution I include a Rule abstract class which can you can use as an Interface when translating the solution to the programming language of your choice.
The Solution class looks like this:
class Solution(object): def fizzBuzz(self, n): """ :type n: int :rtype: List[str] """ fizzBuzz = FizzBuzz() fizzBuzzRule = FizzBuzzRule() fizzRule = FizzRule() buzzRule = BuzzRule() fizzBuzz.addRule(fizzBuzzRule) fizzBuzz.addRule(fizzRule) fizzBuzz.addRule(buzzRule); return fizzBuzz.generateList(n)
All in all, we delegated most of the functionality to FizzBuzz and its single responsibility is now to add the proper rules. Here is the final, fully-functional solution:
class Rule: def matchesRule(self, number): pass def getReplacement(self): pass class FizzRule: def matchesRule(self, number): return not (number % 3); def getReplacement(self): return "Fizz"; class BuzzRule: def matchesRule(self, number): return not (number % 5); def getReplacement(self): return "Buzz"; class FizzBuzzRule: def matchesRule(self, number): return not (number % 15); def getReplacement(self): return "FizzBuzz"; class FizzBuzz(): rules = []; def addRule(self, rule): self.rules.append(rule) def generateList(self, n): list = []; for number in range(1,n+1): list.append(self.transform(number)); return list def transform(self, number): for rule in self.rules: if (rule.matchesRule(number)): return rule.getReplacement(); return str(number); class Solution(object): def fizzBuzz(self, n): """ :type n: int :rtype: List[str] """ fizzBuzz = FizzBuzz() fizzBuzzRule = FizzBuzzRule() fizzRule = FizzRule() buzzRule = BuzzRule() fizzBuzz.addRule(fizzBuzzRule) fizzBuzz.addRule(fizzRule) fizzBuzz.addRule(buzzRule); return fizzBuzz.generateList(n)
Conclusion
In this article, we’ve started from a simple coding exercise and improved the solution using high-level OOP methods. I have thoroughly described the process while mentioning the expectations an interviewer would have on these tasks. Due to slow, step-by-step improvements, we have reached a solution which respects SOLID principles and showcases an array of OOP skills desired by possible employers.
Finally, remember that the most important thing to be mindful of is you. Do not panic if you can’t articulate the answer instantaneously, or if you can’t say as much as you want. Take your time, start small and improve on each intermediary solution. As a matter of fact, apply to one of our positions and try these tricks on us.We won’t mind.
We transform challenges into digital experiences
Get in touch to let us know what you’re looking for. Our policy includes 14 days risk-free!
Free project consultation