Your domain model is wrong, and your type system knows it — it just isn’t telling you. Classes with nullable fields and boolean flags allow combinations of state that can never exist in the real world. You write defensive checks. You add comments. You write tests for cases that should be structurally impossible. The bugs ship anyway.
Algebraic data types (ADTs) solve this at the compiler level. TypeDiagram takes it further: define your model once in a language-neutral DSL, and generate correct, exhaustive code in nine languages automatically.
What Are Algebraic Data Types?
ADTs are composite types that combine other types to model complex data more precisely than traditional object-oriented classes allow. They come in two forms:
Sum types (also called tagged unions, discriminated unions, or variants) represent mutually exclusive states. A value is one of the possible cases — never two at once.
Product types (records or tuples) combine multiple fields together. A value holds all of the fields simultaneously.
The combination of these two primitives is surprisingly powerful. Most real-world domain logic can be expressed clearly and safely using just sums and products.
The Problem with Traditional Classes
Consider a simple authentication state. A typical class might look like this:
class AuthState {
String? userId
String? errorMessage
bool isLoading
}
This model allows logically impossible combinations: userId populated at the same time as errorMessage, or isLoading true while userId is set. The type system offers no protection against these invalid states. You are left writing defensive checks throughout your codebase, hoping nothing slips through.
An ADT models this correctly in TypeDiagram’s DSL:
union AuthState {
Authenticated { userId: String }
Failed { errorMessage: String }
Loading
}
Now the three states are mutually exclusive by construction. The compiler knows it. You cannot accidentally combine them. And when you handle AuthState with a switch expression, the compiler enforces exhaustiveness — if you add a new state later, every handler site fails to compile until it is updated.
This is not just a Dart pattern. Rust has enums with data. TypeScript has discriminated unions. F# and Haskell have had this for decades. Java, Kotlin, C#, Python, and Go are all converging toward it. The 2025 Python Typing Survey found developers explicitly requesting better support for Result and Option types — sum types by another name.
Why Diagram ADTs?
Once you model your domain with ADTs, two things happen:
- Your domain model becomes the authoritative truth — not documentation written separately from the code.
- That model is inherently structured in a way that renders beautifully as a diagram.
Sum types map directly to inheritance hierarchies in a diagram. Product types map to class boxes with fields. The structure is already there; you are just visualising it.
The problem is that most diagramming tools work the other way around. You draw boxes and arrows, then write code separately, then try to keep both in sync. They go out of sync immediately. The diagram becomes archaeology — a record of what someone thought the code was at some point in the past.
TypeDiagram: Define Once, Generate Everything
TypeDiagram flips this workflow. You write a .td file — a small, language-neutral DSL — that describes your data model using sum types and product types. From that single source of truth, TypeDiagram generates:
- SVG diagrams automatically laid out, no dragging required, consistent across machines and git-versionable
- Source code in nine languages: TypeScript, Python, Rust, Go, C#, F#, Dart, PHP, and Protobuf
The diagram is a side effect. The goal is a model that stays in sync with implementation because it generates the implementation.
This matters for teams working across language boundaries — a backend in Go, a frontend in TypeScript, a mobile client in Dart. Define the domain model once in TypeDiagram, regenerate when the model changes, and every implementation updates together.
Round-Trip Conversion
TypeDiagram also works in reverse. If you have an existing codebase with classes and types already defined, you can convert them back to the .td DSL format. This means you do not need to greenfield a project to get value from it — you can retrofit your existing domain models, generate the diagram you never had, and start using TypeDiagram as the source of truth going forward.
The Development Workflow
The VS Code extension provides syntax highlighting, hover documentation, and a live SVG preview as you edit your .td file. The feedback loop is immediate: change a field, see the diagram update, regenerate the code.
This is the workflow that diagramming tools have always promised but rarely delivered. The reason it works here is that TypeDiagram is not a diagramming tool with code export bolted on — it is a code-generation tool where the diagram is a first-class output of the same process.
Making Impossible States Impossible
The phrase “make impossible states impossible” has become a rallying cry in functional programming communities, but the insight applies across every language and paradigm. Invalid combinations of data are a class of bug that cannot be caught by tests alone — because the tests have to know what combinations are invalid in order to test for them. The type system can know this structurally, without you having to enumerate every bad case.
ADTs are how you encode that knowledge into the type system. TypeDiagram is how you make that knowledge visible, shared, and automatically propagated into every language your team uses.
Getting Started
Visit typediagram.dev, install the VS Code extension, and write your first .td file. If you have an existing codebase, try the round-trip conversion to see what your domain model looks like when it is properly expressed as ADTs and rendered as a diagram.
The goal is not a prettier diagram. The goal is a domain model that is correct by construction — one where the compiler is your first line of defence, and the diagram is always an accurate reflection of the code because it generates the code.
NimbleSite helps engineering teams improve Developer Experience, including tooling, modelling practices, and cross-language architecture. Get in touch if your team is working through domain modelling challenges.
