OCaml Module 9
Printing, polymorphic variants and objects interview questions
Interview questions on printing, polymorphic variants and objects: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
When would you reach for Format rather than Printf in OCaml?
-
Fresher to mid
In a polymorphic variant type that OCaml infers, what do the < of a closed row and the > of an open row each promise?
The < marks a closed row: a value of that type carries at most the listed tags, so a function taking it, such as a match on the tags Green and Red with no wildcard, refuses any other tag at compile time. The > marks an open row: the value carries at least the listed tags and may carry more, like a lone tag or the argument of a match that ends in a wildcard. Both signs hide a row variable, so the type can still change as more uses are checked; a type with neither sign is exact.
From the lesson Polymorphic variants
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A service logs with let debug fmt = if verbose then Printf.eprintf fmt else Printf.ifprintf stderr fmt. With verbose off, handlers that call debug "rows %d\n" (count_rows db) are still slow, although nothing is printed. Explain why, and design a debug helper whose disabled calls do almost no work.
- What it tests
- Knowing that OCaml evaluates arguments before a call, what ifprintf and ksprintf still do, how a closure or %t puts work off, and how to keep a wrapping helper polymorphic and type-checked.
- First step
- Count before you change anything: make count_rows increment a counter, run 1,000 debug calls with verbose off and read the counter. Then ask which part of debug "rows %d\n" (count_rows db) runs before debug itself is entered.
- Follow-ups
-
- Why does a helper built on Printf.ksprintf not fix it?
- How do you put a "[db] " prefix on every message and keep the type check?
- Why does let debug = if verbose then Printf.eprintf else Printf.ifprintf stderr fail on its second use?
From the lesson Printing with Printf and Format
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; nothing renews unless you choose to. 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 4 minutes
-
Mid to senior
Bills are printed with Printf.printf "%-20s|%8.2f\n" name amount. Rows whose item names are written in Hindi, such as मसाला डोसा, push the amount column out of line, while English names line up. Explain what the width 20 counts, and how you would line up UTF-8 names in OCaml 5.5.
- What it tests
- Knowing what a Printf width measures, how UTF-8 stores Devanagari and accented letters, how to count characters in OCaml, and where a character count still differs from the columns a terminal draws.
- First step
- Measure before you fix: print String.length of the Hindi name and of an English name that lines up, and compare both with the number of letters and marks you see on the screen.
- Follow-ups
-
- Does Format pad %-20s differently from Printf in OCaml 5.4 and later?
- Why can a character count still misalign a row that holds an emoji or a Chinese name?
- How would you test the alignment of the bill?
From the lesson Printing with Printf and Format
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A payments service has three modules, a ledger, a bank client and a fraud screen, and each .mli declares its own exact error type as polymorphic variant tags. Design the error type and the one handler that turns every failure into a message, and say what happens when a fourth module adds a tag.
- What it tests
- Joining exact polymorphic variant types into one union, converting each result with a coercion, and writing the handler without a wildcard so that warning 8 or a type error points at every new tag.
- First step
- Exact types cannot unify, so chaining the three results with let* fails. Declare the union once in the service, type error = [ Ledger.error | Bank.error | Fraud.error ], convert each module's result into it with :>, and give the handler the annotation error -> string, with one case per tag and no wildcard.
- Follow-ups
-
- The fourth module is added to the union, but nobody updates the handler. What does the build say?
- Why not end the handler with a wildcard that returns a generic message?
- When would an ordinary variant with one constructor per module serve better?
From the lesson Polymorphic variants
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
-
Fresher to mid
A logging helper maps levels to names with a match on the tags Debug, Warn and Error that ends in a wildcard returning INFO. A call with the misspelt tag Eror compiled and logged a disk-full failure as INFO. Explain why the compiler accepted it, and change the code so that it refuses such typos.
- What it tests
- Reading the open row a wildcard gives, knowing that open rows unify by adding up their tags, and closing the type where callers meet it with an exact annotation.
- First step
- Read the type the toplevel prints for the helper: the wildcard gave its argument an open row, at least Debug, Warn and Error, so Eror is one more tag it accepts. Declare a level type with the four tags and annotate the public log function's argument with it; the misspelt call is then refused on the line where it is written.
- Follow-ups
-
- Why did the compiler not suggest the right name, as it does for ordinary constructors?
- The wildcard stays, because a library sends more levels such as Trace. How do you keep the check?
- Would an ordinary variant be the better choice here?
From the lesson Polymorphic variants
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Senior to staff
Your service parses JSON with Yojson.Basic, whose type has seven tags, and a reporting library takes and returns Yojson.Safe.t, which adds one tag for integers too large for an OCaml int. How do you hand your values to the library, what does that cost, and how do you take its results back?
- What it tests
- Knowing that a coercion into a larger exact type returns the same value, recursion included, that the reverse needs a conversion, and deciding at the boundary what a large integer means.
- First step
- Every tag of Yojson.Basic.t is also a tag of Yojson.Safe.t with the same argument, so a coercion with :> converts a whole tree, and returns the very same value. The way back cannot be a coercion, because a Safe.t value may hold the extra tag Intlit: convert once at the boundary with Yojson.Safe.to_basic, or reject such values there.
- Follow-ups
-
- What does the coercion cost for a tree of 1,000 values?
- What does Yojson.Safe.to_basic do with 12345678901234567890?
- Your own JSON type has six tags, with no floats and no long integers. What changes?
From the lesson Polymorphic variants
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes