Beginner

Hello World in Erlang

Your first Erlang program - the classic Hello World example with Docker setup using escript

Every programming journey starts with Hello World. Let’s write our first Erlang program and explore what makes this concurrent functional language tick.

The Code

Create a file named hello.erl:

1
2
3
#!/usr/bin/env escript
main(_) ->
    io:format("Hello, World!~n").

This is an Erlang script (escript) - the simplest way to run Erlang code.

Understanding the Code

Let’s break down this short program:

The Shebang Line

1
#!/usr/bin/env escript

This tells Unix-like systems to run the file with escript, Erlang’s scripting tool. It’s optional when running with escript hello.erl directly but makes the file executable on its own.

The Main Function

1
2
main(_) ->
    io:format("Hello, World!~n").
  • main(_) - The entry point for escripts. The _ means we’re ignoring the command-line arguments
  • -> - Separates the function head from the body
  • io:format/1 - Calls the format function from the io module
  • "Hello, World!~n" - The format string, where ~n is a newline
  • . - Ends the function definition (like a statement terminator)

Erlang’s Syntax Quirks

Erlang syntax takes some getting used to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
% This is a comment

% Statements end with a period
main(_) -> io:format("Hello!~n").

% Clauses separated by semicolons
greet(english) -> "Hello";
greet(spanish) -> "Hola";
greet(french)  -> "Bonjour".

% Expressions in a clause separated by commas
do_things() ->
    X = 1,
    Y = 2,
    X + Y.

Running with Docker

The easiest way to run Erlang without installing anything locally is with Docker:

1
2
3
4
5
# Pull the official Erlang image
docker pull erlang:alpine

# Run the program
docker run --rm -v $(pwd):/app -w /app erlang:alpine escript hello.erl

Understanding the Docker Command

  • docker run --rm - Run a container and remove it when done
  • -v $(pwd):/app - Mount the current directory to /app in the container
  • -w /app - Set the working directory to /app
  • erlang:alpine - Use the official Erlang Docker image (Alpine-based, smaller)
  • escript hello.erl - Run our script with escript

Expected Output

Hello, World!

Alternative Approaches

Erlang offers several ways to write and run programs. Let’s explore them.

Traditional Module Approach

For larger programs, you’d use a proper module:

1
2
3
4
5
6
% hello_module.erl
-module(hello_module).
-export([hello/0]).

hello() ->
    io:format("Hello, World!~n").

Run it in the Erlang shell:

1
docker run --rm -v $(pwd):/app -w /app erlang:alpine sh -c 'erl -noshell -s hello_module hello -s init stop'

Or interactively:

1
docker run --rm -it -v $(pwd):/app -w /app erlang:alpine erl
1
2
3
4
5
1> c(hello_module).  % Compile the module
{ok,hello_module}
2> hello_module:hello().  % Call the function
Hello, World!
ok

Module Declaration Explained

1
2
-module(hello_module).  % Module name MUST match filename
-export([hello/0]).     % Exported functions (name/arity)
  • -module(name) - Declares the module name (must match filename without .erl)
  • -export([...]) - Lists functions visible from outside the module
  • hello/0 - Function hello with 0 arguments (arity)

Using io:fwrite

1
2
3
#!/usr/bin/env escript
main(_) ->
    io:fwrite("Hello, World!~n").

io:fwrite/1 is an alias for io:format/1.

Using Format Strings

Erlang’s format strings are powerful:

1
2
3
4
#!/usr/bin/env escript
main(_) ->
    Name = "World",
    io:format("Hello, ~s!~n", [Name]).

Common format specifiers:

  • ~s - String
  • ~p - Pretty print any term
  • ~w - Write any term (raw)
  • ~n - Newline
  • ~B - Integer in base 10

Multiple Expressions

1
2
3
4
5
#!/usr/bin/env escript
main(_) ->
    io:format("Hello, "),
    io:format("World!"),
    io:format("~n").

Note: Commas separate expressions within a function body.

Understanding Erlang Types

Even in Hello World, we encounter Erlang’s data types:

Atoms

1
2
3
4
5
hello    % An atom - a literal constant
'Hello'  % Atoms can be quoted for special characters
true     % Booleans are atoms
false
ok       % Common return value atom

Strings

1
"Hello"  % Actually a list of integers (character codes)

