Articles
Coding Interview with an Unfamiliar Library: Read the API, Build a Probe, Verify the Result
Practice unfamiliar-library coding interviews with a contract-reading workflow, minimal API probes, edge-case tests, and a clear verification script.
An interviewer gives you a small library you have never used and asks for working code in 35 minutes. The hardest part is rarely typing the final loop. It is discovering which behavior is guaranteed, which behavior you only guessed, and whether a successful example actually proves the result.
Use a three-part response: read the contract, build a discriminating probe, verify the composed behavior. This guide turns that sequence into a complete original exercise. The invented API below is a practice fixture, not a claim about a real package or a particular employer. If the interview environment restricts documentation, terminals, or external sites, ask what is allowed before consulting them. CoderPad's candidate guide documents an editor, Run output, and an optional REPL for some languages; it does not set your interviewer's rules.
For a related practice prompt, try Define a Reliable Trade-Reconciliation Library Contract. That record is practice material, not evidence that your target company asks this exercise.
Start with permissions and the observable contract
Ask one concise question: “May I use the provided docs and run small probes, or should I reason only from the interface shown?” This is useful even in a collaborative interview. An allowed local help page is different from an unauthorized web search or AI assistant. If the interviewer says to work only with supplied material, do exactly that and identify assumptions aloud.
Next, restate the task as input, output, and failure behavior. Suppose a fictional package called LedgerKit provides:
parse_amount(text: str) -> Amount
Accepts a decimal amount like "12.30".
Raises AmountFormatError for malformed input.
add(left: Amount, right: Amount) -> Amount
Returns a new Amount; inputs are not modified.
format_amount(value: Amount, currency: str) -> str
Returns a display string for a supported currency.
Raises CurrencyError for an unsupported currency.
The interview task is: given a list of optional transaction strings, return the total in USD, ignoring blank entries but rejecting malformed nonblank entries. You are also told the list may be empty. The names are not enough to solve it. Does “blank” include whitespace? Does parse_amount accept a plus sign? Does format_amount round, and is a negative amount allowed? Is a zero Amount constructible only through parse_amount("0")? These questions change tests and error handling.
Prioritize contract facts in this order: the accepted input domain, return type, error semantics, mutation or ownership, and only then convenience methods. Python's inspect.signature can show a callable's parameters when runtime introspection is available, but a signature does not reveal all semantic guarantees. A method named sum might skip invalid elements, coerce them, or stop with an exception. Documentation and a probe must resolve that uncertainty.
Do not spend ten minutes reading an entire package manual. Scan the method page, one example, and the error section relevant to your path. Write down the two or three facts you will rely on. In the fixture, that might be: parse_amount raises rather than returns None; add is immutable; format_amount is for output only. Everything else can wait.
Build a probe that distinguishes interpretations
A probe is a tiny experiment chosen because different plausible API meanings produce different results. Printing an ordinary 12.30 conversion is weak: nearly every interpretation agrees. Test the disputed edges instead.
| Uncertainty | Smallest probe or question | What changes in the solution |
|---|---|---|
| Whitespace handling | parse_amount(" 12.30 ") | Trim before parsing or treat spaces as invalid |
| Negative support | parse_amount("-2.00") | Permit refunds or reject at the application boundary |
| Empty input | parse_amount("0") | Choose a valid accumulator seed |
| Error contract | parse_amount("12x") | Catch a specific error or let it propagate |
| Immutability | Compare left before and after add(left, right) | Assign returned value rather than assuming in-place change |
Ask the interviewer which behavior the task requires before you probe everything. The first two rows may be product-policy questions, not mysteries the library should decide. A good line is: “I see an ambiguity about whitespace. I will treat whitespace-only items as blank, trim nonblank items, and reject a malformed amount unless you prefer a different contract.” That is a transparent assumption, not a silent guess.
If execution is allowed, keep the probe isolated from the solution. Use the provided scratch pane or a short test rather than adding debug calls to production code. When execution is not allowed, state the competing interpretations and proceed under one explicit assumption. You can still use static evidence: examples, type signatures, documented exceptions, and caller code included in the prompt.
The point is information gain. A probe that cannot alter your implementation or confidence is probably unnecessary. Conversely, if the entire algorithm depends on whether add mutates, resolve that before building a chain of calls.
Compose the smallest correct solution
Under these explicit fixture assumptions—whitespace-only entries count as blank, trimming is allowed, negatives represent refunds, parse_amount("0") is valid, and malformed nonblank values should propagate AmountFormatError—the core solution is short:
def total_usd(entries, ledgerkit):
total = ledgerkit.parse_amount("0")
for raw in entries:
if raw is None or raw.strip() == "":
continue
value = ledgerkit.parse_amount(raw.strip())
total = ledgerkit.add(total, value)
return ledgerkit.format_amount(total, "USD")
Notice the dependency on the task contract. raw.strip() assumes every non-None entry is a string. If mixed types are possible, validate types rather than letting an incidental AttributeError define the public behavior. The code also delegates decimal representation to LedgerKit; it does not convert to a binary float and risk an avoidable currency arithmetic detour. We do not catch every exception merely to return a convenient zero: that would silently turn malformed data into a legitimate amount.
If the library instead returns a tagged result such as Ok(value) or Err(reason), the control flow must unwrap that result. If add mutates, the assignment may be redundant, although retaining it might be wrong if the method returns None. The design is therefore conditional on evidence, not a memorized pattern.
An interviewer may ask for an incremental version that streams transactions. In that case, ask whether the final formatting must wait for the stream to end, whether the iterator can be consumed once, and where the first error should stop processing. Do not invent streaming semantics from a list-based prompt. Expand the interface only when asked.
Verify behavior at three levels
First check a normal example: ["1.20", "2.30"] should represent 3.50 in whatever exact display form the fixture documents. Do not assert a dollar symbol unless format_amount promises it. Second check boundary classes: empty list, None, spaces, a negative value, and malformed text. Third check integration: confirm the code uses the library's parser, adder, and formatter in the documented order and preserves the specified error.
An original test matrix makes the interview conversation concrete:
| Input | Expected semantic result | Reason |
|---|---|---|
[] | USD zero | Accumulator seed and empty case |
[None, " ", "1.20"] | USD 1.20 | Skip optional/blank entries only |
["5.00", "-2.00"] | USD 3.00 | Refund handling |
["1.00", "oops"] | AmountFormatError | Malformed nonblank data is not ignored |
["0.10", "0.20"] | USD 0.30 | Avoid float arithmetic assumptions |
Python's unittest documentation describes assertions and subtests that can express cases like these. In a short interview, a few direct assertions may be enough. What matters is that expected values come from the task contract rather than from copying whatever the current implementation prints. Otherwise your test becomes a transcript of the bug.
If you cannot run code, do a mental dry run for one ordinary and one adversarial case. Track total after each entry and note precisely which API call can raise. A mental trace checks your control flow, but it cannot establish that the unfamiliar library behaves as guessed. Say that limitation out loud. If you can run code, show both the probe result and the final cases rather than presenting a single green run as comprehensive proof.
How to talk through a wrong result
Suppose the result for ["1.20", "2.30"] is USD zero. Do not immediately rewrite the loop. Separate the layers. Is entries actually populated? Does parse_amount return an Amount? Is add immutable, requiring the returned object to be assigned? Does format_amount receive the updated object? One observation at a time narrows the fault.
Suppose a malformed string produces USD 1.20 rather than an error. Check whether the code accidentally catches AmountFormatError and continues. This is not a numeric issue; it is a policy mismatch. Suppose the result is USD 3.49. Check whether a float conversion was introduced outside LedgerKit, but do not assert the library itself is inaccurate without isolating its calls.
Use a compact communication pattern: “I expected X from the documented contract; observed Y in this probe; therefore I will inspect Z.” That is stronger than “the library seems weird.” It also gives the interviewer a chance to correct a mistaken assumption early.
Common traps and a better answer
The first trap is treating autocomplete as documentation. Completion can suggest a method name, but it cannot tell you whether the method mutates, what errors it raises, or which edge inputs are valid. The second is broad probing: trying every API method before understanding the one path required. The third is over-catching: swallowing parse errors to make a demo pass. The fourth is relying on unauthorized tools because the environment happens to expose them. Ask first and keep your work reproducible within the allowed environment.
A concise interview answer could be: “I will confirm the allowed references, restate the input and error contract, inspect only the parser/add/formatter APIs, then test the disputed blank and error behavior with tiny probes. I will implement the smallest composition and verify empty, ordinary, refund, and malformed cases. If execution is unavailable, I will mark the unverified library assumption rather than pretend a dry run proves it.”
That answer shows judgment beyond syntax. It keeps contract discovery, code construction, and verification separate so the interviewer can see exactly what is known.
Practice questions to extend the exercise
| Question | Transferable skill |
|---|---|
| Define a Reliable Trade-Reconciliation Library Contract | Make input, error, and ordering behavior explicit |
| Turning Interview Code into a Reusable Library or API | Expose a stable interface after proving the core |
| Design an Extensible Tree Traversal Library | Read and shape an unfamiliar abstraction boundary |
| Implement CRUD API and tests | Verify observable behavior, not just a happy path |
| Test Severity Filtering with Contract-Distinguishing Cases | Choose cases that separate plausible semantics |
When you practice, swap in a different fictional API and write down your assumptions before coding. The goal is not to memorize LedgerKit; it is to make uncertainty visible, resolve what matters, and test the result you actually promised.
Sources and further reading
- CoderPad Interview preparation guide — platform capabilities; interview-specific permissions still come from the interviewer.
- Python
inspectdocumentation — callable signatures and introspection limits. - Python
unittestdocumentation — assertions and organized test cases.