Core Ruby language concepts explained in depth, one at a time.
Decorator, Observer, and Singleton already ship with Ruby as delegate, observer, and singleton — yet most codebases hand-roll all three. SimpleDelegator forwards everything you did not override (and lets you swap the target), DelegateClass(Array) is composition where you would have subclassed Array, and Singleton closes the dup/clone/Marshal leaks plus the thread race that a hand-written @instance ||= new always misses.
Ruby 4.0's experimental container for isolating constants, monkey patches, and loaded libraries inside a single process — what it actually separates, and why it is namespace isolation rather than parallelism.
obj.method(:name) hands you a callable value you can compose with >> and <<, curry, and introspect; unbind strips the receiver off so a module's implementation can be bind_call'd onto an object that never included it.
Why class_eval defines instance methods and instance_eval defines class methods, the _exec variants that fix the vanished-ivar trap, and how RSpec-style internal DSLs are built on the same self switch.
self controls where @var lives and who receives a bare method call; singleton classes explain why 'class methods' are just methods defined on the class object itself.
method_missing vs. define_method, source_location, ObjectSpace, and the included/extended hooks behind ActiveSupport::Concern.
How include, extend, and prepend actually change the method lookup chain, and why that matters more than memorizing the three keywords.
Ruby ships the Fiber::Scheduler interface but no implementation — async is the production one, and it makes concurrent code look like ordinary blocking Ruby. Falcon turns that into a web server where a request costs a fiber instead of a thread, so one worker holds thousands of slow-I/O connections that Puma's thread pool could never fit.
Collecting results with join/value, why a thread's exception waits silently until you join it, Mutex beyond synchronize, and Fiber's cooperative pause/resume model.
Why Ruby threads give you I/O concurrency but not CPU parallelism, and when Ractors are the tool that actually changes that.
The Hash.new default-value trap that silently shares one object across every key, fetch/merge-with-block/slice/transform_* and Hash#to_proc, then Set: insertion-ordered uniqueness, the full |, &, -, ^ vocabulary, a <=> that compares by subset and returns nil, and add?/delete? returning nil instead of false.
Implement each and get map/select/reduce for free via Enumerable, then drive sequences from the outside with Enumerator — external iteration, custom generators, and .lazy for infinite pipelines.
The closures that make Ruby's Enumerable idiomatic — and the arity/return-semantics difference between proc and lambda that trips up most newcomers.
Keyword-argument separation in 3.0, Ractors, Set becoming core, Prism as the default parser in 3.4, and *nil no longer converting via to_a in 4.0 — the version timeline other Ruby concepts on this platform assume you already know.
Ruby is dynamically but strongly typed, and two very different optional-typing layers sit on top: RBS ships with Ruby, keeps signatures in separate sig/*.rbs files, and never affects runtime — while Sorbet (Stripe) puts sig blocks inside your .rb files and checks them at runtime too, raising a real TypeError on a wrong argument.
The regex features past /pattern/ — %r{} literals, match? vs. the $~ globals, /i /m /x /o, named groups, Hash and block substitution, Regexp.union — plus catastrophic backtracking: why it happens, what Ruby 3.2's linear-time matching does and does not cover, and how Regexp.timeout= turns a pinned CPU into a rescuable exception.
The traps that produce no error at all: a setter without self. becoming a local, a misspelled initialize that never runs, {} vs do...end binding the block to the wrong call — plus source_location, sync = true, and freeze as the tools that turn silent bugs loud.
An object's type is the set of messages it responds to, not its class — plus the explicit (to_s) vs. implicit (to_str) conversion split that decides whether Ruby itself will treat your object as substitutable for a built-in type.
The exception hierarchy, ensure/retry, and why catch/throw is the cheaper tool for jumping out of nested code that isn't an error.
Deconstructing arrays, hashes, and custom objects with case/in — structural matching, binding, guards, and the pin operator.
Tracing vs. sampling profilers, the bias in each timing mode, the MRI memory toolkit from ObjectSpace to memory_profiler, and why a 12x micro-benchmark win can change nothing.
Why YJIT only pays off in long-running processes, what TruffleRuby and JRuby actually trade away for speed and Java interop, and when a runtime switch is the wrong answer.
Why the MRI heap never shrinks after a spike, how to tell bloat from a real leak, and when thread-arena fragmentation and jemalloc actually matter.
Most Ruby that runs in production isn't a request handler — it's a script, a deploy task, or a Rake job. The -n/-p/-a flags, ARGV vs ARGF, OptionParser, ENV's real scoping rules, and Rake's task graph are the half of the language framework code never touches.
Ruby's syntax tolerates almost any formatting choice, which is exactly why real teams run a linter — RuboCop's configurable cops and severities, or Standard's zero-config answer to the same problem, plus how to adopt either on a codebase that's never had one.
The Gemfile says what you're willing to run; Gemfile.lock is what you're actually running. Understanding bundle exec, groups, and version constraints is the difference between reproducible deploys and "works on my machine."
puts vs. p vs. pp, pausing execution with the official debug gem and rdbg, and Pry's REPL-as-navigation model with show-source for reading a gem's real implementation live.