100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Ruby

Method Visibility

Learn how Ruby's public, private, and protected keywords control which parts of an object's interface callers can access, and why visibility shapes good API design.

Advanced OOPIntermediate9 min readJul 9, 2026
Analogies

Method Visibility

Every method you define in Ruby has a visibility: public, private, or protected. Visibility determines who is allowed to call a method and how. By default, every instance method you write is public, meaning any object holding a reference to your object can call it directly with an explicit receiver. Ruby lets you tighten this default using the private and protected keywords, which is essential for encapsulation — hiding implementation details so that a class's internal helper methods can change freely without breaking code that depends on the class.

🏏

Cricket analogy: Method visibility is like a team's information tiers: public stats anyone can see on the scoreboard, private dressing-room strategy only the team can access, and protected data like fitness reports shared only within the squad's own management.

Public methods

Public methods form a class's external interface. They can be called with an explicit receiver, some_object.public_method, from anywhere in the program. Unless stated otherwise, initialize is a special case: it is always private, because Ruby only ever wants new to invoke it internally. Every other method you define is public unless you say otherwise.

🏏

Cricket analogy: Public methods forming the external interface, callable from anywhere, are like a player's public stats accessible to any fan, while initialize being always private is like the selection meeting that picks the player, only the selectors (new) ever run it internally.

Private methods

A private method cannot be called with an explicit receiver — not even self.method_name in most cases (Ruby 2.7+ makes an exception for attribute writers like self.name =). Private methods exist to hide implementation details that are only meaningful inside the object itself, such as a helper that formats data for a public method to use. Marking methods private communicates intent to other developers: 'this is not part of the contract; do not depend on it.'

🏏

Cricket analogy: A private method not callable with an explicit receiver is like a bowler's personal grip adjustment technique, useful only to that bowler internally, never something a teammate can invoke by name from the boundary.

ruby
class BankAccount
  def initialize(balance)
    @balance = balance
  end

  def withdraw(amount)
    raise ArgumentError, 'insufficient funds' unless sufficient_funds?(amount)
    @balance -= amount
  end

  private

  # Cannot be called as account.sufficient_funds?(50) from outside
  def sufficient_funds?(amount)
    amount <= @balance
  end
end

account = BankAccount.new(100)
account.withdraw(30)          #=> 70
account.sufficient_funds?(10) #=> NoMethodError: private method called

Protected methods

Protected methods sit between public and private: they can be called with an explicit receiver, but only from within an instance method of the same class (or a subclass). This is the idiomatic choice when one object needs to compare itself against another object of the same type using an internal value that should not be exposed publicly.

🏏

Cricket analogy: A protected method callable with an explicit receiver only from within the same class is like two batters from the same team comparing their internal strike rates mid-innings, something an opposing player could never do since they're not part of that team's class.

ruby
class Money
  def initialize(cents)
    @cents = cents
  end

  def >(other)
    cents > other.cents  # calling other.cents requires 'protected', not 'private'
  end

  protected

  attr_reader :cents
end

Money.new(500) > Money.new(300) #=> true
Money.new(500).cents            #=> NoMethodError: protected method called

In Python, all attributes are technically accessible; underscore prefixes like _name or __name are conventions, not enforcement. Ruby's private and protected are enforced by the interpreter itself — calling a private method externally raises NoMethodError, giving Ruby stronger encapsulation guarantees than Python's convention-based approach.

A common mistake is assuming private hides a method from subclasses — it does not. Private methods are inherited and remain callable (without an explicit receiver) inside subclass instance methods. private restricts the calling syntax, not which classes can see the method.

  • Public methods can be called with an explicit receiver from anywhere; this is the default visibility.
  • Private methods cannot be called with an explicit receiver (with a narrow exception for self.attr= setters since Ruby 2.7).
  • Protected methods can be called with an explicit receiver, but only from within another instance method of the same class or its subclasses.
  • initialize is always private so that only new can invoke it.
  • Visibility is enforced by the interpreter, unlike Python's convention-based underscore naming.
  • Use protected when objects of the same class need to compare or interact using internal state.

Practice what you learned

Was this page helpful?

Topics covered

#Ruby#RubyStudyNotes#Programming#MethodVisibility#Method#Visibility#Public#Methods#Functions#StudyNotes#SkillVeris