Introduction to Erlang
Erlang, introduced in 1986, is widely recognized for its capabilities in developing concurrent,
distributed systems with high availability. It excels in handling fault tolerance and real-time
applications, making it ideal for telecommunications, banking systems, and more. Erlang's
lightweight processes (actors), pattern matching, and built-in support for distribution contribute
to its robustness in fault-tolerant systems. Additionally, Erlang's "let it crash" philosophy, which
encourages letting processes fail and recover gracefully, enhances its resilience and scalability.
The language's immutable data structures and message-passing concurrency model further simplify
complex system design and ensure consistency across distributed components.
Table of Contents
Junior-Level Erlang Interview Questions
Here are some junior-level interview questions for Erlang:
Question 01: What is Erlang, and why is it used?
Answer: Erlang is a functional programming language designed for building scalable and
fault-tolerant systems, particularly in telecommunications. Its concurrency model, based on
lightweight processes, allows for efficient handling of numerous simultaneous tasks, making it ideal
for applications requiring high availability and reliability.
Erlang's features include immutable data, pattern matching, and a built-in distributed computing
support. These characteristics enable developers to create robust systems that can be updated
without downtime, ensuring continuous operation in critical environments.
Question 02: Explain the concept of immutability in Erlang.
Answer:
Immutability in Erlang means that once a variable is assigned a value, it cannot be changed. This
feature ensures that data remains consistent and free from unintended side effects, which is
particularly useful in concurrent programming. For example:
% Example of immutability
X = 5,
% Attempting to reassign X will result in an error
X = 10. % This line will cause an error
In the example, X is assigned the value 5. Trying to reassign X to 10 will cause an error because
Erlang enforces immutability, ensuring that variables retain their initial values throughout their
scope, thereby promoting safer and more predictable code.
Question 03: What will be the output of the following code snippet?
add(X, Y) ->
X + Y.
result = add(5, 10),
io:format("Result: ~p~n", [result]).
Answer: The output will be 'Result: 15'
Question 04: What are processes in Erlang, and how do they differ from operating system processes?
Answer: Processes in Erlang are lightweight, isolated, and concurrent entities used for
parallel execution of tasks. Unlike operating system processes, Erlang processes are managed by the
Erlang runtime system, have minimal memory overhead, and can be created and terminated quickly. They
communicate using message passing, which avoids shared state and reduces the risk of race
conditions. For example:
% Spawning a new process in Erlang
Pid = spawn(fun() -> io:format("Hello from new process!~n") end),
% Sending a message to the new process
Pid ! {self(), hello}.
In this example, a new Erlang process is spawned with the spawn function, executing a function
that prints a message.
Question 05: Describe the message-passing mechanism in Erlang.
Answer: The message-passing mechanism in Erlang allows processes to communicate by sending and
receiving messages. Each process has a unique identifier (PID), and messages are sent asynchronously
using this PID. The receiving process retrieves messages from its mailbox and can pattern-match on
the messages to handle them appropriately. This mechanism promotes concurrency and avoids shared
state issues. For example:
% Spawning a process that waits for a message
Pid = spawn(fun() ->
receive
{Sender, Msg} -> io:format("Received ~p from ~p~n", [Msg, Sender])
end
end),
% Sending a message to the process
Pid ! {self(), "Hello"}.
In this example, a process is spawned that waits to receive a message using the receive block.
When the process receives a message tuple {Sender, Msg}, it prints the message and the sender's PID. The
message is sent using Pid ! {self(), "Hello"}.
Question 06: What is OTP in Erlang?
Answer:
OTP (Open Telecom Platform) is a collection of libraries and design principles for building Erlang
applications. It includes frameworks for creating servers, supervisors, and generic behaviors,
providing a standard way to structure and manage complex systems.
OTP helps developers create robust, maintainable, and fault-tolerant applications by offering
predefined patterns and tools for common tasks such as process supervision, error handling, and code
upgrades. It is a cornerstone of professional Erlang development.
Question 07: Explain the concept of a "supervisor" in Erlang's OTP framework.
Answer: A supervisor in Erlang's OTP framework is a process responsible for monitoring and
managing other processes, known as worker processes. Supervisors ensure fault tolerance by
restarting child processes if they fail according to specified strategies, such as one-for-one,
one-for-all, or rest-for-one. This helps maintain the system's stability and reliability. For
example:
% Supervisor module
-module(my_supervisor).
-behaviour(supervisor).
% Exported functions
-export([start_link/0, init/1]).
start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
ChildSpecs = [
{my_worker, {my_worker, start_link, []}, permanent, 5000, worker, [my_worker]}
],
{ok, {{one_for_one, 5, 10}}, ChildSpecs}
In this example, my_supervisor is a simple supervisor that manages my_worker processes. It uses a
one_for_one restart strategy, meaning if a worker fails, only that worker is restarted.
Question 08: What will be the output of the following code?
-module(test).
-export([reverse/1]).
reverse(List) ->
lists:reverse(List).
Result = reverse([1, 2, 3, 4, 5]),
io:format("Reversed List: ~p~n", [Result]).
Answer: The output will be 'Reversed List: [5, 4, 3, 2, 1]'.
Question 09: What is pattern matching in Erlang?
Answer:
Pattern matching in Erlang is a feature that allows you to destructure and match data structures
against patterns. It simplifies code by enabling you to extract and bind values from complex data
types, such as tuples and lists, directly in function heads or case statements. For example:
% Function using pattern matching
double({number, X}) -> X * 2.
double(_) -> error.
In this example, the function double/1 uses pattern matching to check if the input is a tuple of
the form {number, X}. If so, it extracts X and returns its double. If the input doesn't match the
pattern, it returns an error.
Question 10: What are tuples and lists in Erlang, and how do they differ?
Answer: Tuples and lists are both data structures in Erlang, but they serve different purposes
and have different characteristics. A tuple is a fixed-size collection of values, typically used for
grouping related items together. Tuples are efficient for accessing elements by index.
Lists, on the other hand, are linked sequences of elements that can vary in length. They are more
flexible than tuples and are commonly used for iterative operations and pattern matching. Lists are
generally less efficient for random access due to their linked structure.
Mid-Level Erlang Interview Questions
Here are some mid-level interview questions for Erlang:
Question 01:How does Erlang handle process termination and cleanup?
Answer:
Erlang handles process termination through its built-in process supervision mechanism. When a
process encounters an error, it can send a shutdown signal to other processes it supervises. The
supervision tree structure ensures that when a process terminates, its supervisor is notified and
can handle the cleanup or restart the failed process if necessary.
Additionally, Erlang uses lightweight processes, which are isolated and do not affect each other
directly. This isolation means that when a process dies, only its own resources are cleaned up, and
the rest of the system remains unaffected. The garbage collector then reclaims any memory used by
terminated processes, ensuring efficient resource management.
Question 02: Explain the concept of "hot code swapping" in Erlang.
Answer: Hot code swapping in Erlang allows updating code in a running system without stopping
it. This feature is crucial for maintaining high availability and minimizing downtime, as you can
deploy new versions of code while the system is live. For example:
-module(my_module).
-export([hello/0]).
hello() ->
io:format("Hello, old version!~n").
In this example, you can replace the code while the system is running.
Question 03: What will be the output of the following code?
-module(test).
-export([example/0]).
example() ->
X = 10,
Y = X + 5,
io:format("~p~n", [Y]).
Answer: The output will be 15. The code assigns the value 10 to X, then computes Y as X + 5,
which results in 15. The io:format function prints this value.
Question 04: Explain the role of "ETS tables" in Erlang.
Answer: ETS (Erlang Term Storage) tables provide a powerful and efficient in-memory storage
mechanism for Erlang processes to store and manage large amounts of data. They offer fast access to
data and support operations like reading, writing, and deleting records. For example:
-module(ets_example).
-export([start/0, add_record/2, get_record/1]).
start() ->
Table = ets:new(my_table, [named_table, public, set]),
Table.
add_record(Key, Value) ->
ets:insert(my_table, {Key, Value}).
get_record(Key) ->
case ets:lookup(my_table, Key) of
[{Key, Value}] -> {ok, Value};
[] -> not_found
end.
In this example, ets:new/2 creates a new ETS table named my_table. Records are added using
ets:insert/2 and accessed with ets:lookup/2.
Question 05: What is a "gen_server" in OTP, and how does it manage state?
Answer: In OTP, a gen_server is a behavior module that provides a generic server
implementation for handling synchronous and asynchronous calls, along with process management. It
abstracts the common patterns of server processes, making it easier to implement server-like
behaviors in Erlang applications.
A gen_server manages state through its internal state variable, which is maintained across function
calls. The state is updated through callback functions such as handle_call, handle_cast, and
handle_info, which receive messages and process them while potentially modifying the internal state.
The state is passed along with each message, ensuring that the server's state remains consistent and
up-to-date.
Question 06: What are "process dictionaries" in Erlang?
Answer: Process dictionaries in Erlang are a mechanism for storing key-value pairs within the
context of an individual process. They allow processes to maintain temporary, process-specific state
that is accessible throughout the process’s lifetime. For example:
-module(process_dict_example).
-export([start/0, set_key/2, get_key/1]).
start() ->
%% Example usage
set_key(a, 1),
get_key(a).
set_key(Key, Value) ->
%% Set a key-value pair in the process dictionary
erlang:put(Key, Value).
get_key(Key) ->
%% Retrieve a value from the process dictionary
case erlang:get(Key) of
undefined -> not_found;
Value -> {ok, Value}
end.
In this example, erlang:put/2 is used to store a key-value pair in the process dictionary, and
erlang:get/1 retrieves it.
Question 07: Explain the concept of "linking" in Erlang.
Answer: In Erlang, "linking" is a mechanism that establishes a connection between two
processes, ensuring that if one process terminates, the other is notified and can handle the
termination appropriately. This is useful for managing process dependencies and ensuring reliable
failure handling. For example:
-module(link_example).
-export([start/0, linked_process/0]).
start() ->
%% Start a process and link it to the current process
Pid = spawn(linked_process),
%% Link to the newly spawned process
erlang:link(Pid).
linked_process() ->
%% Process that will terminate
receive
_Msg -> ok
end.
In this example, erlang:link/1 establishes a link between the current process and the newly
spawned linked_process/0. If linked_process/0 terminates, the current process will receive an exit
signal and can take appropriate action, such as performing cleanup or handling errors.
Question 08: Identify the error in the following code:
-module(test).
-export([example/0]).
example() ->
{ok, File} = file:open("test.txt", [write]),
file:write(File, "Hello"),
file:close(File),
io:format("File written successfully~n").
Answer: The code has a missing closing parenthesis on the file:open function. The correct code
should be:
-module(test).
-export([example/0]).
example() ->
{ok, File} = file:open("test.txt", [write]),
file:write(File, "Hello"),
file:close(File),
io:format("File written successfully~n").
Question 09: How does Erlang handle "exceptions" and "errors"?
Answer:
In Erlang, exceptions and errors are handled using a combination of mechanisms designed for fault
tolerance and robustness. Errors typically result in the process crashing, which is a normal
behavior in Erlang's fault-tolerant design. Processes that encounter errors can terminate, and their
supervisors can handle their cleanup and restart them if needed.
Exceptions, on the other hand, are managed using Erlang’s try...catch construct. This allows
processes to handle specific errors gracefully without crashing. The catch block can catch
exceptions thrown by code within the try block, enabling recovery or logging before taking
appropriate action.
Question 10: What is the role of the -record directive in Erlang?
Answer: The -record directive in Erlang defines a record type, which is a data structure used
to group related data fields. It provides a convenient way to handle structured data, making code
more readable and easier to manage. For example:
-module(record_example).
-export([create_person/3, get_name/1]).
-record(person, {name, age, city}).
create_person(Name, Age, City) ->
#person{name = Name, age = Age, city = City}.
get_name(PersonRecord) ->
PersonRecord#person.name.
In this example, -record(person, {name, age, city}) defines a record type person with three
fields: name, age, and city. The create_person/3 function creates a new record of type person, and
get_name/1 extracts the name field from a given person record.
Expert-Level Erlang Interview Questions
Here are some expert-level interview questions for Erlang:
Question 01: Explain the difference between Erlang's processes and operating system threads.
Answer:
Erlang's processes are lightweight and managed entirely by the Erlang runtime system. They are
isolated and communicate through message passing, which avoids the need for locks and shared memory.
This model makes it easier to build concurrent applications with thousands of processes running
simultaneously without significant overhead.
Operating system threads, on the other hand, are managed by the OS kernel and have a higher resource
cost. They share the same memory space, which can lead to issues like race conditions and deadlocks.
Threads require careful synchronization to avoid these problems, making them more complex to manage
compared to Erlang's processes.
Question 02: How does Erlang handle distributed computing?
Answer:
Erlang handles distributed computing through its built-in support for message passing between
processes running on different nodes. It provides abstractions for network communication, process
management, and fault tolerance, allowing developers to build scalable and resilient distributed
systems. For example:
% On Node 1
-module(node1).
-export([start/0, send_message/2]).
start() ->
node2:start().
send_message(Node, Message) ->
{ok, _} = rpc:call(Node, io, format, ["~p", [Message]]).
% On Node 2
-module(node2).
-export([start/0]).
start() ->
io:format("Node 2 is running~n").
In this example, node1 and node2 are two separate Erlang nodes. The
send_message/2 function on node1 uses rpc:call/3 to send a message to node2, demonstrating how
Erlang’s distributed computing model allows nodes to communicate seamlessly. The start/0 function
initializes node2, showing how nodes are launched and interact within a distributed system.
Question 03: Explain how the Erlang supports functional programming?
Answer: Erlang supports functional programming through its focus on immutable data,
higher-order functions, and a declarative approach to computation. Functions are first-class
citizens in Erlang, meaning they can be passed as arguments, returned from other functions, and
assigned to variables. For example:
-module(math_utils).
-export([sum/2, apply_function/2]).
% Function to calculate the sum of two numbers
sum(A, B) ->
A + B.
% Higher-order function that applies a given function to two arguments
apply_function(Fun, A, B) ->
Fun(A, B).
In this example, sum/2 is a simple function that adds two numbers, showcasing Erlang's use of
immutable data and basic function definition. The apply_function/3 demonstrates higher-order functions
by taking another function (Fun) as an argument and applying it to two values.
Question 04: How does Erlang's garbage collection work?
Answer: Erlang's garbage collection (GC) works by managing memory at the process level. Each
Erlang process has its own heap, and garbage collection is done per process rather than globally.
When a process’s heap becomes full, the GC collects unused objects and reclaims memory, which helps
in efficiently managing memory for each process individually. For example:
-module(memory_example).
-export([start/0, create_list/1]).
start() ->
create_list(1000000).
create_list(N) ->
List = lists:seq(1, N),
io:format("List created with ~p elements~n", [length(List)]).
In this example, create_list/1 generates a large list of integers. When the list is created,
Erlang’s garbage collector will eventually reclaim the memory used by this list once it is no longer
needed. Each process, including the one running create_list/1, handles its own memory management.
Question 05: What is the observer tool in Erlang, and how does it assist in monitoring and
debugging?
Answer: The Observer tool in Erlang provides a graphical user interface for monitoring and
debugging Erlang applications. It offers real-time insights into system performance, process
activity, and system resources, making it easier to diagnose issues and understand application
behavior. To start Observer in an Erlang shell:
Running observer:start(). in the Erlang shell opens the Observer GUI. It provides various tabs and
views, including system statistics, process monitoring, and application-specific metrics. This helps
developers visualize process loads, message passing, and memory usage, aiding in the diagnosis of
performance issues and debugging of applications.
Question 06: What are some best practices for designing scalable and maintainable Erlang systems?
Answer: For designing scalable and maintainable Erlang systems, modularity is key. Break down
the system into small, reusable components or modules that handle specific tasks. This approach
improves clarity, makes it easier to test and debug, and allows for more straightforward updates and
scaling.
Another best practice is to leverage Erlang's built-in features like lightweight processes and
message passing for concurrency. Use supervision trees to manage process failures and ensure system
resilience. These techniques help maintain system stability and scalability even as demand
increases.
Question 07: Discuss the use of ets:delete/1 in Erlang.
Answer: In Erlang, ets:delete/1 is used to remove a specific object from an ETS (Erlang Term
Storage) table. ETS tables are in-memory storage for large amounts of data, and ets:delete/1 helps
manage these tables by allowing the removal of individual entries.
For example:
-module(ets_example).
-export([start/0, demo/0]).
start() ->
Table = ets:new(my_table, [named_table, public, set]),
ets:insert(Table, {key1, value1}),
ets:insert(Table, {key2, value2}),
Table.
demo() ->
Table = start(),
ets:delete(Table, key1),
io:format("Remaining entries: ~p~n", [ets:lookup(Table, key2)]).
In this example, start/0 creates an ETS table named my_table and inserts two key-value pairs.
demo/0 then deletes the entry with key1 from the table using ets:delete/1. The io:format/2 call shows
that only the entry with key2 remains after deletion.
Question 08: How Erlang's rpc module facilitates remote procedure calls?
Answer: Erlang's rpc module facilitates remote procedure calls (RPCs) by allowing functions
to be invoked on remote nodes in a distributed Erlang system. This module provides functions for
calling remote procedures and retrieving their results, making it easier to interact with processes
across different nodes. For example:
% On Node 1
-module(remote_call).
-export([start/0, remote_sum/2]).
start() ->
node2:start().
remote_sum(Node, A, B) ->
rpc:call(Node, math, sum, [A, B]).
In this example, remote_sum/3 uses rpc:call/4 to execute the sum/2 function from the math module
on a remote node specified by Node. This allows node1 to remotely invoke node2's function and get the
result back.
Question 09: Discuss the concept of "process isolation" in Erlang.
Answer: Process isolation in Erlang refers to the design principle where each process is
completely independent and isolated from others. This means that processes do not share memory and
communicate solely through message passing, which enhances fault tolerance and system reliability.
-module(isolation_example).
-export([start/0, process1/0, process2/0]).
start() ->
P1 = spawn(fun process1/0),
P2 = spawn(fun process2/0),
{P1, P2}.
process1() ->
receive
{msg, From, Content} ->
io:format("Process 1 received: ~p from ~p~n", [Content, From])
end.
process2() ->
receive
{msg, From, Content} ->
io:format("Process 2 received: ~p from ~p~n", [Content, From])
end.
In this example, process1/0 and process2/0 are two isolated processes that communicate by sending
messages. process1 and process2 do not share state or memory directly; instead, they interact only
through messages.
Question 10: What are the implications of Erlang's single-assignment variable model on
concurrent
programming?
Answer:
Erlang's single-assignment variable model simplifies concurrent programming by ensuring that
once a
variable is bound to a value, it cannot be changed. This immutability eliminates concerns about
data
races and synchronization issues that are common in mutable state systems. It guarantees that
concurrent processes operate on consistent data without conflicts.
This model promotes a functional programming style, where data is not modified but rather
transformed, making the code easier to reason about and debug. It also supports safe and
predictable
concurrent execution, as processes can communicate through message passing without worrying
about
shared state or synchronization problems.
Ace Your Erlang Interview: Proven Strategies and Best Practices
To excel in an Erlang technical interview, a strong grasp of core Erlang concepts is
essential.
This includes a comprehensive understanding of Erlang's syntax and semantics, data models,
and
control flow. Additionally, familiarity with Erlang’s approach to error handling and best
practices for building robust, fault-tolerant systems is crucial. Proficiency in working
with
Erlang's concurrency mechanisms and performance optimization can significantly enhance your
standing, as these skills are increasingly valuable.
- Core Language Concepts: Understand Erlang's syntax, lightweight processes
(actors),
pattern matching, message passing, and functional programming paradigms.
- Error Handling: Learn managing exceptions, implementing supervision trees, and
following Erlang’s recommended practices for error handling and system stability.
- Standard Library and Packages: Gain familiarity with Erlang’s built-in features
such
as OTP for building robust applications, Mnesia for distributed database management, and
commonly used third-party packages.
- Practical Experience: Demonstrate hands-on experience by building projects that
leverage Erlang for concurrent, distributed systems with high availability and fault
tolerance.
- Testing and Debugging: Start writing unit tests for your Erlang code using
frameworks
like EUnit to ensure code reliability and correctness.
Practical experience is invaluable when preparing for a technical interview. Building and
contributing
to projects, whether personal, open-source, or professional, helps solidify your understanding
and
showcases your ability to apply theoretical knowledge to real-world problems. Additionally,
demonstrating your ability to effectively test and debug your applications can highlight your
commitment
to code quality and robustness.
Get started with CodeInterview now
No credit card required, get started with a free trial or choose one of our premium plans
for hiring at scale.