100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

The Type System and Abstract Types

Understand Julia's single-rooted type hierarchy, how abstract types organize dispatch, how multiple dispatch selects methods, and how to diagnose type instability.

Arrays & TypesAdvanced11 min readJul 10, 2026
Analogies

Julia's Type Hierarchy

Every value in Julia has exactly one type, and every type in Julia participates in a single-rooted hierarchy where Any sits at the top as the supertype of all types. Types are split into abstract types, which exist purely to organize the hierarchy and cannot be instantiated (you can never create a value whose type is literally Real or Number), and concrete types, which are the leaves of the tree and can actually be instantiated — Float64, Int64, and String are all concrete. The subtype operator <: expresses these relationships, so Float64 <: AbstractFloat <: Real <: Number <: Any all hold true, and functions like supertype(Float64) and subtypes(Real) let you inspect the hierarchy interactively.

🏏

Cricket analogy: Julia's type hierarchy is like the ICC's format classification — 'Cricket' (Any) is the broad root, 'Limited Overs' is an abstract grouping you can't literally play, and 'T20' or 'ODI' are the concrete formats actually played on the field, just like Float64 or Int64 are the concrete types actually instantiated.

Defining Abstract Types and Subtyping

You declare an abstract type with abstract type Animal end, and then attach concrete or further-abstract types to it using the <: subtyping syntax, as in struct Dog <: Animal name::String end and struct Cat <: Animal name::String end. This hierarchy exists purely for organizing dispatch and reasoning about code — Animal itself carries no fields and can't be constructed — but it lets you write functions like function speak(a::Animal) that accept any current or future subtype of Animal, and use isa(fido, Animal) or fido isa Dog to check membership in the hierarchy at runtime.

🏏

Cricket analogy: abstract type Bowler end with struct Pacer <: Bowler and struct Spinner <: Bowler is like the team classifying all its bowling options under one selection category so the captain's function to pick an over works for any current or future bowler type without rewriting the team sheet logic.

julia
abstract type Animal end

struct Dog <: Animal
    name::String
end

struct Cat <: Animal
    name::String
end

speak(a::Dog) = "$(a.name) says Woof!"
speak(a::Cat) = "$(a.name) says Meow!"

function greet(a::Animal)
    println(speak(a))
end

greet(Dog("Fido"))     # dispatches to the Dog method
greet(Cat("Whiskers")) # dispatches to the Cat method
fido_is_animal = Dog("Fido") isa Animal   # true

Multiple Dispatch

Multiple dispatch means a function call's implementation is chosen based on the types of all its arguments, not just one privileged 'receiver' as in classical single-dispatch object orientation — calling collide(a, b) can select a different method depending on whether (a, b) is (Circle, Circle), (Circle, Rectangle), or (Rectangle, Rectangle). Julia resolves this at each call by finding the most specific applicable method among all methods defined for that function name, and calling methods(f) lists every method currently defined for a generic function f; when two methods are equally specific for a given call and neither is more specific than the other, Julia raises a MethodError for ambiguity rather than guessing.

🏏

Cricket analogy: Multiple dispatch on collide(a, b) is like a DRS review's outcome depending on the combination of both the mode of dismissal claimed AND the type of delivery bowled simultaneously — a caught-behind review is judged differently from an LBW review, just as dispatch differs per argument-type combination.

You can inspect what method Julia will actually call for given argument types using @which f(args...), and see the full list of applicable methods with methods(f) — both are invaluable for understanding dispatch behavior when debugging unexpected method selection.

Type Stability and Performance

A function is type-stable when the type of its return value can be inferred from the types of its inputs alone, without depending on runtime values — this lets Julia's compiler generate specialized machine code ahead of time instead of falling back to slow, boxed, dynamically-dispatched operations. The macro @code_warntype prints the compiler's inferred types for a function, highlighting any Any or Union types in red as a warning sign of instability; a common cause is a container like Vector{Any} (or an abstract-typed struct field) forcing every element access to be dynamically typed, and a common fix is ensuring containers and fields use concrete, consistent element types.

🏏

Cricket analogy: A type-stable function is like a bowler whose delivery is always predictably a yorker at the death overs given the match situation, letting the batsman's brain 'compile' a fixed response in advance; an unpredictable mixed-delivery bowler forces the batsman into slower, reactive decision-making each ball, like dynamic dispatch.

A Vector{Any} — an array whose element type is the abstract Any — is one of the most common accidental performance bugs in Julia. Every read from such an array requires the compiler to insert a dynamic type check and dispatch, often making loops 10-100x slower than the equivalent operation on a concretely-typed Vector{Float64} or Vector{Int}; prefer building typed containers or using Union{T,Nothing} for the narrower case of 'a value or nothing' instead of falling back to Any.

  • Every Julia value has exactly one type, all rooted under the single supertype Any.
  • Abstract types organize the hierarchy and cannot be instantiated; concrete types are the leaves you actually construct.
  • The <: operator expresses subtyping; supertype() and subtypes() let you inspect the hierarchy.
  • abstract type declarations plus <: let you write functions that generically accept any current or future subtype.
  • Multiple dispatch selects a method based on ALL argument types, not a single privileged receiver.
  • Ambiguous method calls (no single most-specific method) raise a MethodError rather than guessing.
  • @code_warntype reveals type instability (Any/Union return types), a major cause of slow Julia code.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#JuliaStudyNotes#TheTypeSystemAndAbstractTypes#Type#System#Abstract#Types#StudyNotes#SkillVeris#ExamPrep