Programming Techniques

Reflection & Introspection

A program examining and modifying its own structure at runtime.

Overview

Reflection is a program's ability to examine and modify its own structure and behaviour at runtime. Introspection is the read-only subset, inspecting types, methods, and fields without changing them. Together they power debugging tools, serialisers, dependency injection containers, and testing frameworks.

Origin

Reflection was formalized in Brian Cantwell Smith's 1982 PhD thesis on procedural reflection. Smalltalk implemented a full reflective system in the 1970s. Java's java.lang.reflect package (1.1, 1997) brought it to mainstream enterprise programming.

Examples

Introspection in Ruby

class Order
  attr_accessor :id, :status, :total

  def complete!
    self.status = 'completed'
  end
end

order = Order.new
order.class          # => Order
order.class.ancestors  # => [Order, Object, Kernel, BasicObject]

Order.instance_methods(false)   # => [:id, :id=, :status, :status=, :total, :total=, :complete!]
order.respond_to?(:complete!)   # => true

# Serialise all attributes dynamically
Order.instance_methods(false)
  .select { |m| !m.to_s.end_with?('=') }
  .each_with_object({}) { |m, h| h[m] = order.send(m) }

JavaScript runtime type inspection

function inspect(obj) {
  const proto     = Object.getPrototypeOf(obj)
  const ownKeys   = Object.getOwnPropertyNames(obj)
  const protoKeys = Object.getOwnPropertyNames(proto)
    .filter(k => k !== 'constructor')

  return {
    constructor: obj.constructor.name,
    ownProperties: ownKeys,
    methods: protoKeys,
    isExtensible: Object.isExtensible(obj),
  }
}

// TypeScript reflect-metadata for DI containers
import 'reflect-metadata'

@Injectable()
class UserService {
  constructor(private repo: UserRepository) {}
}

const paramTypes = Reflect.getMetadata('design:paramtypes', UserService)
// => [UserRepository], the DI container uses this to inject the dep

Use Cases

  • 01Dependency injection containers discovering constructor parameter types
  • 02Serialisers mapping object fields to JSON/XML without explicit field lists
  • 03Test frameworks inspecting objects to generate assertions or mocks
  • 04Admin panels and ORMs that generate forms from model schemas
  • 05Plugin systems dynamically loading and inspecting extension classes

When Not to Use

  • //When the structure is known at compile time, explicit code is faster and easier to reason about
  • //In performance-critical hot paths: reflection bypasses compiler optimisations
  • //When it enables callers to reach private internals, prefer explicit public APIs

Technical Notes

  • Reflection breaks encapsulation by design, it is a power tool, not default practice
  • TypeScript's reflect-metadata and emitDecoratorMetadata enable type-safe reflection in compiled output
  • Ruby's send bypasses method visibility (send :private_method). Use public_send to respect visibility
  • In Java, reflection calls are significantly slower than direct method calls. JIT can inline direct calls but cannot predict reflected calls