Learn by Example

Examples

Learn FezLang by reading real code. Each example is 30–100 lines — long enough to be real, short enough to read in 2 minutes.

01. Hello World

The simplest FezLang program. Print a message, declare a variable, use string interpolation.

io · variables · string interpolation

// Hello World in FezLang
io.print("hello, world!")

name = "FezLang"
version = 1
io.print("Welcome to {name} v{version}")

02. Calculator

A simple REPL calculator that reads user input, parses it, and performs basic math.

io · math · if/else · string interpolation

import "math"

io.print("Simple Calculator")
io.print("Enter: number operator number")
io.print("Operators: + - * /")

while true {
  input = io.read("> ")
  parts = str.split(input, " ")

  if str.len(parts) != 3 {
    io.print("Usage: 5 + 3")
    continue
  }

  a = math.parse_f64(parts[0])
  op = parts[1]
  b = math.parse_f64(parts[2])

  if op == "+" { io.print("= {a + b}") }
  else if op == "-" { io.print("= {a - b}") }
  else if op == "*" { io.print("= {a * b}") }
  else if op == "/" {
    if b == 0.0 {
      io.print("Error: division by zero")
    } else {
      io.print("= {a / b}")
    }
  } else {
    io.print("Unknown operator: {op}")
  }
}

03. TODO List

A CLI task manager with add, remove, and list commands. Shows structs, modules, and a REPL loop.

structs · modules · arrays · while loop · io

struct Task {
  id: int
  text: str
  done: bool
}

module todo {
  next_id = 1
  tasks = []

  fn add(text: str) -> Task {
    task = Task { id: next_id, text: text, done: false }
    tasks = append(tasks, task)
    next_id = next_id + 1
    return task
  }

  fn remove(id: int) -> bool {
    for i, task in tasks {
      if task.id == id {
        tasks = splice(tasks, i, 1)
        return true
      }
    }
    return false
  }

  fn list() {
    if len(tasks) == 0 {
      io.print("  No tasks.")
      return
    }
    for task in tasks {
      mark = if task.done { "x" } else { " " }
      io.print("  [{mark}] {task.id}: {task.text}")
    }
  }
}

io.print("TODO List — commands: add , done , remove , list, quit")

while true {
  input = io.read("> ")
  parts = str.split(input, " ")
  cmd = parts[0]

  if cmd == "quit" { break }
  else if cmd == "list" { todo.list() }
  else if cmd == "add" {
    text = str.join(parts[1:], " ")
    task = todo.add(text)
    io.print("  Added: {task.text} (#{task.id})")
  } else if cmd == "remove" {
    id = math.parse_int(parts[1])
    todo.remove(id)
  } else {
    io.print("  Unknown command: {cmd}")
  }
}

04. FizzBuzz

The classic interview question. Clean, readable, no tricks.

for loop · modulo · if/else · io

// FizzBuzz: print 1-100
// multiples of 3 → "Fizz"
// multiples of 5 → "Buzz"
// multiples of both → "FizzBuzz"

for i in 1..101 {
  if i % 15 == 0 {
    io.print("FizzBuzz")
  } else if i % 3 == 0 {
    io.print("Fizz")
  } else if i % 5 == 0 {
    io.print("Buzz")
  } else {
    io.print("{i}")
  }
}

05. File Processor

Read a CSV file, process each line, and write results. Shows file I/O and error handling.

file · str · error handling · for loop

import "file"

// Read input CSV
lines, err = file.read_lines("scores.csv")
if err {
  io.print("Error reading file: {err.message}")
  os.exit(1)
}

results = []
for line in lines {
  cols = str.split(line, ",")
  if len(cols) < 2 { continue }

  name = str.trim(cols[0])
  score = math.parse_int(str.trim(cols[1]))
  grade = if score >= 90 { "A" }
    else if score >= 80 { "B" }
    else if score >= 70 { "C" }
    else { "F" }

  results = append(results, "{name},{score},{grade}")
}

// Write output
output = str.join(results, "\n")
err = file.write("grades.csv", output)
if err {
  io.print("Error writing file: {err.message}")
  os.exit(1)
}
io.print("Processed {len(results)} records → grades.csv")

