Why Odin & Thinking in Odin

Welcome to Odin. Before we write a single line of code, let us spend a little time understanding why this language looks the way it does. Odin is not a mystery box: every rule has a reason, and once you know the reasons, the syntax stops feeling strange and starts feeling obvious.

Think of this page as the map you unfold before a long walk. You do not need to memorise it. You just need to know the shape of the territory: what Odin optimises for, what it deliberately leaves out, and which habits will make the rest of this track feel natural.

Meet Odin

Odin is a general-purpose, statically typed, compiled programming language. You write a .odin file, the compiler reads it, and you get a native executable — no virtual machine, no interpreter, no surprise garbage collector pausing your program in the middle of a frame.

What Odin Is

In practical terms, Odin is the language you reach for when you want C-level control without C-level pain:

  • Fast native code. The Odin compiler produces machine code through LLVM, the same mature backend used by many production compilers.
  • Small, regular syntax. The whole language is designed to fit in a person's head — the author's stated goal is that the entire specification should be memorisable by a mere mortal.
  • Explicit over implicit. Memory, errors, and control flow are visible in the source instead of hidden behind machinery.
  • Batteries included. A high-quality core: library and a large vendor: collection of bindings ship with the compiler.
  • Friendly to C. You can call C libraries directly, which keeps decades of existing code available to you.
  • Open source under the zlib licence — one of the most permissive licences in existence.

If you already know Python, C, Go, or C++, you will recognise a lot here, and that is on purpose. Odin borrows deliberately from Pascal, C, Go, Oberon-2, Newsqueak, and GLSL.

Who Made It, and Why

Odin was created by Bill Hall, known in the community as Ginger Bill. The project began one evening in late July 2016, after one too many frustrating hours spent programming in C++. He first tried to write a preprocessor that would add missing capabilities to C — and concluded that was a dead end. The alternative was to start again with a clean sheet, and that is exactly what happened.

The design idols behind the language are Niklaus Wirth (Pascal, Modula, Oberon) and Rob Pike (Go, Plan 9). If those names are familiar, the rest of Odin's taste will make sense immediately: a handful of small, orthogonal building blocks that compose well, rather than a wide surface of special cases.

A word about C++. Odin is often described as "an alternative to C++", but it is not a C++ successor and not a C++ subset. It has its own clean syntax and its own ideas. It simply occupies the same niche: systems and application programming where performance and control matter.

The Guiding Principles

Every language is a pile of compromises. What makes a language feel coherent is that the compromises are made in a consistent direction. Odin's direction is written down openly by its author — five ideas that decide which features get in and which stay out.

Simplicity and Readability

Odin is minimal on purpose: "there ought to be one way to write something". When a language offers three ways to do the same thing, every reader has to understand all three, and every codebase slowly splits into dialects. Odin would rather give you one good way and stop there.

A related line from the design notes is worth remembering, because it explains a great deal of what follows:

Code is about expressing algorithms — not the type system.

That is why Odin has no operator overloading, no inheritance hierarchies, and no clever metaprogramming tricks in ordinary code. The type system is there to describe your data clearly, not to become the subject of the program.

Orthogonality — Small Pieces That Combine

An orthogonal design means the features do not interfere with each other. for works with ranges, arrays, slices, maps, and enumerations without needing a special loop for each. A procedure can return several values whether it is called from a loop, a when block, or another procedure. You learn one feature and it keeps working in new places.

This is also why Odin's syntax is so uniform: declarations put the name first and the type after, everywhere. Once you have read one declaration, you can read all of them.

The Joy of Programming

The fourth principle is refreshingly human: we got into programming because we love to solve problems, so our tools should bring us joy while doing it. In practice that means fast compiles, clear error messages, no build-system archaeology, and a standard library that is pleasant to call. Fun is not decoration here — it is a design requirement.

Four guiding principles — simplicity, orthogonality, explicitness, and joy — feeding into the Odin language and out to readable, predictable systems code
Four principles shape every decision in the language: they decide which features enter Odin, and the result is code you can read like a page of prose.

