Type Inference (Part 1) A tutorial series on type inference begins, covering the Damas-Hindley-Milner type system, unification, bidirectional type-checking, Algorithm J, row polymorphism, newtype declarations, type annotations, mutually recursive declarations, and side-effects/value restriction. The series assumes familiarity with abstract syntax trees, type systems, and OCaml, with code available in a companion repository. Type Inference Part 1 June 25, 2026 june-25-2026 Welcome to part 1 of my tutorial series on type inference This post will cover - The Damas-Hindley-Milner type system - Unification - Bidirectional type-checking - Algorithm J - Row polymorphism - Newtype declarations - Type annotations - Mutually recursive declarations - Side-effects / value restriction We assume you understand - Abstract Syntax trees - Vaguely understand what a type system is. - Have some familiarity with reading OCaml. All the code in this article can be found in the companion repository https://github.com/smasher164/hm tut https://github.com/smasher164/hm tut . Each file under the lib directory corresponds to the features added in each section of this article. Type inference is the process of taking some expression that represents part of a program and returning its type. If the expression is invalid, i.e. it does some invalid operation according to the rules of your language like adding a bool to a string , type inference will fail. If the expression lacks the sufficient information to return a type, i.e. it is missing a binding in its scope or type information for a binding, type inference will fail. In this way, type inference is a superset of type-checking. Another term that is used synonymously is type reconstruction. What type inference is useful for: - Reducing the number of type annotations you have to write. - Validating that a program is type-safe, i.e. the program is safe to compile and execute and won't encounter a TypeError. - Using types to generate code. For example, knowing the types of a struct's fields can help you determine the struct's layout. Aside The formal definition of type safety is that "well-typed programs do not get stuck". What this means is if you have an interpreter for a language, and you pass it a program that's been successfully type-checked, it will never reach an unexpected state where it doesn't know how to evaluate something. For example, a multiplication operator in a VM might expect there to be two operands on the stack. Having fewer than two operands would be unexpected. Different kinds of type systems have different requirements and limitations to type inference. For instance, subtyping might allow a List