Erlang strings are lists of characters:

1
2
3
4
1> "Hello" == [72, 101, 108, 108, 111].
true
2> [$H, $e, $l, $l, $o].  % $ gives character code
"Hello"

Binaries (Modern String Handling)

For efficient string handling, use binaries:

1
2
3
4
#!/usr/bin/env escript
main(_) ->
    Greeting = <<"Hello, World!">>,
    io:format("~s~n", [Greeting]).

The Erlang Shell

The Erlang shell (erl) is your interactive playground:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ erl
Erlang/OTP 27 [erts-15.0]

1> io:format("Hello, World!~n").
Hello, World!
ok
2> 2 + 2.
4
3> Greeting = "Hello".
"Hello"
4> io:format("~s, World!~n", [Greeting]).
Hello, World!
ok
5> q().  % Quit
ok

Useful shell commands:

  • h(). - Help
  • c(module). - Compile a module
  • l(module). - Load a module
  • m(module). - Module information
  • q(). - Quit (graceful)
  • halt(). - Quit (immediate)

Pattern Matching Preview

Even simple programs hint at Erlang’s pattern matching:

1
2
3
4
5
#!/usr/bin/env escript
main([]) ->
    io:format("Hello, World!~n");
main([Name|_]) ->
    io:format("Hello, ~s!~n", [Name]).

Run with:

1
2
escript hello.erl         # Hello, World!
escript hello.erl Alice   # Hello, Alice!

This shows:

  • Multiple function clauses (separated by ;)
  • Pattern matching on arguments
  • [Head|Tail] list destructuring

Installing Erlang Locally

If you prefer to run Erlang without Docker:

macOS with Homebrew:

1
brew install erlang

Ubuntu/Debian:

1
sudo apt install erlang

Using asdf (version manager):

1
2
3
asdf plugin add erlang
asdf install erlang latest
asdf global erlang latest

After installation:

1
2
3
4
5
# Run an escript
escript hello.erl

# Or use the shell
erl

Common Beginner Mistakes

Forgetting the Period

1
2
3
4
5
6
7
% Wrong - missing period
main(_) ->
    io:format("Hello!~n")

% Right
main(_) ->
    io:format("Hello!~n").

Wrong Module Name

1
2
3
% In file: hello.erl
-module(hello_world).  % Wrong! Must match filename
-module(hello).        % Right

Confusing Semicolons and Periods

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
% Semicolon separates clauses
greet(1) -> "One";
greet(2) -> "Two";
greet(_) -> "Other".  % Period ends the function

% Comma separates expressions
compute() ->
    X = 1,
    Y = 2,
    X + Y.  % Last expression, period ends function

Case Sensitivity

1
2
3
4
5
6
7
8
9
% Variables start with uppercase
Name = "Alice".

% Atoms start with lowercase
hello.
ok.

% This is a variable, not an atom
Hello = atom.

Why Erlang for Hello World?

Even in this simple program, Erlang’s philosophy shows:

  1. Functional style - io:format is a function call, not a method
  2. Explicit module system - We specify io:format, not just print
  3. Pattern matching - The _ in main(_) ignores arguments
  4. Atoms for control - The shell returns ok atom to indicate success

Historical Note

Erlang was developed at Ericsson starting in 1986 by Joe Armstrong, Robert Virding, and Mike Williams. The language was designed for building highly concurrent, fault-tolerant telephone switches. The name “Erlang” honors mathematician Agner Krarup Erlang, who pioneered telephone traffic engineering.

Today, Erlang powers systems requiring extreme reliability:

  • WhatsApp (billions of messages daily)
  • Discord (real-time communication)
  • RabbitMQ (message broker)
  • CouchDB (database)

Next Steps

Continue to Processes and Concurrency to learn about Erlang’s legendary concurrency model - the feature that makes it unique among programming languages.

Key Takeaways

  1. escript runs Erlang scripts without compilation
  2. io:format/1,2 outputs formatted text with ~n for newlines
  3. Periods end function definitions
  4. Module names must match filenames
  5. Atoms (lowercase) and variables (uppercase) are different
  6. _ is a throwaway pattern that matches anything
  7. The Erlang shell (erl) is great for experimentation

Running Today

All examples can be run using Docker:

docker pull erlang:alpine
Last updated: