Mojo is turning into an amazing language: it unlocks maximum performance while still offering modern tooling and language features that make it a joy to write.
Mojo started extremely Pythonic, and it still is, but it has also developed its own identity, which is great. However, in my opinion, __dunder__ methods don’t fit as well in a modern language like Mojo.
In this post, I’ll focus on these dunder methods:
__getitem__(indexing with[])__len__(length withlen())__contains__(membership within)
Problems
Discoverability
Dunder methods are effectively hidden from a developer’s view. When you type my_list. in your IDE, autocomplete usually shows __getitem__, __len__, or __contains__ near the end, because they’re treated as “magic” methods and not something you call directly.
If you don’t already know these methods exist (and what they do), you won’t discover them through normal API exploration. That creates a steeper learning curve for new developers and makes it harder to understand what a type can do.
For example, if you have a custom Vector type, how do you discover that you can use [] for indexing? You can’t, you have to guess, try it, or read the documentation. That’s a mediocre developer experience in 2026.
IDE hover docs
All three examples above ([], len(), in) don’t show hover documentation in IDEs:
data[0]: no hover docs at all"key" in data: no hover docs at alllen(data): docs for globallen()function, not specific to the type ofdata
This means developers have to:
- Know that
[]maps to__getitem__ - Manually search for
__getitem__in the documentation - Read and understand the method signature
Compare that to my_list.get(0), where hovering directly shows the method’s documentation, parameters, and return type.
Global functions vs methods
Functions like len() are global rather than methods on the object. That creates several issues:
- Discovery: How do you know if
len()works on a type? You have to try it and see if it errors. How do you even know which other global functions exist that operate on your type? You have to read documentation or guess. - Namespacing: Global functions pollute the namespace and can lead to naming conflicts.
- Inconsistency: Some operations are methods (
data.append()), others are global functions (len(data)). That’s confusing. - Type safety: With methods, the IDE can suggest only valid operations. With global functions, you often won’t know until you try.
The modern approach (Rust, Swift, etc.) is to prefer methods. data.length() or data.len() makes it explicit that the operation belongs to the object.
Unsafe by default
Indexing with [] typically panics or crashes when an index is out of bounds.
That’s unsafe by default.
A better approach would be to return an Optional/Result (or provide a default value). For performance critical code, you could still expose an unsafe version like data.get_unchecked(10), but the safe version should be the default and easiest to use.
Ambiguous across types
The same syntax means different things for different types:
- For lists:
data[0]gets an element - For dicts:
data["key"]gets a value by key - For strings:
data[1:5]returns a slice - For custom types:
data[x, y]might mean 2D indexing
This overloading makes the language harder to understand and reason about. Explicit method names would be clearer:
data.get_at_index(0)— get element at indexdata.get_value("key")— get value by keydata.slice(1, 5)— get substring slicedata.get_2d(x, y)— get element in a 2D structure
What if we rethink dunder methods?
Instead of relying on “magic” dunder methods and operator overloading, what if Mojo embraced explicit, discoverable methods?
Proposed alternatives
Indexing
Explicit methods for indexing:
data.get(0)data.get_or(0, default_value)data.get_unchecked(0)(unsafe, no bounds checking)
Length
data.length()ordata.len()
Membership
data.contains(value)
Benefits
- Autocomplete works: type
data.and see available operations - Hover documentation works: hover over
.get()and see the docstring, params, and return type - Explicit is better than implicit: clear method names over magic operators
- Safe by default: methods can return
OptionalorResult - Consistency: operations are methods, not special cased globals
- Extensibility: easy to add variants like
.get_mut(),.get_unchecked(), etc.
Addressing counterarguments
“But [] is familiar and widely used!”
Some might argue that indexing with [] is familiar and widely used across languages. That’s true—but sometimes we should be willing to rethink old conventions to improve clarity, safety, and developer experience.
Familiarity doesn’t mean optimal. Consider:
- C had manual memory management (familiar) → Rust introduced ownership (better)
- JavaScript had
var(familiar) →letandconstare now standard (better) - Many languages had
null(familiar) → Modern languages useOptional(better)
“It’s more verbose!”
data.get(5) is only three characters longer than data[5], but it’s:
- More explicit and clear
- Shows up in autocomplete
- Has hover documentation