skip to content

Namespaces & Constant Lookup

Modules group constants under :: paths, and Ruby resolves a bare constant through lexical scope before the ancestors. Interviewers probe why class A::B finds different constants than nesting.

on this pageshow

explore

questions

5

In Ruby, how does a module serve as a namespace, and what does the leading :: in ::JSON change?

level: juniorimportance: must knowfreq 62%

answer

  1. constants live inside modules
  2. :: is the scope operator
  3. inner name shadows the outer
  4. leading :: starts at Object
  5. modules have no new

basics

~20 s

A module is a container for constants, so Ingest::JSON::Parser and Ingest::CSV::Parser can coexist. :: walks into a module; a leading ::JSON starts the lookup at Object, the top level, bypassing a nested JSON that shadows it.

solid answer

~40 s

In Ruby, classes and modules are values stored in **constants**, and every module has its own constant table, so wrapping code in `module Ingest` gives each name a qualified path: `Ingest::JSON::Parser` and `Ingest::CSV::Parser` never collide. The `::` operator reads a constant from the module on its left, and it looks only at that module and its ancestors, not at the code's surroundings. A bare name such as `JSON` inside `module Ingest` finds `Ingest::JSON` first, which **shadows** the standard library's top-level `JSON`, so `JSON.parse` fails with `NoMethodError`. Writing `::JSON` starts the lookup at `Object`, where top-level constants live, and reaches the library module. A module cannot be instantiated, so it is a pure namespace unless you also mix it in.

code

ruby · 20 lines
ruby
require "json"

module Ingest
  module JSON
    class Parser
      def parse(raw)
        # JSON.parse(raw)  # NoMethodError: JSON here is Ingest::JSON
        ::JSON.parse(raw) # top-level JSON from the standard library
      end
    end
  end

  module CSV
    class Parser; end
  end
end

Ingest::JSON::Parser.new.parse('{"id": 1}') # => {"id" => 1}
Ingest::CSV::Parser == Ingest::JSON::Parser   # => false
Ingest::String                                # NameError: uninitialized constant Ingest::String

go deeper

for a junior

Explain that a module groups constants under a path like Ingest::CSV::Parser, that :: reads a constant from a module, and that ::JSON means the top-level JSON.

for a middle

Show the shadowing case: an inner module named JSON hides the library for bare references, and a leading :: fixes it; mention that explicit paths do not fall back to Object.

for a senior

Talk about naming policy in a shared gem: one root module, reusing a top-level name only deliberately, and writing ::Name consistently so readers and loaders agree on what each constant means.

for a principal

Frame namespaces as the unit of ownership across teams and gems: they prevent collisions, but they are not an access boundary, so decide which constants are public API and mark the rest private_constant.

