Prev Next

Erlang / Erlang Advanced Interview questions

1. What is the difference between linking and monitoring a process? 2. Why would you choose monitor over link for a client process? 3. What is a dirty scheduler and when should a NIF use one? 4. Explain the internal working of Erlang's per-process garbage collector? 5. Why does Erlang use generational (per-process) garbage collection instead of a single global GC? 6. What is EPMD and what role does it play in distributed Erlang? 7. Why do distributed Erlang nodes require a shared cookie? 8. Explain the internal working of the Erlang distribution handshake between two nodes? 9. What is the two-version code loading rule and what happens when a third version is loaded? 10. Explain the internal working of code:purge/1 and code:soft_purge/1? 11. What is a gen_event and when would you choose it over gen_server? 12. What is the difference between error, exit, and throw in Erlang? 13. Why is exit/1 different from exit/2? 14. What is an OTP application (.app file) and how does it differ from a single module? 15. Explain the execution flow of application:start/1 and its dependency resolution? 16. What is a release in OTP and how does it differ from an application? 17. Explain the internal working of a relup-based hot upgrade? 18. What is the code_change/3 callback used for in gen_server? 19. How does the global module handle process name registration across a cluster? 20. Why can global name registration cause a network partition ("split brain") problem? 21. What is the pg (process groups) module used for? 22. What are Erlang maps and how do they differ from records? 23. When should you choose a map over a record for structured data? 24. What is the difference between ets:match, ets:select, and ets:foldl? 25. How can you use match specifications to filter ETS data efficiently? 26. What do the write_concurrency and read_concurrency ETS options optimize for? 27. How can you optimize binary pattern matching for parsing variable-length network protocols? 28. Why is copying a large list between processes more expensive than copying a large binary? 29. What is tail call optimization in Erlang and why does it matter for long-running loops? 30. How do you write a properly tail-recursive accumulator-based function? 31. Explain the internal working of selective receive and why message order in the queue matters? 32. What is erlang:process_flag(priority,...) used for? 33. Why should high-priority processes be used sparingly in Erlang? 34. What is rpc:call/4 and how does it work across distributed nodes? 35. What is the difference between rpc:call and simply sending a message to a remote PID? 36. How does erlang:send/3 with the nosuspend option change message-sending behavior? 37. What is Dialyzer and how does success typing differ from static typing? 38. Why doesn't Dialyzer catch every type error the way a traditional type checker would? 39. How do you define and use a custom behavior with the -callback attribute? 40. What is the difference between a behavior callback module and a plain library module? 41. Explain the internal working of exception propagation through nested try/catch blocks? 42. When should you use throw instead of returning an {error, Reason} tuple? 43. How do you troubleshoot a memory leak caused by large binaries not being garbage collected? 44. What is the significance of the binary reference count and off-heap binary garbage collection? 45. Why is the Erlang distribution protocol not encrypted by default, and how can you secure inter-node traffic? 46. Why is Mnesia's two-phase commit necessary for distributed transactions? 47. How do you troubleshoot slow ETS lookups on a heavily-used table? 48. Why might increasing the number of BEAM schedulers not improve throughput linearly?

1. What is the difference between linking and monitoring a process?

Both let one process find out when another terminates, but they differ in direction and severity of the notification. A link is bidirectional and, by default, fatal: if either linked process crashes, the other receives an exit signal and dies too, unless it's trapping exits. A monitor ( erlang:mo...

Read full answer

2. Why would you choose monitor over link for a client process?

A client that calls into a server it doesn't own — say, a request handler calling a shared cache process — usually shouldn't die just because that server crashed. Using monitor/2 instead of link/1 gets you the crash notification without coupling the client's own lifecycle to the serve...

Read full answer

3. What is a dirty scheduler and when should a NIF use one?

A regular NIF runs inline on a normal BEAM scheduler thread, which is fine for short operations but dangerous for anything that blocks or takes more than roughly a millisecond, since it stalls that entire scheduler and every process waiting behind it. Dirty schedulers are a separate pool of OS th...

Read full answer

4. Explain the internal working of Erlang's per-process garbage collector?

Each Erlang process has its own private heap, so garbage collection happens independently, one process at a time, rather than as one global stop-the-world pause across the whole node. When a process's heap fills up (typically triggered by a message arriving or an allocation), the BEAM runs a gene...

Read full answer

5. Why does Erlang use generational (per-process) garbage collection instead of a single global GC?

A single global collector would need to pause every process on the node at once to safely scan and compact memory, which directly conflicts with Erlang's goal of soft real-time responsiveness — a telecom switch or messaging backend can't tolerate a multi-hundred-millisecond global pause jus...