Nothing Important Is Hidden

Most modern languages hide useful machinery behind friendly syntax: a + that secretly calls a function, a property that secretly runs a query, an exception that secretly unwinds your stack. Odin's stance is the opposite — if a line of code does something, you should be able to see it.

No Methods, No Inheritance

Odin has no methods and no classes. Data lives in types, behaviour lives in procedures, and the two are joined by ordinary calls. There is no hidden table of functions attached to your struct, so a struct is exactly as big as its fields say it is — nothing more.

Here is the whole idea in a few lines. Notice how the procedure takes the data as a normal parameter:

import "core:math"

// A type is just data. No hidden function table, no constructor, no destructor.
Vector2 :: struct {
    x, y: f32,
}

// Behaviour is a plain procedure that TAKES the data.
// Reading the call `length(v)` you can see nothing secret can happen.
length :: proc(v: Vector2) -> f32 {
    return math.sqrt(v.x*v.x + v.y*v.y)
}

If you are used to object-oriented languages this may feel like a step backwards for a week, and like fresh air for the month after that. Every call site tells you exactly which procedure runs.

No Exceptions

Odin has no try, no catch, and no throwing. Failure is reported as an ordinary value in the result of a procedure, so a function call can never silently teleport you out of the code you are reading.

The simplest form is a second return value that says whether the operation worked:

import "core:fmt"

// The procedure reports failure in its RESULT, not by throwing.
// We ignore the real parsing for now — the shape of the signature is the lesson.
parse_age :: proc(text: string) -> (age: int, ok: bool) {
    return 0, false
}

main :: proc() {
    age, ok := parse_age("42")

    // The check is mandatory and visible: nothing can skip this line for us.
    if !ok {
        fmt.eprintln("that is not an age")
        return
    }
    fmt.println("age is", age)
}

Odin also has or_return to save you from writing those checks in a long chain of calls. We will meet it properly in the Errors, defer & or_return lesson — for now just remember: errors are values you can hold, inspect, and pass along.

Allocation Is Visible

There is no garbage collector in Odin. Memory comes from an allocator — a strategy you choose — and the current one lives in a small structure called the context. Built-ins such as make and new take memory from that context unless you hand them an allocator yourself.

The point is not that you must write more code. The point is that nothing allocates behind your back, and you always have a place to look:

import "core:fmt"

main :: proc() {
    // make() uses the CONTEXT's allocator. Nothing is secret:
    // you could read it right here as `context.allocator`.
    numbers := make([dynamic]int, 0, 8)
    defer delete(numbers)   // hand the memory back when this scope ends

    // append may grow the buffer — using that very same allocator.
    append(&numbers, 10, 20, 30)
    fmt.println(numbers[:], len(numbers), cap(numbers))
}
Thinking in Odin: when memory is explicit, ownership becomes a normal part of your design instead of a surprise in production. This is why Odin programs stay predictable: allocation, cleanup, and errors are all visible in the source you are reading.

Built for Data on Modern Hardware

Here is where Odin stops being "a nicer C" and develops its own opinion. Modern CPUs spend most of their time waiting for memory, not computing. A program that lays its data out well can be several times faster than an identical algorithm laid out badly. Odin treats that as a language-level concern.

Struct-of-Arrays, On Request

Normally, an array of records stores each record whole — field after field, one record after another. This is called AOS, array-of-structs, and it is what almost every language gives you.

SOA, struct-of-arrays, flips the layout: every field becomes its own contiguous array. A loop that only reads x then touches nothing but x values packed tightly together — which is exactly what the cache wants. Odin has built-in support for declaring a collection in SOA form, so you do not have to hand-split your data.

We will write real SOA declarations in the Data-Oriented Design (SOA/AOS) lesson, where we can compare both layouts side by side and measure the difference. For now, simply know that the layout of your data is a first-class idea in Odin.

Array Programming

Odin lets arithmetic work on whole arrays of numbers, element by element. The loop still happens — but it is not cluttering your source, and the compiler is free to vectorise it.

import "core:fmt"

