Is EK9 statically or dynamically typed?

← Getting Started · Ref: Q27

EK9 is statically and strongly typed. All types are resolved at compile time. There are no runtime type errors from incorrect assignments, no 'undefined is not a function', no AttributeError because a method does not exist. If it compiles, the types are correct.

But EK9 does not feel like a statically typed language. Type inference means you rarely write type annotations for local variables:

  count <- 42
  name <- "Steve"
  ratio <- 3.14
  items <- List() of String

The compiler infers Integer, String, Float, and List of String. You get the safety of static typing with the brevity of dynamic typing.

WHEN YOU MUST DECLARE TYPES

Type annotations are required in specific places where inference is not possible or where clarity matters:

  Parameters: -> operand as Integer (always required)
  Return values: <- result as String (always required)
  Explicit base types: shape as Shape: Circle() (when you want the variable typed as the base)
  Properties: name <- String() (class/record fields)

WHEN YOU SHOULD NOT DECLARE TYPES

For local variables where the type is obvious from the right-hand side, let inference do the work:

  count <- 42 (not count as Integer: 42)
  name <- "hello" (not name as String: "hello")
  today <- Date() (not today as Date: Date())

Declaring the type when inference can determine it is redundant and reduces readability.

WHY NOT DYNAMIC TYPING?

Dynamic languages (Python, JavaScript, Ruby) defer type checking to runtime. This means:

  Python: name = 42; name.upper() compiles fine but crashes at runtime with AttributeError
  JavaScript: '5' - 3 gives 2 but '5' + 3 gives '53'. Type coercion rules are inconsistent and produce silent bugs.
  Ruby: method_missing can intercept any call, making it impossible to know at edit time whether a method exists

These bugs are found in production, not during development. Every dynamic language eventually grows optional type systems (Python type hints, TypeScript, Sorbet for Ruby) because runtime type errors are too expensive in production.

EK9 starts with static types so you never need to retrofit safety later. The compiler catches type mismatches, missing methods, incorrect operator usage, and incompatible assignments before your code ever runs.

STRONG TYPING

EK9 is also strongly typed. There are no implicit conversions except the single-level promote operator (see Q25). You cannot accidentally treat a String as an Integer or a Boolean as a number. The type system enforces boundaries and the compiler reports violations as errors, not warnings.

COMPILE-TIME GUARANTEES

Because EK9 is statically typed, the compiler can guarantee:

  Every method call resolves to an actual method
  Every operator is defined on the type it is applied to
  Every assignment is type-compatible (or has a valid promotion)
  Every function parameter matches the expected type
  Every return value matches the declared return type
  Generic types are fully resolved and checked

See also Q22 (How do I declare a variable?) for type inference syntax and Q25 (What is the promote operator?) for the single automatic type widening mechanism.

See Q23 for basic types. See Q29 for unset variables. See Q257 for constrained types that restrict values at the type level.

Example

defines module qa.static.typing

  defines function

    addValues() as pure
      ->
        first as Integer
        second as Integer
      <- result as Integer: first + second

  defines program
    StaticTypingDemo()
      stdout <- Stdout()

      // Type inference - compiler knows the types
      count <- 42
      name <- "Steve"
      ratio <- 3.14
      items <- List() of String

      items += "one"
      items += "two"

      // Explicit type when you want a base type
      floatValue as Float: count

      // All types checked at compile time
      result <- addValues(count, 10)
      stdout.println(`${name}: ${result}`)
      stdout.println(`Ratio: ${ratio}`)
      stdout.println(`Items: ${items}`)
      stdout.println(`Float: ${floatValue}`)

Common mistakes

E50060 — EK9 resolves methods on the type they are defined on. Calling a standalone function as if it were a method on Integer would trigger method-not-found. See ek9 -h E50060 for details.

Incorrect:

result <- count.addValues(10)

Correct:

result <- addValues(count, 10)
Other ways to ask this
  • Does EK9 check types at compile time or runtime?
  • How does type inference work in EK9?
  • Do I have to declare types for every variable in EK9?
  • Is EK9 strongly typed?

Coming from another language?

Python: dynamically typed, type hints optional (PEP 484), runtime AttributeError. JavaScript: dynamically typed, TypeScript added for safety, implicit coercion ('5'+3='53'). Ruby: dynamically typed, Sorbet added. Java: statically typed but verbose (var added Java 10). Kotlin: statically typed with inference. Rust: statically typed with inference. Go: statically typed with := inference. EK9: statically typed with <- inference, feels dynamic, catches everything at compile time.

Keywords: beginner, compile, start, dynamic, typed, type, runtime, anonymous, inference, check, intro, first, typing, capture, migrate, safety, infer, strong, static, closure