Read full answer

6. What is EPMD and what role does it play in distributed Erlang?

EPMD (Erlang Port Mapper Daemon) is a small, lightweight process that runs once per host and acts as a name-to-port lookup service for Erlang nodes on that machine. Each node, on startup, registers its name and the TCP port it's listening on with the local EPMD; other nodes trying to connect firs...

Read full answer

7. Why do distributed Erlang nodes require a shared cookie?

The cookie is a shared secret string that every node in a cluster must present to authenticate itself before another node will accept a connection from it — without a matching cookie, a node can't join the cluster, send messages to remote PIDs, or make remote calls. %% set via command line ...

Read full answer

8. Explain the internal working of the Erlang distribution handshake between two nodes?

When one node connects to another, they don't just open a socket and start exchanging Erlang messages — they first perform a handshake to agree on protocol version and prove they share the same cookie, before the connection is trusted for actual traffic. sequenceDiagram participant A as Nod...

Read full answer

9. What is the two-version code loading rule and what happens when a third version is loaded?

The BEAM keeps at most two versions of a module in memory at once: the current version, which new calls resolve to, and the old version, still executing for any process that was mid-loop when the reload happened. This bounded window is what allows hot code upgrades without an unbounded pile-up of...

Read full answer

10. Explain the internal working of code:purge/1 and code:soft_purge/1?

Both functions remove the old (previous) version of a module from memory, freeing it up so a future reload has room, but they differ in how they treat processes still executing that old code. code:purge/1 code:soft_purge/1 Unconditionally kills any process still running the old version. Checks fi...

Read full answer

11. What is a gen_event and when would you choose it over gen_server?

gen_event is the OTP behavior for a pub/sub-style event manager: one process holds a list of independent handler callback modules, and any event sent to the manager is dispatched in turn to every registered handler, each with its own private state. { ok , Pid } = ge n _eve nt : s tart _li n k() ,...

Read full answer

12. What is the difference between error, exit, and throw in Erlang?

All three raise an exception that unwinds the call stack until caught, but they carry different intent and default severity. error/1 exit/1 throw/1 Signals a programming/runtime error; includes a stack trace; typically left uncaught to crash the process. Signals intentional process termination; p...

Read full answer

13. Why is exit/1 different from exit/2?

exit(Reason) (arity 1) terminates the calling process itself with the given reason, exactly like any other uncaught exception, and propagates that reason to any linked processes. exit(Pid, Reason) (arity 2) instead sends an exit signal to another process, asking it to terminate, without affecting...

Read full answer

14. What is an OTP application (.app file) and how does it differ from a single module?

An OTP application is a packaged, independently startable unit of functionality — typically a whole subsystem (a web server, a connection pool, a whole product component) — described by a .app file that declares its name, version, modules, registered processes, and dependencies on oth...

Read full answer

15. Explain the execution flow of application:start/1 and its dependency resolution?

Calling application:start(my_app) doesn't just run one function — it triggers a dependency-aware startup sequence managed by the application controller. flowchart TD A[application:start my_app] --> B{Dependencies in .app already running?} B -->|No| C[Start each missing dependency first, rec...

Read full answer

16. What is a release in OTP and how does it differ from an application?

A release bundles a specific set of applications (your own plus the OTP applications they depend on, like kernel , stdlib , and sasl ) together with a matching Erlang/OTP runtime version into one deployable, versioned unit — typically built with rebar3 release or similar tooling, producing ...

Read full answer

17. Explain the internal working of a relup-based hot upgrade?

A relup (release upgrade) file is a generated script describing exactly how to move a running system from one release version to another without a restart: which applications to stop/start, which modules to load, in what order, and which processes need their internal state transformed via code_ch...

Read full answer

18. What is the code_change/3 callback used for in gen_server?

code_change(OldVsn, State, Extra) is the hook OTP calls during a release upgrade or downgrade so a gen_server can transform its existing in-memory state to match whatever shape the new code version expects, instead of losing state by simply restarting. code_change(OldVsn, State, _Extra) when OldV...

Read full answer

19. How does the global module handle process name registration across a cluster?

The global module extends Erlang's local process registry ( register/2 ) across an entire distributed cluster: global:register_name/2 makes a PID discoverable by name from any connected node, not just the one it was spawned on. global :register_name(payment_processor, self()), %% from any node in...

Read full answer

20. Why can global name registration cause a network partition ("split brain") problem?

If a cluster's network splits into two isolated groups that can each still see their own members but not the other side, each half only knows about its own view of global names. A process on each side of the partition could register the same global name independently, since neither side can detec...

Read full answer

21. What is the pg (process groups) module used for?

pg lets multiple processes join a named group, cluster-wide, so a caller can broadcast or fan out work to every member without tracking individual PIDs itself — unlike global , which maps one name to exactly one process, pg maps one name to a set of processes. pg:join(chat_room_42, self()),...

Read full answer

22. What are Erlang maps and how do they differ from records?

A map is a dynamic key-value data structure, written #{Key => Value} , where keys can be any term and the set of keys is decided at runtime rather than fixed at compile time. A record is syntactic sugar over a tuple with a fixed, compile-time-known set of named fields. M = # {name => "Ada" , age ...

Read full answer

23. When should you choose a map over a record for structured data?

Reach for a record when the shape of the data is known and fixed at compile time and you want the compiler to catch typos in field names — classic examples are a gen_server's internal state or an internal domain struct that never gets serialized to an external format. Reach for a map when t...

Read full answer

24. What is the difference between ets:match, ets:select, and ets:foldl?

All three read multiple rows out of an ETS table, but they differ in expressiveness and performance characteristics. ets:match ets:select ets:foldl Simple pattern with '_' wildcards; returns matching bound variables. Full match specifications: pattern + guard conditions + result shape, compiled f...

Read full answer

25. How can you use match specifications to filter ETS data efficiently?

A match specification is a low-level, compiled query format — a list of {Pattern, Guards, Result} tuples — that ets:select/2 executes directly inside the table engine, so filtering happens without copying every row out to the calling process first. %% Find {Key, Value} pairs where Val...

Read full answer

26. What do the write_concurrency and read_concurrency ETS options optimize for?

By default, an ETS table uses a locking scheme tuned for a single, simple access pattern. The write_concurrency and read_concurrency options let you tell the table engine what kind of concurrent access to expect, so it can pick internal locking granularity accordingly. ets:new(my_table, [set, pub...

Read full answer

27. How can you optimize binary pattern matching for parsing variable-length network protocols?

Erlang's bit-syntax lets you decode a protocol header directly in a pattern, including fields whose length depends on an earlier field — a common shape in real network protocols (a length-prefixed payload, for instance). parse(<>) -> {Type, P...

Read full answer

28. Why is copying a large list between processes more expensive than copying a large binary?

A large binary (over 64 bytes) is stored off the process heap and reference-counted, so sending it in a message copies only a small pointer-sized reference — the actual byte data never moves. A list, by contrast, is a chain of individually heap-allocated cons cells with no such off-heap, re...

Read full answer

29. What is tail call optimization in Erlang and why does it matter for long-running loops?

A function call is a tail call when it's the very last operation in a clause — nothing happens with its result except returning it directly. Erlang recognizes this pattern and reuses the current stack frame instead of pushing a new one, so a tail-recursive function can loop indefinitely wit...

Read full answer

30. How do you write a properly tail-recursive accumulator-based function?

The standard technique is to carry the running result forward as an extra argument (an accumulator), so each recursive call is the last thing that happens — there's no pending arithmetic or list-building left to do after the call returns. %% Not tail-recursive: work (H + Rest) happens after...

Read full answer

31. Explain the internal working of selective receive and why message order in the queue matters?

A receive block doesn't necessarily take the first message in the mailbox — it scans the queue in arrival order looking for the first message that matches any of its clauses, skipping over (but leaving in place) messages that don't match, then removes only the one it matched. flowchart LR A...

Read full answer

32. What is erlang:process_flag(priority,...) used for?

Every Erlang process has a scheduling priority — low , normal (the default), high , or max — that influences how eagerly the scheduler picks it from the run queue relative to other ready processes on the same scheduler. process_flag(priority, high). Higher-priority processes get sched...

Read full answer

33. Why should high-priority processes be used sparingly in Erlang?

Erlang's whole scheduling model assumes most processes are roughly equal citizens sharing time fairly via reduction counting. Marking a process high or max priority breaks that assumption locally: it can consistently jump ahead of normal-priority work on the same scheduler, and if it runs frequen...

Read full answer

34. What is rpc:call/4 and how does it work across distributed nodes?

rpc:call(Node, Module, Function, Args) lets one node synchronously invoke a function on another connected node and get the result back, wrapping the underlying message-passing machinery so it looks like an ordinary function call. Result = rpc:call('nodeb@host', lists, sort, [[3,1,2]]). %% Result ...

Read full answer

35. What is the difference between rpc:call and simply sending a message to a remote PID?

Sending Pid ! Msg to a remote PID is a raw, asynchronous, fire-and-forget send — you get no return value unless the receiving process is written to reply, and you must already know that specific PID. rpc:call/4 is a higher-level convenience built on top of message passing that packages up "...

Read full answer

36. How does erlang:send/3 with the nosuspend option change message-sending behavior?

A plain Pid ! Msg send can, in rare cases, briefly suspend the sending process if the underlying distribution buffer to a remote node is full — effectively creating unwanted back-pressure on the sender. erlang:send(Pid, Msg, [nosuspend]) asks for the same send but refuses to suspend the cal...

Read full answer

37. What is Dialyzer and how does success typing differ from static typing?

Dialyzer is Erlang's static analysis tool for finding type discrepancies — but it works differently from a conventional static type checker like those in typed languages. Instead of requiring every value to be annotated and rejecting code that can't be proven correct up front, Dialyzer uses...

Read full answer

38. Why doesn't Dialyzer catch every type error the way a traditional type checker would?

Success typing is intentionally conservative: it only reports a call site when it can prove the function can never succeed with those argument types, based on inferring what the function's body could actually produce or accept. If a function's implementation is loose enough that a call is merely ...

Read full answer

39. How do you define and use a custom behavior with the -callback attribute?

Beyond OTP's built-in behaviors (gen_server, supervisor, and so on), you can define your own reusable process pattern by declaring the required callbacks a module implementing your behavior must export, using the -callback attribute. %% my_plugin.erl - the behavior definition -module(my_plugin). ...

Read full answer

40. What is the difference between a behavior callback module and a plain library module?

A plain library module (like lists or string ) exposes stateless functions you call directly whenever you want — there's no framework driving it, no lifecycle, no expected shape beyond whatever arguments and return value each function defines on its own. A behavior callback module instead p...

Read full answer

41. Explain the internal working of exception propagation through nested try/catch blocks?

An exception raised anywhere inside a try block unwinds the call stack looking for the nearest enclosing catch whose pattern matches the exception's class and reason — skipping over any intervening function calls that don't themselves catch it, exactly like exception handling in most langua...

Read full answer

42. When should you use throw instead of returning an {error, Reason} tuple?

Both signal "this didn't work," but they fit different shapes of control flow. A tagged {error, Reason} return keeps the failure visible in the function's ordinary return value, forcing every caller to explicitly pattern-match and decide what to do — it composes naturally with Erlang's usua...

Read full answer

43. How do you troubleshoot a memory leak caused by large binaries not being garbage collected?

Large (>64 byte) binaries live off the process heap and are reference-counted, but the counter only drops when the owning process's heap is actually garbage collected — a process that receives many large binaries but rarely triggers a GC cycle (because its own small heap of local variables ...

Read full answer

44. What is the significance of the binary reference count and off-heap binary garbage collection?

Any binary larger than 64 bytes is allocated once, off the process heap, and shared by reference rather than copied whenever it's passed around within reach of the same underlying data (for instance, sub-binary matches, or being stored in multiple variables). A reference count tracks how many liv...

Read full answer

45. Why is the Erlang distribution protocol not encrypted by default, and how can you secure inter-node traffic?

The standard distribution protocol (used for node-to-node traffic once the cookie handshake succeeds) sends Erlang term data in plaintext over TCP — it was designed assuming nodes sit on a trusted, private network, not a hostile or public one, so encryption wasn't built in as the default to...

Read full answer

46. Why is Mnesia's two-phase commit necessary for distributed transactions?

When a Mnesia transaction writes to tables replicated across multiple nodes, every replica must end up agreeing on the outcome — either all of them commit the change, or none of them do. Without coordination, a network hiccup could leave one node having applied the write while another hasn'...

Read full answer

47. How do you troubleshoot slow ETS lookups on a heavily-used table?

A "fast" O(1) ETS set / bag lookup can still degrade under real load for a handful of specific, diagnosable reasons rather than randomly — the first step is confirming which of these actually applies before reaching for a fix. ets:info(Tab, memory), %% table size, in words ets:info(Tab, siz...

Read full answer

48. Why might increasing the number of BEAM schedulers not improve throughput linearly?

Adding scheduler threads only helps if there's enough independent, ready-to-run work to actually fill them, and if nothing else becomes the new bottleneck once CPU contention eases. Several factors commonly cap the gains well before scheduler count matches core count: Contended shared resources &...

Read full answer

«
»
AI

Comments & Discussions