skip to content

Public, Private & Protected

Ruby's private means no explicit receiver (self. allowed since 2.7), while protected admits other instances of the class, as in ==. Interviewers probe both rules and class-method privacy.

on this pageshow

explore

questions

4

In Ruby, which callers may invoke a public, a private and a protected method, and can a subclass call its parent's private methods?

level: juniorimportance: must knowfreq 80%

answer

  1. the rule is about the receiver
  2. private: no receiver, or literal self
  3. self. allowed since Ruby 2.7
  4. protected: caller's self is_a? owner
  5. subclasses inherit private methods

basics

~20 s

Public methods are callable by anyone. Private methods can be called only without an explicit receiver or with literal self. Protected methods accept an explicit receiver when the caller's self is a kind of the defining class. Subclasses can call inherited private methods.

solid answer

~40 s

Ruby's visibility rules are about the **receiver** of a call, not about which class the code sits in. `public`, the default, allows any caller. `private` allows a call only with no explicit receiver, or, since Ruby 2.7, with the literal `self.` as receiver; `other.secret` raises `NoMethodError` even when `other` is the same class, and so does `copy = self; copy.secret`. `protected` allows an explicit receiver as long as the calling code's `self` is a kind of the class or module that defines the method, which lets one `Salary` read another's protected `amount`. Because a subclass calls inherited methods on `self` with no receiver, it can use its parent's private methods freely: Ruby's `private` does not hide anything from subclasses.

code

ruby · 19 lines
ruby
class Salary
  def initialize(amount) = @amount = amount

  def doubled      = amount * 2          # implicit receiver: fine
  def doubled_self = self.amount * 2     # literal self: fine since Ruby 2.7
  def compare(other) = other.amount      # explicit receiver: NoMethodError

  private

  def amount = @amount
end

class Bonus < Salary
  def tripled = amount * 3               # inherited private method: fine
end

Bonus.new(10).tripled                    # => 30
Salary.new(1).compare(Salary.new(2))
# NoMethodError: private method 'amount' called for an instance of Salary

go deeper

for a junior

Recall the three levels: public for anyone, private only without a receiver or with self., protected for other objects of the same class family.

for a middle

Explain that the checks are about the receiver, that private is per object, that the literal self exemption arrived in 2.7, and that subclasses can call inherited private methods.

for a senior

Show how you use visibility to shape interfaces: small public surfaces, protected only for peer operations like comparison, and awareness that send can still reach private methods.

for a principal

Weigh visibility as a communication tool against its limits, deciding where a team relies on it versus separate objects or modules to keep internals out of reach.

## The rule is about the receiver Every Ruby method call has a **receiver**: the object before the dot, or `self` when there is no dot. Ruby's three visibility levels decide which receivers are acceptable: | Visibility | Implicit receiver (`amount`) | Literal `self.amount` | Other object (`other.amount`) | |---|---|---|---| | `public` | allowed | allowed | allowed | | `private` | allowed | allowed (Ruby 2.7+) | `NoMethodError` | | `protected` | allowed | allowed | allowed only if the caller's `self` is a kind of the defining class | A violation raises `NoMethodError` with a message such as `private method 'amount' called for an instance of Salary` or `protected method 'amount' called for an instance of Salary`. ## Private: per object, not per class The Ruby syntax documentation puts it precisely: a private method may only be called without a receiver, or with a literal `self` as the receiver. Two consequences surprise newcomers: - **Another instance of the same class cannot call it.** Inside `Salary`, `other_salary.amount` raises when `amount` is private, even though the code is in the same class. - **Only the literal keyword counts.** `copy = self; copy.amount` raises too, because the receiver is a variable, not the word `self`. Before Ruby 2.7, `self.amount` raised as well; only private writers such as `self.amount = 5` were allowed with `self`. Ruby 2.7 allowed any private method to be called on a literal `self`. ## Subclasses see everything In Ruby, **subclasses already have access to all methods defined in the parent class, even private ones**, as the `Module#protected` documentation states. A subclass method that calls `amount` with no receiver finds the inherited private method through normal lookup and runs it. So: 1. `private` is not "hidden from subclasses". 2. `protected` is not "visible to subclasses" either; it is about **peers**, other objects whose class inherits from the defining class. 3. The only thing `private` blocks is calling the method on an explicit receiver other than `self`. ## Protected: for peers `protected` exists for methods that objects of one family need to call on each other, most often comparison operators. Ruby checks one condition when a protected method is called with an explicit receiver: the object running the calling code must be a kind of the class or module where the method is defined. If `Salary` defines protected `amount`, then any `Salary`, or instance of a `Salary` subclass, may call `other.amount` on another `Salary`, while outside code gets `NoMethodError`. ## Private writers and self Writers are the one place where `self.` was always required for a private method. A bare `amount = 5` inside a method creates a local variable, so the only way to call a private `amount=` is `self.amount = 5`, and Ruby has allowed that form for private writers for a long time. Ruby 2.7 extended the same exemption to every private method, so `self.amount` and `self.amount += 1` work too. The exemption stays narrow on purpose: - It applies to the keyword `self` written in the call, nothing else. - It does not let code in another object call the method, whatever that object's class. - It does not change protected methods, which already accepted `self` as a receiver. ## Where the defaults come from - Methods defined in a class body are **public** unless a modifier says otherwise. - `initialize`, `initialize_copy` and `respond_to_missing?` are made **private** automatically. - Methods defined at the top level of a script become **private** methods of `Object`, which is why they can be called anywhere without a receiver but not as `42.helper`. ## Visibility is not security Visibility guards against accidental use. Reflection such as `send` can still reach a private method, so it expresses intent and keeps interfaces small rather than enforcing a security boundary.

  • In Ruby, why does copy = self; copy.amount raise when self.amount works for a private amount?
    The private rule checks the syntax of the call, not the identity of the object. Only the keyword `self` written as the receiver is exempt. A local variable holding the same object is an ordinary explicit receiver, so the call is rejected with `NoMethodError`.
  • In Ruby, what visibility do methods defined at the top level of a script get?
    They become private instance methods of `Object`. That is why a top-level helper can be called with no receiver from anywhere, including inside other classes, while `some_object.helper` raises `NoMethodError` for a private method.

saying these in an interview costs you the question

  • Private methods are invisible to subclasses
  • Another instance of the same class can call a private method
  • self.method never works for private methods on Ruby 4.0
  • Protected means accessible to subclasses, as in Java
  • Any variable pointing at self may call a private method
open as a page

How do you let Ruby Salary objects compare amounts in == without making amount public, and why does private fail there?

level: middleimportance: must knowfreq 62%

basics

~10 s

Declare the reader protected: protected :amount. Inside Salary#==, other.amount is then allowed because the caller is a Salary, while outside code gets NoMethodError. A private amount fails because other.amount uses an explicit receiver.

open as a page

In Ruby, how do a bare private section, private with a symbol list and private attr_reader differ, and what does a section miss?

level: middleimportance: should knowfreq 48%

basics

~20 s

A bare private makes later defs in that class body private; private :a, :b changes named existing methods; private attr_reader :x works since Ruby 3.0. A section does not affect def self.x class methods or other class bodies.

open as a page

In Ruby, how does private_class_method :new force Salary objects through factory methods, and what can still create one without them?

level: seniorimportance: should knowfreq 34%

basics

~20 s

private_class_method :new makes Salary.new private, so outside calls raise NoMethodError while class methods such as Salary.from_cents call new without a receiver. Salary.allocate, send, and dup or clone of an existing Salary still bypass the factories.

open as a page