main :: proc() {
    // Array programming: the same operator works on whole arrays of floats.
    // `c := a * b` multiplies the two arrays ELEMENT BY ELEMENT — no loop in sight.
    a: [4]f32 = {1, 2, 3, 4}
    b: [4]f32 = {10, 20, 30, 40}
    c := a * b

    fmt.println(c)   // [10, 40, 90, 160]
}

The same idea extends to swizzles and SIMD types, which is how Odin reaches the performance of hand-written graphics and scientific code without leaving the language.

Batteries Included

A language is only as productive as its library. Odin ships two: one you use every day, and one that connects you to the outside world.

The core: Library

Anything imported as core:something is part of the standard library that comes with the compiler — no downloads, no package manager, no version conflicts. It covers the ground you would expect: formatting, strings, UTF-8, slices, dynamic arrays, maps, math, time, files, processes, threads, and reflection.

// Every core package is imported by name, with the `core:` prefix.
import "core:fmt"        // printing and formatting
import "core:strings"    // string building and searching
import "core:os"         // files, environment, exit codes

The vendor: Library

Imports beginning with vendor: are ready-made, officially maintained bindings to popular third-party libraries. Odin provides its own bindings for the major graphics APIs — OpenGL, Vulkan, Direct3D 11 and 12, Metal, wgpu, and WebGL — and for widely used libraries such as SDL2, GLFW, raylib, miniaudio, and microui.

// vendor: gives you bindings maintained by the Odin team itself,
// so the headers, types, and calling conventions already match.
import "vendor:raylib"   // games, graphics, audio
import "vendor:glfw"     // windows, input, OpenGL contexts

And when a library is not covered, Odin's foreign interface lets you call C directly. That is the subject of Foreign Interface (C Interop) later in the track.

Odin Next to C, C++, and Rust

You do not need to know any of these languages to learn Odin, so feel free to skim this section and come back to it later. It is here to answer one honest question: why would anyone build another systems language?

A Feature Comparison

ConcernCC++RustOdin
Memorymalloc / freeManual plus RAII destructorsOwnership checked by the compilerManual, through explicit allocators and a context
FailureReturn codes, easy to ignoreExceptions, sometimes expectedResult with the ? operatorErrors as values, ok and or_return
Reuse modelFunctions onlyClasses and inheritanceTraits and genericsProcedures, structs, parametric polymorphism
MetaprogrammingPreprocessor macrosTemplates and macrosMacros and genericsCompile-time when blocks
BuildMake, CMakeMake, CMake, MSBuildCargoBuilt into the compiler: odin build
C interopNativeextern "C"Through FFI bindingsDirect, with zero-cost bindings
FeelSmall but unforgivingEnormous surface areaStrict but safe by defaultSmall, regular, and explicit

The pattern is deliberate. Odin keeps C's performance and closeness to the machine, borrows C++'s control and Go's simplicity, and avoids the complexity that makes large systems hard to reason about.

Thinking in Odin

Philosophy is only useful if it changes your hands. Here are the five habits that will make this track — and every Odin program you write afterwards — feel natural.

Five Habits to Carry Forward

  1. Read the signature before the body. Odin declares everything in the same order — name first, then type — so name :: proc(args) -> results already tells you what memory is used and which failures are possible.
  2. Ask "where does the memory come from?" Before using anything that grows, find its allocator and decide who frees it. If you cannot answer, the design is not finished.
  3. Treat failure as a value. Nothing throws. Check the second return value, or propagate it with or_return, and your program stays readable top to bottom.
  4. Move work to compile time when you can. If a value or a branch is fixed while the compiler runs, when and parametric polymorphism will remove it from the final binary entirely.
  5. Design the data, then the code. Choose a layout that matches how you actually loop — contiguous, cache-friendly, and SOA where it pays off. In Odin this is a choice you make, not one the language makes for you.
You are ready. Nothing here required you to write code — that was intentional. Next we install the toolchain and run our first Odin program, and you will find that all five habits are already visible in a five-line file.

Continue with Toolchain & Environment →