Arun Pandian M

Arun Pandian M

Android Dev | Full-Stack & AI Learner

From Nothing to Arrows: Objects and Simple Graphs

“Every complex system that works evolved from a simple system that worked.” — John Gall

Category theory doesn’t begin with abstractions or equations. It begins much like programming itself: with nothing… and then with arrows.

Before we talk about fancy ideas like functors or monads, we need to understand how structure is born.

A Small Story: Starting from Nothing

Imagine opening a brand-new Kotlin project for the first time.

No files. No classes. No functions.

Just a silent editor and a blinking cursor.

At first, it feels useless — there’s nothing to run, nothing to test, nothing to debug. And yet, that emptiness is not chaos. It’s valid. The compiler isn’t confused. The IDE isn’t broken. The project exists, even though it does nothing.

That “nothing” is not an error — it’s a starting point.

In Kotlin, and in good system design in general, emptiness is often intentional. It represents a state where nothing can happen yet, but also nothing can go wrong. No invalid states, no surprises, no side effects. Just a clean, well-defined beginning.

This is the same idea behind Kotlin’s Nothing type and category theory’s empty category: a space with no values, no paths, and no behavior — yet one that fits perfectly into the system’s rules.

Before we build arrows, before we compose functions, before we move data around, we begin here — with nothing. And from that quiet place, structure emerges.

1. No Objects — When Nothing Is Still Valid

https://storage.googleapis.com/lambdabricks-cd393.firebasestorage.app/empty_category.svg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-fbsvc%40lambdabricks-cd393.iam.gserviceaccount.com%2F20260411%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260411T064004Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=a499f15a80db2e16e45e202ee0d3b83bd67ab59045b0e9d6c8316874a7e043465a6b1203897010e1aaccabf45b6e528239a02e700757b1e3f35d22eac45df18c41d29af684b9f8a9e4cc9ee6d220bdf5255d7fa2e1a0e1a787340b825e1f9b43a9f16ab474cd157f16029c4186e7ef8a7b865c406644fb4eb8b59bd5afc04ba91b046f3fb584cfe86a9099e2f74f25fdd88bc63b4073c44dbce3193d058e8c8b6499fbfbde580d3a09cbaafca120b21d1c70e88d66dfddfedebf9a26707edc41d4a2f8d7e4460d4a9752e2587c187b69c4b85195efa841f22be39a996a9a9336060b90819d726d316b017e13b8105517f565a05ff62e690d62833bb460298837

In category theory, there exists something called the empty category.

