Picture this: you’re sipping your morning coffee, browsing through yet another “revolutionary” programming language announcement, when suddenly you stumble upon something called Gleam. Your first thought? “Great, another JavaScript framework disguised as a programming language.” But hold your horses – Gleam is something entirely different, and dare I say, refreshingly sane. Gleam isn’t trying to reinvent the wheel or solve world hunger through blockchain-powered AI. Instead, it’s doing something much more valuable: making functional programming on the BEAM accessible without sacrificing the power that makes Erlang and Elixir so compelling for building bulletproof systems.

What Exactly Is This Gleam Thing?

Gleam is a statically-typed functional programming language that compiles to both Erlang bytecode and JavaScript. Think of it as the friendly neighborhood superhero of the BEAM ecosystem – it has all the superpowers of Erlang (fault tolerance, massive concurrency, hot code swapping) but with a syntax that won’t make you question your career choices. The language recently hit its v1.0 milestone, which in programming language years means it’s finally ready to meet your parents. Created with inspiration from languages like Elm and Rust, Gleam brings modern language design principles to the battle-tested BEAM virtual machine.

graph TD A[Gleam Source Code] --> B[Gleam Compiler] B --> C[Erlang Bytecode] B --> D[JavaScript Code] C --> E[BEAM VM] D --> F[Browser/Node.js] E --> G[Concurrent Systems] E --> H[Distributed Applications] F --> I[Web Applications] F --> J[Frontend Interfaces]

Why Should You Care About Another Programming Language?

Before you roll your eyes and mutter “not another one,” let me give you three compelling reasons why Gleam deserves a spot on your learning list: Safety First, Questions Later: Gleam’s type system is like having a really pedantic but helpful friend who catches your mistakes before they become production disasters. Static typing means your code either compiles correctly or doesn’t compile at all – no more “undefined is not a function” surprises at 3 AM. Standing on Giant Shoulders: By targeting the BEAM virtual machine, Gleam inherits decades of battle-tested concurrency and fault-tolerance innovations. You get actor-model concurrency, supervision trees, and hot code reloading without having to learn Erlang’s… unique syntax choices. JavaScript Flexibility: Need to run your logic in the browser? Gleam compiles to JavaScript too, meaning you can share code between your backend and frontend without resorting to Node.js everywhere.

Setting Up Your Gleam Playground

Let’s get our hands dirty, shall we? Setting up Gleam is refreshingly straightforward – no Docker containers, no complex build tools, just good old-fashioned software installation.

Prerequisites: The Usual Suspects

First, you’ll need Erlang/OTP installed on your system. Don’t panic – this isn’t as scary as it sounds. Head over to the official Erlang website and grab the installer for your operating system. If you’re on macOS and have Homebrew, you’re in for a treat:

brew install erlang

For the Gleam compiler itself, you have several options. The simplest approach is using your system’s package manager:

# macOS with Homebrew
brew install gleam
# Or download pre-compiled binaries from GitHub
# https://github.com/gleam-lang/gleam/releases

Verification: Trust But Verify

Let’s make sure everything is working properly:

gleam --version
erl -version

If both commands return version information without throwing tantrums, you’re golden.

Your First Dance with Gleam

Time for the traditional “Hello, World!” – because what’s a programming tutorial without this classic ritual?

Project Creation: Starting Fresh

Gleam comes with a project generator that sets up everything you need:

gleam new my_first_gleam_app
cd my_first_gleam_app

This creates a beautifully organized project structure that would make Marie Kondo proud:

my_first_gleam_app/
├── gleam.toml          # Project configuration
├── src/                # Your source code lives here
│   └── my_first_gleam_app.gleam
└── test/               # Tests (yes, testing is built-in!)
    └── my_first_gleam_app_test.gleam

The Obligatory Hello World

Open src/my_first_gleam_app.gleam and replace its contents with:

import gleam/io
pub fn main() {
  let greeting = "Hello, Gleam! Welcome to the BEAM party!"
  io.println(greeting)
  // Let's get a bit fancy
  let excitement_level = 9000
  io.println("My excitement level is over " <> int.to_string(excitement_level))
}

