Ruby Interview Questions - Part 2

Anand Soni - Aug 31 - - Dev Community

1. How would you implement a thread-safe Singleton in Ruby?

Answer:

  • The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. In Ruby, the Singleton module can be used, but it may not be thread-safe. Here’s a custom implementation using mutex for thread safety:
   class MySingleton
     @instance = nil
     @mutex = Mutex.new

     def self.instance
       return @instance if @instance
       @mutex.synchronize do
         @instance ||= new
       end
       @instance
     end

     private_class_method :new
   end
Enter fullscreen mode Exit fullscreen mode

2. Explain how Ruby handles method dispatch and how you can modify it.

Answer:

  • Ruby handles method dispatch through the method lookup path: first checking the object's singleton class, then its class, then included modules, and finally parent classes.
  • You can modify method dispatch using techniques like alias_method, prepend, method_missing, or even overriding respond_to?.
  • Example using prepend:

     module LoggerModule
       def greet
         puts "Logging: Calling greet"
         super
       end
     end
    
     class MyClass
       prepend LoggerModule
    
       def greet
         "Hello!"
       end
     end
    
     puts MyClass.new.greet # => Logs the message and then returns "Hello!"
    

3. How does garbage collection work in Ruby, and how can you optimize it?

Answer:

  • Ruby uses a mark-and-sweep garbage collector, which tracks objects in memory and cleans up those no longer in use.
  • The generational garbage collection (introduced in Ruby 2.1) improves performance by categorizing objects into generations and focusing on cleaning up younger objects more frequently.
  • Optimization techniques include avoiding the creation of unnecessary objects, using symbols instead of strings when appropriate, and utilizing tools like GC.start judiciously to control when garbage collection occurs.
  • Example:

     # Force garbage collection (usually not recommended in production)
     GC.start
    
     # Tune the garbage collector (for advanced scenarios)
     GC::Profiler.enable
    

4. What is the difference between Marshal and YAML for object serialization in Ruby?

Answer:

  • Marshal:

    • Binary serialization format.
    • Faster and more compact than YAML.
    • Limited portability (Ruby-specific, not human-readable).
    • Example:
       data = { name: "Ruby", version: 3.0 }
       marshaled_data = Marshal.dump(data)
       restored_data = Marshal.load(marshaled_data)
    
  • YAML:

    • Text-based serialization format.
    • Slower and more verbose, but human-readable and language-agnostic.
    • More suitable for configuration files and data exchange between different programming languages.
    • Example:
       require 'yaml'
       data = { name: "Ruby", version: 3.0 }
       yaml_data = data.to_yaml
       restored_data = YAML.load(yaml_data)
    

5. How would you implement method chaining in Ruby?

Answer:

  • Method chaining is a technique where multiple methods are called on an object sequentially in a single statement. Each method returns the object itself (or a modified version of it), allowing the next method to be called.
  • Example:

     class Calculator
       def initialize(value = 0)
         @value = value
       end
    
       def add(n)
         @value += n
         self
       end
    
       def subtract(n)
         @value -= n
         self
       end
    
       def result
         @value
       end
     end
    
     calc = Calculator.new(10)
     result = calc.add(5).subtract(3).result # => 12
    

6. How would you handle large numbers and precision in Ruby?

Answer:

  • Ruby’s Integer type automatically handles large numbers by switching from Fixnum to Bignum. However, for floating-point numbers, BigDecimal is preferred to maintain precision in financial calculations or when dealing with very large/small numbers.
  • Example:

     require 'bigdecimal'
     require 'bigdecimal/util'
    
     number = BigDecimal("1.234567890123456789")
     result = number + BigDecimal("0.000000000000000001")
     puts result.to_s('F')  # => "1.234567890123456790"
    

7. Explain the difference between public, private, and protected methods in Ruby.

Answer:

  • Public:
    • Accessible from anywhere. Methods are public by default unless specified otherwise.
  • Private:
    • Can only be called within the context of the current object. Cannot be called with an explicit receiver, even self.
  • Protected:
    • Can be called by any instance of the defining class or its subclasses. Unlike private methods, protected methods can be called with an explicit receiver if the receiver is of the same class or subclass.
  • Example:

     class MyClass
       public
    
       def public_method
         "Public"
       end
    
       protected
    
       def protected_method
         "Protected"
       end
    
       private
    
       def private_method
         "Private"
       end
     end
    
     obj = MyClass.new
     obj.public_method      # => "Public"
     # obj.protected_method  # => NoMethodError
     # obj.private_method    # => NoMethodError
    

8. How would you implement a custom enumerable in Ruby?

Answer:

  • To create a custom enumerable, a class must include the Enumerable module and define an each method that yields items to a block.
  • Example:

     class MyCollection
       include Enumerable
    
       def initialize(items)
         @items = items
       end
    
       def each
         @items.each { |item| yield(item) }
       end
     end
    
     collection = MyCollection.new([1, 2, 3, 4])
     collection.map { |x| x * 2 }  # => [2, 4, 6, 8]
     collection.select(&:even?)     # => [2, 4]
    

9. What are fibers in Ruby, and how do they differ from threads?

Answer:

  • Fibers:
    • Fibers are a form of lightweight concurrency in Ruby, allowing you to pause and resume code execution manually. They provide finer control over the execution flow but are non-preemptive (the programmer must explicitly yield control).
  • Threads:
    • Threads are system-level concurrency primitives. Ruby uses green threads or native threads (depending on the Ruby implementation). Threads can run in parallel (in JRuby or Rubinius) or time-slice (in MRI) but require careful handling of synchronization and resource sharing.
  • Example of a Fiber:

     fiber = Fiber.new do
       puts "Hello"
       Fiber.yield
       puts "World"
     end
    
     fiber.resume # => "Hello"
     fiber.resume # => "World"
    

I hope that Helps!!
Check out more About me

. . . . . . . .