06. JSON API Client

Fetch data from a JSON API, parse the response, and display results.

http · json · error handling · structs

import "http"
import "json"

struct Post {
  id: int
  title: str
  body: str
}

// Fetch posts from API
resp, err = http.get("https://jsonplaceholder.typicode.com/posts")
if err {
  io.print("Request failed: {err.message}")
  os.exit(1)
}

posts, err = json.decode(resp.body, []Post)
if err {
  io.print("Parse error: {err.message}")
  os.exit(1)
}

// Display first 5 posts
for post in posts[:5] {
  io.print("#{post.id}: {post.title}")
  io.print("  {str.slice(post.body, 0, 80)}...")
  io.print("")
}

07. HTTP Server

A simple JSON API server with routes, request handling, and JSON responses.

http · json · modules · structs · routing

import "http"
import "json"

struct Message {
  text: str
  status: int
}

module api {
  fn hello(req: http.Request) -> http.Response {
    name = req.query("name")
    if name == "" { name = "world" }
    msg = Message { text: "hello, {name}!", status: 200 }
    return http.json_response(200, json.encode(msg))
  }

  fn health(req: http.Request) -> http.Response {
    return http.json_response(200, `{"status": "ok"}`)
  }

  fn not_found(req: http.Request) -> http.Response {
    msg = Message { text: "not found", status: 404 }
    return http.json_response(404, json.encode(msg))
  }
}

server = http.new_server()
server.route("GET", "/hello", api.hello)
server.route("GET", "/health", api.health)
server.fallback(api.not_found)

io.print("Server listening on :8080")
server.listen(":8080")

08. Error Handling

Multiple returns, error propagation, defer for cleanup, and the _ discard pattern.

error handling · multiple returns · defer · _ discard

import "file"
import "json"

struct Config {
  host: str
  port: int
  debug: bool
}

module config {
  fn load(path: str) -> Config, err {
    // Read file — propagate error if it fails
    data, err = file.read(path)
    if err {
      return Config{}, error("failed to read config: {err.message}")
    }

    // Parse JSON — propagate error if it fails
    cfg, err = json.decode(data, Config)
    if err {
      return Config{}, error("failed to parse config: {err.message}")
    }

    // Validate
    if cfg.port < 1 {
      return Config{}, error("invalid port: {cfg.port}")
    }

    return cfg, nil
  }
}

// Load config — handle the error
cfg, err = config.load("app.json")
if err {
  io.print("Config error: {err.message}")
  io.print("Using defaults...")
  cfg = Config { host: "localhost", port: 8080, debug: false }
}

io.print("Server: {cfg.host}:{cfg.port}")
io.print("Debug: {cfg.debug}")

// Sometimes you don't care about the error
_ = file.write("last_run.txt", time.now())

// Defer runs cleanup in reverse order
fn process_file(path: str) -> err {
  f, err = file.open(path)
  if err { return err }
  defer file.close(f)

  lock, err = file.lock(path)
  if err { return err }
  defer file.unlock(lock)

  // Work with file — both close and unlock
  // happen automatically when function returns
  data = file.read_all(f)
  io.print("Read {str.len(data)} bytes")
  return nil
}

09. Concurrency

Spawn goroutine-like tasks, communicate via channels, and wait for results.

spawn · channel · concurrency · http

import "http"
import "time"

struct Result {
  url: str
  status: int
  elapsed: f64
}

fn fetch(url: str, ch: channel) {
  start = time.now_ms()
  resp, err = http.get(url)
  elapsed = time.now_ms() - start

  if err {
    ch <- Result { url: url, status: 0, elapsed: elapsed }
  } else {
    ch <- Result { url: url, status: resp.status, elapsed: elapsed }
  }
}

urls = [
  "https://fezlang.org",
  "https://go.dev",
  "https://rust-lang.org",
  "https://python.org",
  "https://nodejs.org",
]

ch = channel(len(urls))

for url in urls {
  spawn fetch(url, ch)
}

// Collect results
for _ in urls {
  result = <-ch
  status = if result.status == 0 { "FAIL" } else { "{result.status}" }
  io.print("{result.url} → {status} ({result.elapsed}ms)")
}