Run your creation:

gleam run

Congratulations! You’ve just written and executed your first Gleam program. That warm fuzzy feeling? That’s the satisfaction of clean, working code.

Gleam Syntax: Where Elegance Meets Practicality

Now that we’ve broken the ice, let’s dive into Gleam’s syntax. If you’ve used any modern functional language, you’ll feel right at home. If you haven’t, don’t worry – Gleam’s syntax is refreshingly intuitive.

Variables and Immutability: Embrace the Immutable Life

In Gleam, variables are immutable by default – once you bind a value, it stays bound. This might feel restrictive at first, but trust me, it’s liberating. No more worrying about some distant function mutating your data.

import gleam/io
import gleam/int
import gleam/float
import gleam/bool
pub fn main() {
  // Basic types - the building blocks of greatness
  let developer_name = "Maxim"
  let years_of_experience = 10
  let coffee_addiction_level = 95.5
  let loves_functional_programming = True
  // String concatenation with the <> operator
  io.println("Developer: " <> developer_name)
  io.println("Experience: " <> int.to_string(years_of_experience) <> " years")
  io.println("Coffee addiction: " <> float.to_string(coffee_addiction_level) <> "%")
  io.println("Loves FP: " <> bool.to_string(loves_functional_programming))
}

Functions: The Heart of Functional Programming

Functions in Gleam are first-class citizens. They’re clean, composable, and absolutely delightful to work with:

import gleam/io
import gleam/int
// A simple function that calculates the square of a number
pub fn square(x: Int) -> Int {
  x * x
}
// A function that demonstrates pattern matching
pub fn describe_number(n: Int) -> String {
  case n {
    0 -> "Zero - the loneliest number"
    1 -> "One - the ultimate authority"
    n if n < 0 -> "Negative - looking on the dark side"
    n if n > 100 -> "Big number - probably someone's age in dog years"
    _ -> "Just a regular number, nothing special"
  }
}
pub fn main() {
  let number = 42
  let squared = square(number)
  io.println("The square of " <> int.to_string(number) <> " is " <> int.to_string(squared))
  io.println(describe_number(number))
}

Custom Types: Building Your Own Universe

Custom types in Gleam are where the magic really happens. They let you model your domain precisely and safely:

import gleam/io
import gleam/int
// Define a custom type for different programming paradigms
pub type ProgrammingParadigm {
  Functional
  ObjectOriented
  Procedural
  LogicBased
  Reactive(enthusiasm_level: Int)
}
// A custom type for a developer profile
pub type Developer {
  Developer(
    name: String,
    favorite_paradigm: ProgrammingParadigm,
    coffee_cups_per_day: Int,
  )
}
pub fn describe_paradigm(paradigm: ProgrammingParadigm) -> String {
  case paradigm {
    Functional -> "Functional - where side effects go to die"
    ObjectOriented -> "OOP - everything is an object, including confusion"
    Procedural -> "Procedural - step by step, like IKEA instructions"
    LogicBased -> "Logic-based - for when you want to feel really smart"
    Reactive(level) -> "Reactive with " <> int.to_string(level) <> "/10 enthusiasm"
  }
}
pub fn main() {
  let me = Developer(
    name: "Maxim",
    favorite_paradigm: Reactive(enthusiasm_level: 9),
    coffee_cups_per_day: 5,
  )
  case me {
    Developer(name, paradigm, coffee) -> {
      io.println("Developer: " <> name)
      io.println("Paradigm: " <> describe_paradigm(paradigm))
      io.println("Daily coffee consumption: " <> int.to_string(coffee) <> " cups")
    }
  }
}

Lists and Pattern Matching: The Dynamic Duo

Lists are fundamental in Gleam, and pattern matching makes working with them a joy:

