Reflection is the ability of a program to inspect, and sometimes modify, its own structure and behaviour at runtime.
It is commonly used to:
- inspect types, methods, fields, modules, or attributes
- discover capabilities dynamically
- build frameworks, serializers, ORMs, dependency injection containers, and test tools
- invoke code by name instead of by static references
Reflection is related to, but not identical to:
- Introspection: reading information about program structure
- Metaprogramming: generating or transforming code, sometimes at compile time instead of runtime
- RTTI: runtime type information, usually a narrower feature than full reflection
Tradeoffs
Benefits:
- flexible and dynamic
- useful for generic tooling and frameworks
- reduces boilerplate in some cases
Costs:
- weaker static guarantees
- harder to reason about and refactor safely
- runtime overhead is often higher
- API misuse can fail later, at runtime
Comparison Between Major Languages In This Vault
| Language | Reflection support | Typical style | Notes |
|---|---|---|---|
| Python | Strong runtime introspection and reflection | dynamic and ergonomic | getattr, setattr, hasattr, type, inspect, decorators, and metaclasses make reflection common |
| Javascript | Strong runtime introspection | prototype-based and dynamic | properties can be inspected and modified easily; Reflect and Proxy support meta-level operations |
| Typescript | Limited at runtime | mostly compile-time typing | TypeScript types are erased at runtime, so reflection usually falls back to JavaScript objects or decorator metadata |
| Java | Strong standardized runtime reflection | class-based and explicit | java.lang.reflect is widely used by frameworks such as serializers and dependency injection containers |
| Ruby | Very strong runtime reflection | highly dynamic and metaprogramming-heavy | methods and classes are easy to inspect and redefine; reflection is part of idiomatic Ruby |
| Go | Moderate runtime reflection | explicit and conservative | the reflect package is powerful but verbose; often used in encoding, testing, and framework code |
| C++ | Limited built-in reflection | mostly compile-time techniques | traditional C++ relies more on templates, RTTI, and macros than on rich runtime reflection |
| Rust | Intentionally limited runtime reflection | static and safety-focused | favors traits, enums, generics, and macros over dynamic reflection |
| Elixir | Strong introspection on BEAM metadata | functional and runtime-aware | modules, functions, and runtime code loading are inspectable, though the style differs from OO reflection |
| Haskell | Limited runtime reflection | type-driven and compile-time-heavy | usually prefers typeclasses, generics, and advanced type-level programming instead |
Rough Spectrum
Languages with more dynamic reflection:
Languages with more limited or deliberate reflection:
When Reflection Fits
Reflection is a good fit when you are building:
- serializers and deserializers
- test runners
- plugin systems
- schema or form generators
- dependency injection containers
- developer tooling
It is usually a poor fit when:
- static dispatch is simpler
- correctness depends on compile-time guarantees
- performance is critical
- the same result can be achieved with clearer explicit code