RB 129 Interview Preparation
A guide to preparing for the RB129 Interview
After completing the RB129 written exam, I immediately read through the guide for the interview and thought it seemed pretty basic.
“The interviewer will ask you to present or teach OOP topics. You should have a strong conceptual understanding of the core concepts in RB120. You should be able to talk about why they exist, and how to use them in code. You’ll also need to use that conceptual understanding to reason with and solve various code examples and problems that the interviewer will present you with.
We’ll ask you to speak and drive the conversation, so practice speaking and teaching others on technical topics. Make sure you study the following resources, perhaps going through them more than once.”
What followed was just links to some of the materials I had used to prepare for the written exam. Since I had just done so well there, I scheduled an interview for a few days later. In the interim I did go back through the OOP book and finished working through the exercises. To be honest, I felt very confident the morning I showed up for the interview. It was all downhill from there.
My interviewer was friendly and helpful throughout the exam, but I knew as soon as we had finished that I had not passed. I also learned two important lessons:
- Knowing the material does not mean you are ready to teach the material; and
- I really suck at making up code examples under pressure.
To help future students going through the same process, I’m writing this guide I wish I had while going through the interview. While this will not contain exact interview questions, it should give you a good idea of whether you are ready for the interview and some examples of good code samples to use. One tip I will give to every interviewee-do not overcomplicate your code examples. Seriously-if the interviewer asks you to demonstrate one specific concept, don’t throw in a couple more unrelated concepts to show off. It will just make the interviewer wonder if you really know what they are asking and waste your precious time.
What is encapsulation in Ruby, and why does it matter? Demonstrate with code.
Encapsulation is a form of data protection in Ruby. It defines the boundaries in the application and allows us to hide functionality from parts of the code base in order to protect our data. We accomplish this in Ruby by creating objects and exposing interfaces (methods) to interact with those objects.
class Cat
attr_reader :name def initialize(name)
@name = name
end
endwhiskers = Cat.new("Whiskers")boots = Cat.new("Boots")
Here, we have created two different cat objects that each have a name instance variable assigned to them through the initialize method. This shows an example of encapsulation because this initialize method is only accessible when creating a new Cat object and would not be available to any other classes (unless they inherited from this class). In addition, the name variable for each object is unique to that object and not accessible by the other object.
What is polymorphism in Ruby? Again, explain by giving code examples.
Polymorphism is the ability of objects with different types to respond to the same method, but not necessarily in different ways.
Full credit for this example goes to Hayley Keefer, who I studied with while preparing to retake the interview:
class Vehicle
def accelerate
puts "#{self.class} is moving forward"
end
endclass Car < Vehicle
endclass Truck < Vehicle
endvehicles = [Vehicle.new, Car.new, Truck.new]vehicles.each do |vehicle|
vehicle.accelerate
end
Here, the Vehicle, Car, and Truck class all can use the accelerate method. We could have instead added different functionality to the Car and Truck methods and this would still be an example of polymorphism.
You will typically see polymorphism work in 3 distinct ways. One is through inheritance. I’ll discuss this in more detail later, but essentially you could put a method in a superclass that several other classes inherit from. Since that method could then be called in all three classes, this would be an example of polymorphism.
A second way would be through mixins. Again, several classes could use the same method from a mixin, such as a module, which would be another way to demonstrate polymorphism.
Finally, the third (and most coding-intensive) way would be to use duck typing, which is really just a fancy way of saying create two different classes which have a method with the same name. You can call the method on objects of both classes (even though they are entirely separate) which demonstrates polymorphism.
Method Access Control
How do we control access to methods in Ruby? One of the major ways is by deciding whether methods should be public, private, or protected.
Private methods are only available inside of theclass. See this example:
class Student def initialize(name, age)
@name = name
@age = age
end def get_age
puts @age
endprivate
attr_reader :ageendmike = Student.new("Mike", 25)
mike.age
Here, we create create a new student object, mike, and assign the instance variable name to “Mike” and the instance variable age to 25.
However, we make the reader (getter method) for the age variable private, which means when we try to access it outside of the class, it raises an error. However, we could instead do the following:
class Studentdef initialize(name, age)
@name = name
@age = age
enddef get_age
puts @age
endprivate
attr_reader :ageendmike = Student.new("Mike", 25)
mike.get_age
Since get_age is a public method, it can be called anywhere. Since get_age is within the class, it has access to the age getter method even though it is private.
Then we get to what may be the most confusing part of access control. What is the difference between a private and protected method? An easy way to explain this is by trying to compare the ages of two students.
class Studentdef initialize(name, age)
@name = name
@age = age
enddef ==(other)
age == other.age
endprivate
attr_reader :ageendmike = Student.new("Mike", 25)
diane = Student.new("Diane", 25)puts mike == diane
This code again returns an error because the getter method for age is private. But isn’t this still being accessed within the class? Not exactly. To understand this it is important to understand the different between implicit and explicit receivers. As far as I can tell, implicit means that object is only calling the method on itself. As soon as you add in another object (here mike trying to compare to diane), this makes the code an explicit call and it will not work with a private method. On the other hand, if I swapped private for protected as follows, the code will work:
class Studentdef initialize(name, age)
@name = name
@age = age
enddef ==(other)
age == other.age
endprotected
attr_reader :ageendmike = Student.new("Mike", 25)
diane = Student.new("Diane", 25)puts mike == diane
How does inheritance work in Ruby? Also, when would inheritance be appropriate?
In Ruby each class can inherit from no more than 1 other class, which is called the superclass. This is powerful because it can allow us to group behaviors in to one class that are shared between multiple different objects, allowing us to save time as we develop those classes. Here is an example:
class Animal
attr_accessor :name, :weight def initialize(name, weight)
@name = name
@weight = weight
endendclass Dog < Animal def speaks
puts "Woof"
end
endclass Cat < Animal def speaks
puts "Meow"
end
end
Here, we create an Animal superclass which Dog and Cat inherit from. By doing this we can allow Dog and Cat objects to be initialized with name and weight variables, but they can have different functionality when they call the speaks method (this is also an example of polymorphism, which we already covered).
What are getter and setter methods in Ruby and how could we create them? Again, demonstrate with code.
Getter methods are used to access the variables within our objects, while setter methods are used to set those variables. Here is an example:
class Animal def initialize(name, weight)
@name = name
@weight = weight
end def weight
@weight
end def weight=(new_weight)
@weight = new_weight
endend
As you can see, it takes quite a few lines of code just to be able to get or set one variable. If we had code with numerous variables, this could get very cumbersome quickly. To make this easier, Ruby has built in functionality using attr_accessor (allows you to both get and set the variable), attr_reader (allows you to get the variable) and attr_writer (allows you to set the variable). Here is an example of the same code above using Ruby’s built-in functionality:
class Animal
attr_accessor :weightdef initialize(name, weight)
@name = name
@weight = weight
end
end
This code has the exact same functionality as the code above, but obviously saves considerable time and gives me much less room for errors.
Instance methods vs. class methods and using “self”
This was one of the concepts that took some time to understand clearly. I am going to use self while discussing instance vs. class methods because it can be very helpful. Keep in mind that instance methods will typically act on individual objects, while class methods will instead act on the class itself. Below is some code that demonstrates instance methods and how to use “self” within instance methods.
class Dog
attr_accessor :name, :weight
def initialize(name, weight)
@name = name
@weight = weight
end
def gain_five
self.weight += 5
end
def lose_five
self.weight -= 5
end
endfido = Dog.new("Fido", 75)
scooby = Dog.new("Scooby", 75)
fido.gain_weight
Here, we have a Dog class that assigns two instance variables to each new Dog object, name and weight. I have created two instance methods, gain_five and lose_five, that will cause the value of the individual dog object’s weight to go up or down by 5.
We know this is an instance variable because even though I have caused fido’s weight to increase by 5 in the above example, if I check scooby’s weight, it will remain 75. This is because these methods only act on the individual object.
On the other hand, an example of a class method is below:
class Dog
attr_accessor :name, :weight
@@number_of_dogs = 0 def initialize(name, weight)
@name = name
@weight = weight
@@number_of_dogs += 1 end
def self.total_dogs
@@number_of_dogs
end
endfido = Dog.new("Fido", 125)
p Dog.total_dogs
Here, we can call the total_dogs method on the class because this is a class method. This would be the case even if we weren’t using a class variable (denoted by the double @ before it’s name) since we call self on the class, not on the instance of an object.
Modules
A module is a collection of behaviors that can be “mixed in” to multiple classes using the include key word. This is a great way to deal with situations where Ruby’s single-inheritance limitation just won’t work. For instance, here is a classic example:
class Animal def initialize(name, weight)
@name = name
@weight = weight
end
end
class Dog < Animalendclass Cat < Animalendclass Platypus < Animalend
Suppose in the above example we has this Animal superclass that contained a number of behaviors we wanted all of these animals to share. However, we wanted to add a swim method that would be appropriate for the Dog and Platypus classes but which should not be included in the Cat class. This gives us a couple of options:
- We could create a swim method for each individual class or copy and paste the same method into multiple classes. This may work in this simple example, but what if we plan to implement dozens more animals and may want to add even more behaviors in the future?
- We could create a module to “mix in” to the classes we want to have access to the swim method. This is likely the best approach for scaling, and is implemented below.
module Swims def swim
p "I can swim!"
endendclass Animaldef initialize(name, weight)
@name = name
@weight = weight
end
endclass Dog < Animal
include Swimsendclass Cat < Animalendclass Platypus < Animal
include Swimsend
When we have collections of behaviors, constants, or classes we may need to access, traditionally we organize that in a special type of module called a “namespace”. This is a module that tends to be much broader in scope. For instance, the following:
module LawFirmLAW_SECTIONS = {1.01 => Torts, 1.02 => Criminal, 1.03 => Probate}module TryCases
def litigate
end
endclass Legal def initialize(name, hourly_rate)
(implementation here)
end def research
endendclass Lawyer < Legal
include TryCasesendclass Paralegal < Legal
endend
Then when we need to reference part of this code, such as the LAW_SECTIONS hash, we would do that with a double colon. For instance:
legal_hash = LawFirm::LAW_SECTIONS
This is a good overview of the major topics you will be tested on through the interview. Honestly, if you have a good grasp of the above and can come up with similar code examples on your own, you might be ready. I strongly encourage anyone getting ready for the test to either try to make the scheduled study groups and to reach out to other studying through both the “120–139 study group” on slack and on “the spot” slack group. After doing poorly I reached out and over the week had multiple one-on-one meetings with others preparing for the exams as well as a group session, all of which greatly helped me with talking through the material. I am now scheduled to take the test again and know it will go much better than before. I wish everyone still studying the best!