## Constants are the names of classes and modules In Ruby, `class Parser` and `module Ingest` are not special declarations. Each one creates an object (an instance of `Class` or `Module`) and stores it in a **constant** named `Parser` or `Ingest`. A constant is any name starting with an uppercase letter. Every class and module owns a **constant table**. When you write one module inside another, the inner constant is stored in the outer module's table, not at the top level: - `module Ingest` stores `Ingest` in `Object`'s table (the top level). - `module Ingest; module JSON` stores `JSON` in `Ingest`'s table, giving the path `Ingest::JSON`. - `class Parser` inside that stores `Parser` in `Ingest::JSON`'s table. That is all a **namespace** is in Ruby: a module used as a container for constants. A data-import gem can ship `Ingest::JSON::Parser` and `Ingest::CSV::Parser` side by side, and neither collides with a `Parser` defined by the application or by another gem. ## The :: scope operator `::` reads a constant out of the module named on its left. `Ingest::CSV::Parser` means: find `Ingest`, read `CSV` from it, then read `Parser` from that. | Expression | Where Ruby looks | |---|---| | `Parser` (bare) | the lexical nesting at that line, then the ancestors of the innermost class or module, then `Object` | | `Ingest::CSV` | `Ingest` and its ancestors only | | `::JSON` | `Object`, the top level | | `Ingest::String` | `Ingest` only; `NameError`, because an explicit path does not fall back to top-level constants | Two details of explicit paths matter in practice: 1. The left side must be a class or module. `"text"::Foo` raises `TypeError` saying the receiver is not a class/module. 2. An explicit path respects `private_constant`. A bare name used inside the namespace does not. ## Shadowing and the leading :: Naming an inner module after a library is common and useful (`Ingest::JSON` groups everything about JSON imports), but it **shadows** the library for every bare reference written inside `Ingest`: - Inside `module Ingest; module JSON; class Parser`, a bare `JSON` resolves to `Ingest::JSON`, because the lexical nesting is searched before anything else. - `JSON.parse(raw)` then raises `NoMethodError`: `Ingest::JSON` has no `parse` method. - `::JSON.parse(raw)` starts at `Object`, finds the standard library's `JSON` module (loaded with `require "json"`), and works. The leading `::` is the fix you reach for when a namespace deliberately reuses a top-level name. It costs nothing at run time and documents the intent. ## A module is not instantiable A module has no `new` method of its own: `Ingest.new` raises `NoMethodError`. That makes a module the natural namespace for code that is never an object in its own right, such as a gem's root: - constants such as `Ingest::VERSION` or a table of supported formats; - module-level methods defined with `def self.parse_file(path)`; - nested classes that do get instantiated, such as `Ingest::CSV::Parser.new(io)`. A class can act as a namespace too (`Ingest::Parser::Error`), which is common for an error class that belongs to one class. Pick a module when the container itself should never be instantiated or inherited from. ## Mistakes interviewers listen for - Treating a module as a package that must be imported: Ruby has no import step for namespaces; once the file defining `Ingest` is loaded with `require`, every path under it is reachable. - Believing a nested module inherits anything from its parent: `Ingest::CSV` is simply stored in `Ingest`'s table. It does not include `Ingest`, does not get its methods, and does not see `Ingest`'s constants except through the lexical nesting of code written inside `module Ingest`. - Expecting `Ingest::CSV::Parser` and `Ingest::JSON::Parser` to share anything because they share a name: they are two unrelated classes stored under two different modules. - Forgetting that the name is recorded once: `Ingest::CSV::Parser.name` returns `"Ingest::CSV::Parser"`, the full path of the first constant the class was assigned to. ## Practical conventions - Give a gem one root module named after the gem and keep every constant under it. - Mirror the path in the file layout (`lib/ingest/csv/parser.rb`), which is what file-based loaders expect. - Reuse a top-level name inside your namespace only on purpose, and then write `::Name` wherever you mean the outer one. - Remember that a namespace is not a boundary: any code can reopen `module Ingest` and add constants; only `private_constant` narrows access to a constant, and only for explicit `::` paths.

  • Why does Ingest::String raise NameError when a bare String inside module Ingest works?
    A bare `String` inside `Ingest` falls back to `Object`, where top-level constants live. An explicit `Ingest::String` searches only `Ingest` and its ancestors and deliberately does not fall back to top-level constants, so a typo in a path cannot silently resolve to an unrelated top-level class.
  • When would you namespace under a class instead of a module?
    When the nested constant belongs to that one class, such as `Ingest::CSV::Parser::Error` for errors only the parser raises. A class works as a namespace, but it can be instantiated and subclassed, so a gem root or a pure grouping of related classes is a module.

A module namespace works like a folder: two files called Parser can live in different folders, a bare name is resolved relative to the folder you are standing in, and ::JSON is an absolute path that starts at the root.

saying these in an interview costs you the question

  • A bare JSON inside module Ingest always means the standard library JSON
  • The leading :: in ::JSON calls a method named JSON
  • Ingest::String finds the top-level String through the namespace
  • You can call Ingest.new to get an instance of a module
  • Namespacing with modules makes constants private to the gem
open as a page

In Ruby, why can class Ingest::CSV::Parser fail to see constants that the same class written inside module Ingest; module CSV sees?

level: middleimportance: must knowfreq 52%

basics

~20 s

Bare constants are searched through the lexical nesting first. Nested syntax puts Ingest::CSV and Ingest in that nesting; compact class Ingest::CSV::Parser puts only the class itself there, so constants of Ingest and Ingest::CSV are not found by bare name.

open as a page

In Ruby, how does Module#const_get resolve a path string such as "CSV::Parser", and how do its inherit flag and a leading :: behave?

level: middleimportance: should knowfreq 28%

basics

~20 s

Module#const_get splits a String on :: and resolves each segment from the previous result, starting at Object when it begins with ::. The inherit flag (default true) applies to every segment; a Symbol path raises NameError, and a missing segment raises NameError.

open as a page

In Ruby, what does Module#private_constant block, and which ways of reaching a private constant still work?

level: middleimportance: should knowfreq 30%

basics

~20 s

Module#private_constant makes a scoped reference such as Ingest::JSON::Tokenizer raise NameError. Bare references from code inside the namespace, subclasses or includers still work, and const_get still returns it; it signals internal API, not a security boundary.

open as a page

In Ruby, when a nesting module and a superclass both define DELIMITER, which one does a bare DELIMITER resolve to, and why does an inherited method ignore the subclass's override?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Ruby checks each module in Module.nesting (own constants only) before the ancestors, so the enclosing module's DELIMITER wins over the superclass's. Constant lookup is fixed where code is written, so an inherited method keeps its own scope; use self.class::DELIMITER for a per-subclass value.

open as a page