How does EK9 limit inheritance depth and what happens when exceeded?

← Type Hierarchy Constraints · Ref: Q697

EK9 limits the depth of class inheritance hierarchies. Excessively deep hierarchies are a code smell that indicates design problems.

DEPTH LIMIT (E11013)

The compiler tracks the number of levels in an inheritance chain. When the chain exceeds the threshold, E11013 is raised. This forces redesign toward flatter hierarchies.

WHY LIMIT DEPTH

Deep hierarchies create problems:
1. Fragile base class: changes at the top cascade unpredictably
2. Understanding difficulty: must read 6+ classes to understand behavior
3. Constructor complexity: super() chains become error-prone
4. Testing complexity: each level adds testing permutations

CORRECT APPROACH

Prefer composition and trait-based design over deep inheritance:

  // Instead of: A -> B -> C -> D -> E -> F
  // Use: F with trait of Auditable, Printable, Validatable

ACCEPTABLE DEPTH

A 3-5 level hierarchy is typically acceptable:

  Base -> Intermediate -> Concrete
  Shape -> Polygon -> Triangle

See Q611 for hierarchy validation summary. See Q677 for abstract implementation chain. See Q602 for circular hierarchy detection.
See Q729 for inheritance depth boundary example.

Example

defines module qa.typehierarchy.depthlimit

  defines trait

    //Traits provide behavior without deep hierarchies
    Auditable
      auditLog() as pure
        <- rtn as String: "audited"

    Printable
      printLabel() as pure
        <- rtn as String: "printable"

  defines class

    //Level 1: abstract base
    Entity as abstract
      entityType() as pure abstract
        <- rtn as String?
      default operator ?

    //Level 2: intermediate with common behavior
    NamedEntity extends Entity as open
      entityName <- String()

      NamedEntity()
        -> entityName as String
        this.entityName: entityName

      getName() as pure
        <- rtn as String: entityName

      override entityType() as pure
        <- rtn as String: "named"

      default operator ?

    //Level 3: concrete with traits for additional behavior
    Customer extends NamedEntity with trait of Auditable, Printable
      customerId <- String()

      Customer()
        ->
          customerName as String
          customerId as String
        super(customerName)
        this.customerId: customerId

      getCustomerId() as pure
        <- rtn as String: customerId

      override entityType() as pure
        <- rtn as String: "customer"

      override auditLog() as pure
        <- rtn as String: `customer:${customerId}`

      override printLabel() as pure
        <- rtn as String: `${getName()} (${customerId})`

      default operator ?

  defines program

    InheritanceDepthLimitDemo()
      stdout <- Stdout()

      customer <- Customer(customerName: "Alice", customerId: "C001")
      stdout.println(customer.printLabel())
      stdout.println(customer.auditLog())
      stdout.println(customer.entityType())

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

stdout.println(customer.printLabel().toUpperCase())

Correct:

stdout.println(customer.printLabel())
Other ways to ask this
  • What is E11013 inheritance depth exceeded?
  • How deep can class hierarchies be in EK9?
  • Is there a maximum inheritance depth?
  • Why does EK9 limit inheritance chain length?

Coming from another language?

Java: no depth limit (static analysis tools may warn). Python: MRO handles deep hierarchies but they are a smell. C++: no depth limit. Kotlin: no depth limit. Rust: no inheritance, uses trait composition. Go: no inheritance, uses embedding. EK9: compile-time depth limit to enforce good design.

Keywords: E11013, chain, limit, depth, inheritance, design, composition, hierarchy, circular, type