Elixir & Erlang/OTP Module 2
Values and types interview questions
Interview questions on values and types: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What does / return in Elixir, and how do you divide two integers to get an integer?
Answered in the lesson Numbers: integers, floats and arithmetic
-
Fresher
What is an atom in Elixir, and why are true, false and nil atoms?
-
Fresher
What is the difference between byte_size/1 and String.length/1 in Elixir?
-
Fresher
When do you use a tuple and when a list in Elixir?
-
Fresher
What is the difference between a keyword list and a map in Elixir, and when do you use each?
Answered in the lesson Keyword lists, maps, MapSets and nested data
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
An invoice service keeps prices as floats in rupees and converts them to paise with trunc(price * 100) before charging. Some customers are charged one paisa less than the price they saw. Explain the cause, and redesign how the service handles money, including how it rounds taxes and discounts.
- What it tests
- Knowing how floats store decimal fractions, why trunc loses a paisa, and designing money handling around integer minor units with one written rounding rule.
- First step
- Reproduce it with one price: 19.99 * 100 is 1998.9999999999998 as a float, so trunc gives 1998. The float stored for 19.99 is a little below 19.99 and trunc drops the fraction. So the fix starts where prices enter the system: read them straight into whole paise and never hold money in a float.
- Follow-ups
-
- Would round(price * 100) be enough instead of trunc?
- How do you add an 18 % tax to 1,999 paise without floats?
- A partner API sends prices as JSON numbers such as 19.99. What do you do with them?
From the lesson Numbers: integers, floats and arithmetic
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A scheduler shows a meeting 5 hours before 02:00 as starting at hour -3, and its result list says "page 10 of 10" for 101 results at 10 a page, so the last result never appears. Find both bugs, choose the integer functions that fix them, and say which cases you would test.
- What it tests
- Telling truncated, floored and ceiled division apart, choosing the remainder that clock arithmetic needs, and picking the edge cases that catch rounding mistakes.
- First step
- Work both values out by hand. 2 - 5 is -3, and rem/2 keeps the sign of the dividend, so rem(-3, 24) is -3 where the clock needs 21; Integer.mod(-3, 24) gives 21. div(101, 10) is 10 pages, one result short; Integer.ceil_div(101, 10) gives 11.
- Follow-ups
-
- Why not write rem(rem(h, 24) + 24, 24) instead?
- Which inputs go into the tests, and why those?
- The code must also run on Elixir 1.19. What replaces Integer.ceil_div/2?
From the lesson Numbers: integers, floats and arithmetic
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
An HTTP handler turns the query parameter sort=name into an atom with String.to_atom/1 to choose the sort field. Explain the risk, rewrite the lookup safely, and say what the handler should answer when the value is unknown.
- What it tests
- Knowing that the atom table is never garbage-collected and has a limit, and replacing dynamic atom creation with an allowlist that rejects everything else.
- First step
- Count what a client controls: every new value of sort becomes a new atom, the table never shrinks, and by default it holds 1,048,576 names. A loop that sends sort=a1, sort=a2 and so on adds names until the limit. Then list the values that are valid: the fields the endpoint can actually sort by.
- Follow-ups
-
- Is String.to_existing_atom/1 a good enough fix?
- The sortable fields will change. How do you keep the allowlist in step with them?
- How would you notice this problem on a running server?
From the lesson Atoms, booleans, nil and comparison
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A report sorts Date values with Enum.sort/1 and marks a task late when today > due. It lists the last day of March after the first day of April, and on the last day of April it marks a task due the next day as late. Explain both bugs, fix them, and say which tests would have caught them.
- What it tests
- Telling structural comparison from comparison by meaning, using Date.compare/2 and Enum.sort/2 with a module, and choosing test dates that cross a month or a year.
- First step
- Look at what > compares. A Date is a struct, which is a map, and maps of one size compare their values in key order: :calendar, then :day, then :month and :year. So the day decides first, and day 30 beats day 1, which makes the last day of April look later than the first day of May.
- Follow-ups
-
- Would sorting the dates by Date.to_string/1 work instead?
- The report also sorts DateTime values from two time zones. What changes?
- Which test data would have caught both bugs?
From the lesson Atoms, booleans, nil and comparison
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A sign-up form limits names to 20 characters with byte_size(name) <= 20 and shortens long display names with binary_part/3. Users with Hindi and Tamil names are rejected, and some previews show broken characters. Explain both bugs, and design the check and the shortening.
- What it tests
- Telling bytes from graphemes in UTF-8, cutting text only at grapheme boundaries, and keeping a separate byte limit where storage needs one.
- First step
- Measure Indian text in each unit: नमस्ते is 18 bytes, 6 code points and 3 graphemes, so a 20-byte limit leaves room for only six Devanagari code points, and those three graphemes already take 18 of the 20 bytes. The form means 20 characters as a person sees them, which is String.length/1, and the preview must be cut with String.slice/3.
- Follow-ups
-
- The database column holds at most 80 bytes. How do the two limits fit together?
- Should the limit count code points instead of graphemes?
- How would you test the fix with names in several scripts?
From the lesson Strings, binaries and charlists
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Users named José or Zoë report that logging in sometimes fails on one device although they type their name correctly. The lookup compares the typed name with the stored one using ==. Explain the cause and fix it, including a login that ignores case.
- What it tests
- Knowing that equal-looking text can hold different code points, normalising at the edge, and combining normalisation with a change of case.
- First step
- Compare bytes, not what is on the screen: é can be the single code point U+00E9, two bytes, or e followed by the combining accent U+0301, three bytes. If one device sends one form and another the other, == compares different bytes, although both names print as José.
- Follow-ups
-
- Do you normalise when storing, when comparing, or both?
- Which normalisation form do you choose, and why not NFKC?
- How do you move the names already stored to the new rule?
From the lesson Strings, binaries and charlists
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A function builds a report of 50,000 lines by appending each line with acc ++ [line], and it takes several seconds. Explain where the time goes, estimate how it grows with the number of lines, and rewrite it.
- What it tests
- Knowing that ++ copies its left list, turning that into a count that grows with the square of the length, and building lists by prepending.
- First step
- Count the copies: the k-th append copies the k - 1 lines before it, so 50,000 appends copy 50,000 × 49,999 / 2, about 1.25 billion list cells. In the lesson's measurement 20,000 appends already took nearly a second, and the work grows with the square of the number of lines.
- Follow-ups
-
- The report is written to a file at the end. Does it need one list at all?
- Would Enum.reduce/3 with acc ++ [line] be faster than the hand-written loop?
- How would you show with a measurement that the fix works?
From the lesson Lists, tuples and ranges
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A service keeps waiting jobs in a list, adds each new job with jobs ++ [job] and takes the next one with hd/1 and tl/1. With 100,000 jobs waiting it slows to a crawl. Explain why, and choose a structure in which adding and taking are both cheap.
- What it tests
- Knowing which list operations are cheap at which end, and choosing a queue that makes both ends cheap, such as Erlang's :queue module.
- First step
- Look at each end separately. Taking from the front with hd/1 and tl/1 costs one step, but adding with jobs ++ [job] copies all 100,000 waiting jobs on every arrival. The structure must make adding at the back as cheap as taking from the front.
- Follow-ups
-
- How can a queue make both ends cheap when lists are cheap only at the front?
- Why not prepend new jobs and take the oldest with List.last/1?
- The jobs gain priorities. What changes?
From the lesson Lists, tuples and ranges
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
An endpoint builds its JSON response from an Elixir map, and a contract test compares the response text with a stored file. After an Erlang/OTP upgrade the test fails, and the only difference is the order of the keys. Explain why, and fix the test and, if a client needs it, the endpoint.
- What it tests
- Knowing that map key order is undefined and what OTP 29 does guarantee, comparing data instead of text, and fixing an order explicitly where a client truly depends on one.
- First step
- First check whether any data changed: decode both texts and compare the results. If they are equal, only the order of the keys moved, and that order was never promised. On Erlang/OTP 29.1.1 a map of 32 integer keys came back sorted and one of 33 did not, so the order can shift with the size of a map or with a version.
- Follow-ups
-
- Why did the test pass for so long before the upgrade?
- A partner's parser really does need the keys in a fixed order. What do you send?
- How do you find other tests with the same weakness?
From the lesson Keyword lists, maps, MapSets and nested data
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
User settings are stored as nested maps, and pages read settings.notifications.email.enabled. Older users have no :notifications key, so their pages crash with KeyError, and a fix with put_in raises ArgumentError. Design reading and updating that work for every user.
- What it tests
- Handling nested data that may lack whole levels, defaults with get_in and Access.key/2, and filling missing levels once by merging stored settings into defaults.
- First step
- Find where the data is incomplete: old records lack a whole level, so every path through :notifications can fail. The dot path raises KeyError at the first missing level, and put_in raises because it cannot put a key into nil. Then decide the default of each setting before choosing the code.
- Follow-ups
-
- Why not check every level before each read?
- How do you merge stored settings into the defaults when both are nested?
- Where do the defaults live, and how do you test them?
From the lesson Keyword lists, maps, MapSets and nested data
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All Elixir & Erlang/OTP interview questions · The Elixir & Erlang/OTP track