It has:

  • no objects
  • no arrows (morphisms)
  • At first, this sounds useless. But as programmers, we already rely on this idea every day.

    Bottom Types — When “Impossible” Still Fits Everywhere

    Before objects appear and arrows connect them, programming languages need a way to describe something subtle but powerful:

    a computation that never produces a value.

    In mathematics and type theory, this idea is written as:

    (pronounced *bottom*).

    The bottom type represents a type that contains no values at all.

    If we think of types as sets of possible values, the picture looks like this:

    Int      = { … -2, -1, 0, 1, 2 … }
    Boolean  = { true, false }
    Unit     = { () }
    ⊥        = { }

    While Int contains infinitely many values and Boolean contains two, the bottom type contains none.

    At first glance this seems useless. Why would a type with no values exist?

    But programming languages rely on this idea constantly.

    Why Bottom Is a Subtype of Every Type

    In type theory, types are often viewed as sets of values, and subtyping follows the same rule as set inclusion.

    If every value of type X is also a value of type Y, then:

    X ⊆ Y

    and we say:

    X is a subtype of Y

    For example:

    Boolean ⊆ Any

    because every Boolean is also a valid value of Any.

    Now consider the empty set:

    {}

    The empty set is a subset of every set.

    Mathematically:

    {} ⊆ A
    {} ⊆ B
    {} ⊆ C

    Which means:

    ⊥ <: A ⊥ <: B ⊥ <: C

    So the bottom type is automatically a subtype of every type.

    Why? Because it contains no values that could violate the rules of any type.

    Kotlin Already Has This Idea

    Kotlin implements the bottom type as:

    Nothing

    Example:

    fun fail(message: String): Nothing =
        throw IllegalStateException(message)

    This function never produces a value.

    Execution stops with an exception, so the return type is Nothing.

    Why the Compiler Accepts This

    Consider this function:

    fun parseUser(id: String): User =
        if (id.isEmpty())
            fail("Invalid id")
        else
            User(id)

    The function is declared to return User, but fail() returns Nothing.

    Why does this compile?

    Because:

    Nothing <: User

    Since Nothing is a subtype of every type, the type system remains consistent. The program either produces a User, or execution stops.

    Another Kotlin Example

    fun parseInt(text: String): Int =
        text.toIntOrNull() ?: fail("Not a number")

    Here the Elvis operator works because the right side has type:

    Nothing

    So the entire expression still has type:

    Int

    The computation either returns an integer or stops execution.

    What Bottom Really Represents

    Bottom does not simply mean termination.

    It represents any computation that never successfully produces a value, including:

  • program crashes
  • thrown exceptions
  • infinite loops
  • unreachable code
  • In other words:

    value  → normal result
    ⊥      → no result ever produced

    A Quiet Connection to Category Theory

    Category theory also needs a way to describe impossible results.

    Just as programming languages use Nothing, mathematics uses the symbol:

    to represent the absence of values.

    So even before objects and arrows appear, our system already understands two fundamental ideas:

    values
    impossible values

    Once those exist, we can begin connecting things.

    Objects appear.

    Arrows connect them.

    Paths form.

    And from there, categories begin to emerge.

    2. Simple Graphs — When Structure Appears

    Now let’s add just a little something.

    Imagine this:

    A → B → C

    This is a directed graph:

  • nodes (A, B, C)
  • arrows between them
  • At this stage, it’s not yet a category. It’s just potential.

    How a Graph Becomes a Category (Naturally)

    Category theory doesn’t ask you to invent new behavior. It asks you to accept what is unavoidable.

    Step 1: Identity — “Stay where you are”

    Every object must be able to do nothing.

    So we add:

    A → A
    B → B
    C → C

    In Kotlin, this is the identity function:

    fun <T> id(value: T): T = value

    You almost never write it — but it’s always there.

    Step 2: Composition — “If it chains, it exists”

    https://storage.googleapis.com/lambdabricks-cd393.firebasestorage.app/composition_path.svg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-fbsvc%40lambdabricks-cd393.iam.gserviceaccount.com%2F20260411%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260411T064004Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=90347a9d019a26d31dfc21cc561b84c9a0114c4f94f4d9cd0dc077874459b6e8b3dd42675212eb7ffcacadf807dd9e6723e6bc695c8b85800eaac192c6d61dd65b724216c39c733784f232216fcdebcda34a062efcf8fba993bc6b1ae4bce4e0c9533d3c6522b95adac1790623d87eb25b61d6b07a08e1fa4e009899dec06ab5f6580ba3030c9e21143078fe689f093980a2a76338555af55895cff17c93d8658a2816b13b61e3deb88c59fa3b2ef9436b955dd1c79e8ecb5a795cd3358d1e6d316da9472003208bf73488b52a95592b280076fe675f59ccdb29680af85dc109a8b670dde490d3bde24c7636f104c03eadec2471965161f37359288d44c20019

    If you can go from:

    A → B
    B → C

    Then you must allow:

    A → C

    This arrow represents doing both steps in sequence.

    In Kotlin, this is just function composition:

    fun <A, B, C> compose(
        f: (A) -> B,
        g: (B) -> C
    ): (A) -> C = { a -> g(f(a)) }

    A Kotlin story (very real)

    fun parse(input: String): Int = input.length
    fun validate(n: Int): Boolean = n > 3
    fun render(ok: Boolean): String =
        if (ok) "Valid" else "Invalid"

    You didn’t plan a pipeline upfront — but Kotlin allows this:

    val pipeline =
        compose(
            compose(::parse, ::validate),
            ::render
        )

    That pipeline emerged.

    This is exactly what category theory calls a free category.

    What “Free Category” Actually Means

    A free category is just a way of not forgetting anything. Imagine a map with places and roads. Every place becomes an object, every road becomes a connection, and every possible route you can travel is remembered. Staying where you are also counts as a route — that’s the identity. If you can go from A to B and then from B to C, the system simply accepts that A to C is a valid path too. Nothing clever is happening; it’s just acknowledging what’s already possible.

    What makes it “free” is that no extra rules are added on top. No shortcuts are assumed, no paths are merged, and no optimizations are made. If there are ten different ways to reach the same destination, all ten are kept. A free category doesn’t try to decide which path is better or faster — it only records the structure of how things can be connected. It’s the most honest starting point: everything that can be composed exists, and nothing more is assumed.

    #kotlin#softwareengineering#freecategory#nothingtype#typesystems#fpfoundations#programmingconcepts#learninginpublic#buildinpublic#categorytheory#functionalprogramming#mathfordevelopers