skip to content

In Ruby, what does writing `class String` with a new method do when String already exists, and which code sees that method?

level: juniorimportance: must knowfreq 60%

answer

  1. class keyword looks up the constant
  2. same class object, not a subclass
  3. existing instances see it too
  4. process-wide, not per file
  5. different superclass: TypeError

basics

~20 s

Writing class String again reopens the existing String class instead of creating one, so the def adds a method to String itself. Every string in the process, including older strings and strings inside gems, can then call it.

solid answer

~40 s

In Ruby, `class String` looks up the constant `String`; because it already names a class, Ruby runs the body inside that same class object rather than defining a new one. Each `def` adds or replaces an entry in `String`'s method table. Method lookup happens at call time, so strings created before the reopening gain the method too, and so does every file and gem in the process — there is no per-file scope. Reopening with a conflicting superclass (`class String < Array`) raises `TypeError` (superclass mismatch), and reopening a constant that holds a non-class raises `TypeError` as well. That global reach is exactly why patches to core classes need care.

code

ruby · 12 lines
ruby
greeting = "hi"

class String
  def shout = upcase + "!"
end

greeting.shout   # => "HI!"  -- created before the reopen
"ok".shout       # => "OK!"

class String < Array
end
# => TypeError: superclass mismatch for class String

go deeper

for a junior

Recall that class String reopens the existing class, and that the new method is available on every string in the process.

for a middle

Explain the constant lookup behind the class keyword, dynamic method lookup for existing objects, and the superclass-mismatch TypeError.

for a senior

Connect the global reach to real risks: collisions between libraries and replaced core behaviour inside code you never read.

for a principal

Decide where core patches may live, who reviews them, and when a wrapper or refinement must replace a global patch.

## What the class keyword really does In many languages a class declaration defines a type once, at compile time. In Ruby, `class Name ... end` is executable code that runs top to bottom: 1. Ruby looks up the constant `Name` in the current scope. 2. If it is **not defined**, Ruby creates a new class, assigns it to the constant, and runs the body inside it. 3. If it **is defined and is a class**, Ruby runs the body inside **that existing class**. This is called **reopening** the class; Ruby classes are therefore called **open classes**. 4. If it is defined but holds something else (an integer, a module), Ruby raises `TypeError`. So writing `class String` in your own file does not create a new `String`; it gives you the real, built-in `String` to modify. Adding or changing methods on a class you do not own this way is **monkey patching**. ## Who sees the new method A `def` inside the reopened body adds an entry to `String`'s **method table**. Because Ruby looks methods up on every call, the effect is immediate and global: - **Existing instances**: a string created before the reopening can call the method afterwards. - **Every file**: there is no import boundary; code that never required your file still sees the method once your file has run. - **Every gem**: library code inside your dependencies sees it too, and so do other patches. - **Subclasses**: any subclass of `String` inherits the method. | Situation | Result | |---|---| | `class String; def shout = upcase + "!"; end` | `"hi".shout # => "HI!"` for every string | | Same name as an existing method | replaces it for everyone (warning only under `-w`) | | `class String < Object` | allowed: Object is already String's superclass | | `class String < Array` | `TypeError`: superclass mismatch for class String | ## Why this matters for core classes The global reach is the whole point — and the whole risk: - A helper like `String#titleize` becomes available everywhere without passing anything around, which is why libraries historically added convenience methods to core classes. - Two libraries defining the same method on `String` collide, and the one loaded last wins. - Replacing an existing method such as `String#length` changes the behaviour of code you never read, including the standard library. Because of this, most teams limit core patches to a few reviewed files, often a `core_ext` directory with one file per patched class, and prefer a helper method or a small wrapper class when the convenience is only needed in their own code. ## Related forms - `module Comparable ... end` reopens an existing module the same way. - Writing the body inside `String.class_eval { ... }` also modifies the existing class, with different scoping rules; that is its own topic. - A **refinement** limits a patch to the files that activate it with `using`, the main alternative when a global change is too risky. ## Common mistakes - **Assuming a new class was created.** There is no second `String` to find; the added method sits on the real one, next to `upcase` and `length`. - **Forgetting load order.** The method exists only after the reopening file has run, so code that runs earlier, for example during boot, raises `NoMethodError`. - **Replacing instead of adding.** Reusing a name `String` already has, such as `strip` or `length`, silently changes a core method for every library, and the `method redefined` warning appears only under `-w`. ## What interviewers are checking This is a screening question about Ruby's object model: a class is an ordinary object that stays mutable at run time. A good answer says that reopening modifies the existing class, that the change is process-wide and affects existing objects, and names the risk that follows — collisions and surprising behaviour in code you did not write.

  • Can you reopen String to change its superclass?
    No. Reopening with an explicit superclass is allowed only when it matches the existing one; `class String < Array` raises `TypeError` with 'superclass mismatch for class String'. A class's superclass is fixed when the class is created.
  • If a gem reopens String, does code that never requires that gem see the new method?
    Yes, once the gem's file has been loaded by anything in the process. `require` loads code into the one shared runtime; there is no per-file visibility for methods added to a class.

saying these in an interview costs you the question

  • Reopening String creates a new class that shadows the built-in one.
  • Only strings created after the reopening get the new method.
  • The new method is visible only in the file that reopened the class.
  • You can reopen String with a different superclass to change its parent.