import gleam/io
import gleam/int
import gleam/list
pub fn sum_list(numbers: List(Int)) -> Int {
  case numbers {
    [] -> 0
    [first, ..rest] -> first + sum_list(rest)
  }
}
pub fn describe_list_length(items: List(a)) -> String {
  case list.length(items) {
    0 -> "Empty list - the void stares back"
    1 -> "Single item - standing alone like a hero"
    2 -> "Two items - perfect for comparisons"
    n if n < 10 -> "Small list - manageable and friendly"
    _ -> "Large list - here be dragons"
  }
}
pub fn main() {
  let numbers = [1, 2, 3, 4, 5]
  let empty_list = []
  let languages = ["Gleam", "Erlang", "Elixir", "Haskell"]
  io.println("Sum of numbers: " <> int.to_string(sum_list(numbers)))
  io.println("Numbers list: " <> describe_list_length(numbers))
  io.println("Empty list: " <> describe_list_length(empty_list))
  io.println("Languages list: " <> describe_list_length(languages))
}

Error Handling: The Result Type Saves the Day

Gleam doesn’t have exceptions – instead, it uses the Result type for error handling. This might seem foreign if you’re coming from languages with try-catch blocks, but it’s actually much more reliable:

import gleam/io
import gleam/int
import gleam/result
pub fn safe_divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("Division by zero - math teachers hate this trick!")
    _ -> Ok(a / b)
  }
}
pub fn process_division_result(result: Result(Int, String)) -> String {
  case result {
    Ok(value) -> "Success! Result is " <> int.to_string(value)
    Error(message) -> "Oops! " <> message
  }
}
pub fn main() {
  let good_division = safe_divide(10, 2)
  let bad_division = safe_divide(10, 0)
  io.println(process_division_result(good_division))
  io.println(process_division_result(bad_division))
  // Using the result module for chaining operations
  let chained_result = 
    safe_divide(100, 4)
    |> result.map(fn(x) { x * 2 })
|--|--|

  io.println("Chained operations: " <> process_division_result(chained_result))
}

Building a Simple Web Application

Now let’s put our knowledge to practical use by building a simple web application. We’ll create a basic todo list API using Wisp, a web framework built specifically for Gleam.

Setting Up the Project

Create a new project and add the necessary dependencies:

The Todo Model

First, let’s define our domain model:

// src/todo.gleam
import gleam/json
import gleam/dynamic
import gleam/result
pub type Todo {
  Todo(id: Int, text: String, completed: Bool)
}
pub type TodoList {
  TodoList(todos: List(Todo), next_id: Int)
}
pub fn new_todo_list() -> TodoList {
  TodoList(todos: [], next_id: 1)
}
pub fn add_todo(list: TodoList, text: String) -> TodoList {
  let todo = Todo(id: list.next_id, text: text, completed: False)
  TodoList(
    todos: [todo, ..list.todos],
    next_id: list.next_id + 1,
  )
}
pub fn complete_todo(list: TodoList, id: Int) -> TodoList {
  let updated_todos = 
    list.todos
    |> list.map(fn(todo) {

      case todo.id == id {
        True -> Todo(..todo, completed: True)
        False -> todo
      }
    })
  TodoList(..list, todos: updated_todos)
}
pub fn todo_to_json(todo: Todo) -> json.Json {
  json.object([
    #("id", json.int(todo.id)),
    #("text", json.string(todo.text)),
    #("completed", json.bool(todo.completed)),
  ])
}
pub fn todo_list_to_json(list: TodoList) -> json.Json {
  json.object([
    #("todos", json.array(list.todos, todo_to_json)),
    #("count", json.int(list.length(list.todos))),
  ])
}

The Web Server

Now let’s create a simple web server:

// src/web.gleam
import wisp
import gleam/http
import gleam/http/request.{Request}
import gleam/http/response.{Response}
import gleam/json
import todo.{TodoList}
pub type Context {
  Context(todo_list: TodoList)
}
pub fn handle_request(req: Request, ctx: Context) -> Response {
  case req.path_segments(req) {
    [] -> home_page(ctx)
    ["api", "todos"] -> handle_todos_api(req, ctx)
    _ -> wisp.not_found()
  }
}
fn home_page(ctx: Context) -> Response {
  let html = "
    <!DOCTYPE html>
    <html>
    <head>
        <title>Gleam Todo App</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .todo-item { padding: 10px; margin: 5px 0; background: #f5f5f5; }
            .completed { text-decoration: line-through; opacity: 0.6; }
        </style>
    </head>
    <body>
        <h1>Welcome to the Gleam Todo App!</h1>
        <p>This is a simple todo application built with Gleam and Wisp.</p>
        <p>Try making requests to:</p>
        <ul>
            <li>GET /api/todos - Get all todos</li>
            <li>POST /api/todos - Add a new todo</li>
        </ul>
        <div id='todo-list'></div>
    </body>
    </html>
  "
  wisp.html_response(html, 200)
}
fn handle_todos_api(req: Request, ctx: Context) -> Response {
  case req.method {
    http.Get -> {
      let json_response = todo.todo_list_to_json(ctx.todo_list)
      wisp.json_response(json_response, 200)
    }
    http.Post -> {
      // In a real app, you'd parse the request body here
      let updated_list = todo.add_todo(ctx.todo_list, "New todo from API")
      let json_response = todo.todo_list_to_json(updated_list)
      wisp.json_response(json_response, 201)
    }
    _ -> wisp.method_not_allowed([http.Get, http.Post])
  }
}

The Main Application

Finally, let’s tie everything together:

// src/todo_app.gleam
import gleam/io
import gleam/erlang/process
import mist
import todo
import web
pub fn main() {
  let initial_context = web.Context(todo_list: todo.new_todo_list())
  let handler = fn(req) { web.handle_request(req, initial_context) }
  let assert Ok(_) = 
    handler
    |> mist.new
|--|--|
    |> mist.start_http

  io.println("🚀 Todo app is running on http://localhost:3000")
  io.println("✨ Built with Gleam - because life's too short for runtime errors!")
  process.sleep_forever()
}

Run your application:

gleam run

Open your browser to http://localhost:3000 and marvel at your creation! You can also test the API endpoints with curl:

# Get todos
curl http://localhost:3000/api/todos
# Add a todo (simplified - in a real app you'd send JSON)
curl -X POST http://localhost:3000/api/todos

The BEAM Advantage: Why It Matters

Running on the BEAM virtual machine isn’t just a technical detail – it’s a superpower. The BEAM was designed for building systems that never stop, never fail gracefully, and scale to millions of concurrent processes. When your Gleam application runs on BEAM, you inherit: Massive Concurrency: Handle millions of lightweight processes without breaking a sweat. Each process is isolated and communicates through message passing. Fault Tolerance: The “let it crash” philosophy means failures are contained and recoverable. Your application keeps running even when individual parts fail. Hot Code Reloading: Update your running application without stopping it. Deploy fixes and features without downtime. Distribution: Scale across multiple machines seamlessly. The BEAM handles networking, node discovery, and process distribution.

What’s Next on Your Gleam Journey?

You’ve taken your first steps into the Gleam ecosystem, and hopefully, you’re as excited as I am about the possibilities. The language combines the best aspects of functional programming with the pragmatic power of the BEAM virtual machine. Here are some directions to explore further: Dive Deeper into OTP: Learn about GenServers, Supervisors, and Applications – the building blocks of fault-tolerant systems. Explore Web Development: Try building more complex web applications with Wisp, or experiment with frontend development using Lustre. Study the Ecosystem: Gleam can interoperate with existing Erlang and Elixir libraries, giving you access to decades of battle-tested code. Join the Community: The Gleam community is welcoming and helpful. Join the Discord server, contribute to open source projects, or just ask questions. Gleam represents something special in the programming language landscape – it’s not trying to be everything to everyone, but rather excelling at building reliable, concurrent systems with a delightful developer experience. In a world full of complex frameworks and over-engineered solutions, Gleam’s simplicity and focus are refreshing. So go forth, write some Gleam code, and remember: if it compiles, it probably works. And if it works on BEAM, it’ll probably keep working when everything else falls apart. That’s not just good engineering – that’s peace of mind. Happy coding, and welcome to the BEAM family! ✨