10. Closures

Higher-order functions, function factories, and callbacks. Functions are first-class values.

closures · lambdas · higher-order functions · first-class functions

// Function factory — returns a function
fn make_adder(x: int) -> fn(int) -> int {
  return |y| x + y
}

add5 = make_adder(5)
add10 = make_adder(10)
io.print("add5(3) = {add5(3)}")    // 8
io.print("add10(3) = {add10(3)}")  // 13

// Map with lambda
numbers = [1, 2, 3, 4, 5]
doubled = map(numbers, |n| n * 2)
io.print("doubled: {doubled}")  // [2, 4, 6, 8, 10]

// Filter with lambda
evens = filter(numbers, |n| n % 2 == 0)
io.print("evens: {evens}")  // [2, 4]

// Reduce
sum = reduce(numbers, 0, |acc, n| acc + n)
io.print("sum: {sum}")  // 15

// Compose functions
fn compose(f: fn(int) -> int, g: fn(int) -> int) -> fn(int) -> int {
  return |x| f(g(x))
}

double = |x| x * 2
increment = |x| x + 1
double_then_inc = compose(increment, double)
io.print("double_then_inc(5) = {double_then_inc(5)}")  // 11

11. Enums & Structs

Define enums and structs, use them together in a realistic order processing module.

enum · struct · modules · pattern matching

enum Status {
  Pending
  Processing
  Shipped
  Delivered
  Cancelled
}

struct Item {
  name: str
  price: f64
  qty: int
}

struct Order {
  id: int
  items: []Item
  status: Status
}

module orders {
  fn total(order: Order) -> f64 {
    sum = 0.0
    for item in order.items {
      sum = sum + item.price * item.qty
    }
    return sum
  }

  fn status_label(s: Status) -> str {
    if s == Status.Pending { return "Pending" }
    if s == Status.Processing { return "Processing" }
    if s == Status.Shipped { return "Shipped" }
    if s == Status.Delivered { return "Delivered" }
    return "Cancelled"
  }

  fn summary(order: Order) {
    io.print("Order #{order.id} — {orders.status_label(order.status)}")
    for item in order.items {
      io.print("  {item.name} x{item.qty} @ ${item.price}")
    }
    io.print("  Total: ${orders.total(order)}")
  }
}

order = Order {
  id: 1042,
  items: [
    Item { name: "Keyboard", price: 79.99, qty: 1 },
    Item { name: "USB Cable", price: 9.99, qty: 3 },
  ],
  status: Status.Shipped,
}

orders.summary(order)

12. Modules Demo

Nested modules, dot-path access, and how FezLang organizes code without classes or packages.

modules · nesting · dot access · organization

// Modules nest. The dot is always a path.
module math {
  const PI = 3.14159265

  module trig {
    fn sin(x: f64) -> f64 {
      // Taylor series approximation
      result = x
      term = x
      for i in 1..10 {
        term = term * (-1.0) * x * x / ((2 * i) * (2 * i + 1))
        result = result + term
      }
      return result
    }

    fn cos(x: f64) -> f64 {
      return trig.sin(math.PI / 2.0 - x)
    }
  }

  module convert {
    fn deg_to_rad(deg: f64) -> f64 {
      return deg * math.PI / 180.0
    }

    fn rad_to_deg(rad: f64) -> f64 {
      return rad * 180.0 / math.PI
    }
  }
}

// Clear, unambiguous paths
angle = math.convert.deg_to_rad(45.0)
result = math.trig.sin(angle)
io.print("sin(45°) = {result}")

// Every dot is a path into a module or struct
// Never a method call. Never a dispatch.
io.print("π = {math.PI}")
io.print("90° = {math.convert.deg_to_rad(90.0)} rad")

13. Ref Parameters

Pass by value is the default. Use ref when you need to mutate in place — explicit at both sites.

ref · pass by value · mutation · structs

struct Point {
  x: f64
  y: f64
}

// Pass by value — original is NOT modified
fn translate_copy(p: Point, dx: f64, dy: f64) -> Point {
  return Point { x: p.x + dx, y: p.y + dy }
}

