Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

F# Module 4

Modelling data with types interview questions

Interview questions on modelling data with types: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.

22 questions: 7 warm-ups and 15 with model answers · Levels: Fresher to staff

Warm-up questions

Short questions with short answers, each answered in full.

  1. Fresher to mid

    When do you use a tuple and when a record in F#?

    Answered in the lesson Tuples, records and anonymous records

  2. Fresher to mid

    How does a discriminated union differ from an enum in F#, and when do you use each?

    Answered in the lesson Discriminated unions and enums

  3. Fresher to mid

    What do the as, OR and AND patterns do in F#? Give an example of each.

    Answered in the lesson F# pattern matching in depth

  4. Fresher to mid

    How does a complete active pattern differ from a partial one in F#?

    Answered in the lesson Active patterns for readable matching

  5. Fresher to mid

    How do option and value option differ in F#, and when does each fit?

    Answered in the lesson Options, value options and nullness

  6. Fresher to mid

    What does structural equality mean in F#, and which types have it?

    Answered in the lesson Structural equality, comparison and hashing

  7. Fresher to mid

    What are units of measure in F#, and what do they cost at run time?

    Answered in the lesson Units of measure

Questions with model answers

What each question tests, the first step of a good answer and the follow-ups to expect.

  1. Mid to senior

    A teammate added a Store record with the same Id and City fields as your Shipment record. Now a file that builds shipments with { Id = …; City = … } fails to compile, although nobody touched it. Why, and how do you make record construction robust in a large codebase?

    What it tests
    How F# infers a record expression's type from its labels and an expected type, why the error appears far from its cause, and which fixes scale beyond one call site.
    First step
    Read the error at the failing line: the expression was inferred as Store, not Shipment. When nothing tells the compiler which type is expected, a record expression takes the most recently declared type that has its labels, and Store now comes later. Fix the line by naming the type once, then ask why two types share every label.
    Follow-ups

    From the lesson Tuples, records and anonymous records

    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.

    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
  2. Mid to staff

    A shared F# library returns anonymous records from its public functions. A minor release added one field to a result, and several services that use the library stopped building. Why did an additive change break them, and how would you design the API instead?

    What it tests
    Knowing that an anonymous record's type is its exact field set, with no structural subtyping, and how to version a public API with named records instead.
    First step
    Find what the failing services do with the result: annotate its type, pass it to their own functions, or compare it with values they build. Each of those names the old field set, and the new value has one field more, so the compiler reports an extra field. Then move the public results to named record types declared in the library.
    Follow-ups

    From the lesson Tuples, records and anonymous records

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  3. Senior to staff

    A pricing service keeps 5 million ticks in memory as an array of F# records with three float fields, and its garbage-collection pauses keep growing. Would you make the record a struct? Estimate what changes, and say what you would measure before and after.

    What it tests
    Reasoning about reference and value layouts with numbers, knowing what a struct record costs as well as saves, and measuring allocation and pauses instead of guessing.
    First step
    Estimate both layouts. Each reference record is an object of its own plus an 8-byte reference in the array; a struct record of three floats is 24 bytes stored inline, with no object for the collector to trace. Then measure allocated bytes and pause times on the real service before deciding, and check how the ticks are passed around.
    Follow-ups

    From the lesson Tuples, records and anonymous records

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  4. Mid to senior

    A service reads order states from a database column as an F# enum. Another team added state 7 to the table, and days later a billing job failed with MatchFailureException, far from the code that reads the column. Explain the failure and redesign the boundary so it cannot happen silently.

    What it tests
    Knowing that enums hold any integer and that a complete-looking match fails on unnamed values, and moving the check to the boundary with a union and an explicit error.
    First step
    An enum holds any integer of its underlying type, so 7 became a valid OrderState with no name and travelled until a match without a fallback met it. Convert the column into a union at the edge, with a function that returns Ok for known numbers and Error for the rest, and decide there what an unknown state means.
    Follow-ups

    From the lesson Discriminated unions and enums

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  5. Fresher to senior

    A refund went to the wrong customer because a function took a customer number and an order number, both int, and a caller passed them in the wrong order. How would you use F# types to make this mistake impossible, and what does your fix cost?

    What it tests
    Using single-case unions instead of type abbreviations, constructing them at the edges, and weighing allocation, serialization and interop costs with numbers.
    First step
    A type abbreviation such as type OrderNo = int is only another name for int, so the swapped call compiled. Wrap each id in its own single-case union, type OrderId = OrderId of int, so the compiler refuses a CustomerId where an OrderId is expected, and build the ids once where they enter the program.
    Follow-ups

    From the lesson Discriminated unions and enums

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  6. Mid to senior

    A discount rule reads | Gold spend | Silver spend when spend > 10000m -> spend * 0.1m. A Gold customer who spent 5,000 rupees complains that they got no discount; the author meant the limit for Silver only. Explain the bug and rewrite the rules so that the intent is clear and the compiler still checks them.

    What it tests
    Knowing that a when guard covers every side of an OR pattern, rewriting rules so each guard is local, and keeping the match exhaustive without a wildcard.
    First step
    The guard belongs to the whole rule, so it applies to Gold spend as well as Silver spend, and Gold 5,000 fell through to the catch-all, which gave 0. Split the OR pattern into one rule per case, put the guard only on the Silver rule, and end with rules for the remaining Silver values instead of a wildcard.
    Follow-ups

    From the lesson F# pattern matching in depth

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 2 minutes
  7. Mid to senior

    A settings reader receives values as obj from a .NET library and matches them with :? int, :? string and :? bool, with a fallback rule that uses the default. After a library upgrade, numbers arrive as int64 and every numeric setting quietly takes its default. Diagnose it and design a safer boundary.

    What it tests
    Understanding that type-test patterns test the exact runtime type, why a silent fallback hides the change, and how to convert obj values into F# types once, with errors that say what arrived.
    First step
    A boxed int64 is not an int, so :? int fails and the fallback rule takes the default without a word. Make the fallback report the type it saw, add the numeric types the library can return, and convert every setting into a typed record in one function at the boundary that returns an error for values it does not understand.
    Follow-ups

    From the lesson F# pattern matching in depth

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  8. Mid to senior

    An active pattern (|Known|_|) looks a customer up in a database. A match uses it in four rules with different guards, and the database now sees up to four queries per message. Explain why, and restructure the code.

    What it tests
    Knowing that a match may run an active pattern once per rule that tests it, keeping active patterns free of side effects, and moving effects to one call before the match.
    First step
    Each rule that tests Known is a separate test of the pattern, and when a guard fails the next rule runs the pattern again, so every failed rule means another query. Look the customer up once, before the match, and match on the result; keep active patterns pure and cheap, like the functions they are.
    Follow-ups

    From the lesson Active patterns for readable matching

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 2 minutes
  9. Senior to staff

    Your library exposes a union Payment that callers match on directly. You need to split Card into DebitCard and CreditCard without breaking them. How do you use active patterns to change the representation, and how do you move callers over?

    What it tests
    Using active patterns as a stable public view over a private representation, as the F# component design guidelines suggest, and planning a migration that keeps old matches compiling.
    First step
    Make the union's representation private and publish a complete active pattern with the old cases, (|Upi|Card|Cash|), implemented over the new cases, so existing matches keep compiling. Add a second pattern or new functions that expose the split for callers who need it, then deprecate the old view over a release cycle.
    Follow-ups

    From the lesson Active patterns for readable matching

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 2 minutes
  10. Mid to senior

    An F# library exposes tryFindCustomer: string -> Customer option. A C# service calls it, reads .Value and crashes with NullReferenceException whenever no customer is found, while every F# test passes. Explain why, and design the API so that both F# and C# callers handle a missing customer safely.

    What it tests
    Knowing that None is stored as null at run time, what a C# caller sees of an F# option, and how to give C# a shape it understands while F# code keeps its options.
    First step
    Check what the C# caller actually receives. F# stores None as null, so for a missing customer the C# variable is null and any member access throws, whatever the F# type says. Then separate the two audiences: keep the option for F# callers, and add a method in a shape C# already handles, such as a TryFindCustomer that returns bool with the customer in an out parameter.
    Follow-ups

    From the lesson Options, value options and nullness

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  11. Senior to staff

    Your team wants F# 9 nullness checking in a service of 180 F# files that calls many .NET libraries. A trial build with <Nullable>enable</Nullable> reports hundreds of FS3261 warnings. How do you roll the checks out without stopping feature work, and which warnings do you fix first?

    What it tests
    Planning an incremental rollout: finding where nulls enter, converting them to options at those boundaries, keeping string | null out of the domain, and stopping new warnings with warnings as errors.
    First step
    Group the warnings by where the value comes from. Most trace back to a few boundaries: .NET calls that may return null, deserialised data and configuration. Convert those values to options where they enter, which clears the warnings downstream, and only then turn FS3261 into an error, project by project, so new code cannot add more.
    Follow-ups

    From the lesson Options, value options and nullness

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  12. Mid to senior

    A monitoring report counts distinct sensor readings twice, once with List.distinct and once with Set.ofList. Most days the two counts match, but on days when sensors fail, the List.distinct count is higher. Explain the cause and fix the pipeline.

    What it tests
    Knowing that = treats nan as unequal to itself while compare treats two nans as equal, recognising NaN as the value a failed reading produced, and handling it once where the data comes in.
    First step
    Look at what a failed sensor sends. If its reading is NaN, List.distinct, which uses =, keeps every NaN, while Set.ofList, which uses compare, keeps one. Confirm it by counting Double.IsNaN over that day's readings, then decide at the import what a NaN means: dropped, counted as a failure, or kept as one marker.
    Follow-ups

    From the lesson Structural equality, comparison and hashing

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  13. Mid to senior

    A service keeps a disk cache keyed by hash productName so that it can skip work after a restart. In tests every lookup hits the cache, but in production almost every lookup misses after each deployment. Explain why, and design a key that survives restarts.

    What it tests
    Knowing that .NET randomises string hashes per process, that hash codes are not unique, and choosing a stable key such as the text itself or a cryptographic digest.
    First step
    Compare the hash of one product name in two runs: .NET randomises string hashes per process, so after a restart the same name hashes differently and every stored key is stale. The tests wrote and read the cache in one process, which hid it. Key the cache by the name itself, or by a stable digest such as SHA-256 of its UTF-8 bytes.
    Follow-ups

    From the lesson Structural equality, comparison and hashing

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  14. Mid to senior

    A routing service models distances as float<km>. A partner starts sending miles in the same JSON field. Nothing fails, but fuel estimates are now about 38 % too low. Why did units of measure not catch it, and how would you design the boundary so that it cannot happen silently?

    What it tests
    Knowing that units are erased at run time and that serializers see plain numbers, and designing a contract that carries its units, with checks where the values enter.
    First step
    Units exist only while compiling: the record's float<km> is a Double at run time, and the deserializer put the partner's number into it unchecked, as erased.fsx read 56 into Distance. Put the unit into the contract, as a field name such as distanceKm or a unit field, check plausible ranges on arrival, and convert to float<km> in one boundary function.
    Follow-ups

    From the lesson Units of measure

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  15. Senior to staff

    You are designing the money types of a payments module that charges in rupees and shows prices in three foreign currencies. Where do units of measure help, where do they not, and which types do you choose?

    What it tests
    Using int64<paise> for exact amounts, units that keep money apart from counts and currencies apart from each other, and naming what units do not check: rounding, rates and formats.
    First step
    Start from what must be exact: rupee amounts in whole paise, int64<paise>, since one rupee is 100 paise. Give every currency its own unit so that rupees never add to dollars, keep exchange rates as dated data with a unit such as INR/USD, and fix one rounding rule that is applied once, at the conversion.
    Follow-ups

    From the lesson Units of measure

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes

All F# interview questions · The F# track

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.