// Pass by ref — original IS modified
fn translate(p: ref Point, dx: f64, dy: f64) {
  p.x = p.x + dx
  p.y = p.y + dy
}

origin = Point { x: 0.0, y: 0.0 }

// Value: origin unchanged
moved = translate_copy(origin, 5.0, 3.0)
io.print("origin: ({origin.x}, {origin.y})")  // (0, 0)
io.print("moved:  ({moved.x}, {moved.y})")    // (5, 3)

// Ref: origin IS changed — explicit at call site
translate(ref origin, 10.0, 20.0)
io.print("origin: ({origin.x}, {origin.y})")  // (10, 20)

// The ref keyword is required at BOTH sites:
// - fn declaration: p: ref Point
// - call site: translate(ref origin, ...)
// This makes mutation always visible to the reader.

14. SQLite CRUD

Connect to a database, create a table, and perform full CRUD operations.

db · error handling · structs · modules · CRUD

import "db"

struct User {
  id: int
  name: str
  email: str
}

module users {
  fn init(conn: db.Conn) -> err {
    return db.execute(conn, `
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL
      )
    `)
  }

  fn create(conn: db.Conn, name: str, email: str) -> User, err {
    err = db.execute(conn, "INSERT INTO users (name, email) VALUES (?, ?)", name, email)
    if err { return User{}, err }
    row, err = db.query_one(conn, "SELECT id, name, email FROM users WHERE email = ?", email)
    if err { return User{}, err }
    return User { id: row.int("id"), name: row.str("name"), email: row.str("email") }, nil
  }

  fn find(conn: db.Conn, id: int) -> User, err {
    row, err = db.query_one(conn, "SELECT id, name, email FROM users WHERE id = ?", id)
    if err { return User{}, err }
    return User { id: row.int("id"), name: row.str("name"), email: row.str("email") }, nil
  }

  fn update(conn: db.Conn, id: int, name: str) -> err {
    return db.execute(conn, "UPDATE users SET name = ? WHERE id = ?", name, id)
  }

  fn delete(conn: db.Conn, id: int) -> err {
    return db.execute(conn, "DELETE FROM users WHERE id = ?", id)
  }

  fn all(conn: db.Conn) -> []User, err {
    rows, err = db.query(conn, "SELECT id, name, email FROM users ORDER BY id")
    if err { return [], err }
    result = []
    for row in rows {
      result = append(result, User { id: row.int("id"), name: row.str("name"), email: row.str("email") })
    }
    return result, nil
  }
}

conn, err = db.connect("sqlite:app.db")
if err { io.print("DB error: {err.message}"); os.exit(1) }
defer db.close(conn)

users.init(conn)
user, _ = users.create(conn, "Cal", "cal@fez.dev")
io.print("Created: {user.name} (#{user.id})")

users.update(conn, user.id, "Calvin")
user, _ = users.find(conn, user.id)
io.print("Updated: {user.name}")

all, _ = users.all(conn)
for u in all {
  io.print("  #{u.id} {u.name} <{u.email}>")
}

15. TCP Echo Server

A concurrent TCP server that echoes back anything a client sends. One spawn per connection.

net · spawn · concurrency · error handling · defer

import "net"

fn handle_client(conn: net.Conn) {
  defer net.tcp_close(conn)
  addr = net.remote_addr(conn)
  io.print("Client connected: {addr}")

  while true {
    data, err = net.tcp_read(conn, 1024)
    if err {
      io.print("Client disconnected: {addr}")
      return
    }
    if str.len(data) == 0 { continue }

    msg = str.trim(data)
    io.print("[{addr}] {msg}")

    _, err = net.tcp_write(conn, "echo: {msg}\n")
    if err {
      io.print("Write error: {err.message}")
      return
    }
  }
}

listener, err = net.tcp_listen(":9000")
if err {
  io.print("Listen error: {err.message}")
  os.exit(1)
}

io.print("Echo server listening on :9000")

while true {
  conn, err = net.tcp_accept(listener)
  if err {
    io.print("Accept error: {err.message}")
    continue
  }
  spawn handle_client(conn)
}