Julia Module 6
The type system and multiple dispatch interview questions
Interview questions on the type system and multiple dispatch: 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
How does an abstract type differ from a concrete type in Julia?
Answered in the lesson The type system: abstract and concrete types
-
Fresher to mid
When would you choose a mutable struct over a struct in Julia?
Answered in the lesson Structs, constructors and redefining types (1.12+)
-
Fresher to mid
How is multiple dispatch different from method overloading in Java or C++?
-
Fresher to mid
What does the T in struct Point{T} do?
Answered in the lesson Parametric types and generic numeric code
-
Fresher to mid
When does Julia call convert(T, x) by itself, and how does T(x) differ?
Answered in the lesson Conversion, promotion and custom number types
-
Fresher to mid
What must a type define to work in a for loop?
Answered in the lesson Interfaces: iteration and custom arrays
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to senior
A library function is declared fit(x::Vector{Float64}, y::Vector{Float64}). Users report MethodErrors when they pass ranges, views, Float32 data and vectors of integers. How do you widen the signature, how far do you widen it, and what does the change cost?
- What it tests
- Knowing invariance and the AbstractVector family, choosing between AbstractVector{<:Real}, a type parameter and no annotation, and knowing that specialisation keeps the Float64 path as fast as before.
- First step
- List the types users actually pass: UnitRange{Int64}, SubArray, Vector{Float32}, Vector{Int64}. None is a Vector{Float64}, and invariance means none ever will be. Then write down what fit needs from its inputs, indexing and real arithmetic, which is what AbstractVector{<:Real} says.
- Follow-ups
-
- Does the wider signature make the Float64 case slower?
- Should x and y be allowed different element types?
- Where should a conversion happen, inside fit or at the caller?
From the lesson The type system: abstract and concrete types
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
-
Mid to staff
A station lookup returns nothing for unknown stations. A nightly batch job fails after three hours with MethodError: no method matching +(::Nothing, ::Float64) inside a summing loop. How do you find the cause, and how do you redesign the lookup and its callers so the failure comes early or never?
- What it tests
- Reading a MethodError that involves Nothing, tracing a Union{Nothing, T} result to its source, and choosing between nothing, missing, an error and a default.
- First step
- The nothing was made by the lookup, not by the loop: find where values enter the sum and check what each source can return, for example with Base.return_types, which shows Union{Nothing, Float64}. Then decide what an unknown station means for this job: an error to stop on, a gap to skip and report, or a value with a default.
- Follow-ups
-
- When should the lookup throw instead of returning nothing?
- What changes when the data has gaps rather than unknown keys?
- How do you make the job fail in its first minute instead of its third hour?
From the lesson The type system: abstract and concrete types
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
Sensor readings are stored in a struct with the fields station, hour, value and unit. Analysts keep finding NaN values, hours above 24 and unknown units in the results. Redesign the type so that such readings cannot exist, and explain what changes for the code that builds them.
- What it tests
- Using an inner constructor to enforce rules, knowing that it removes the default constructors, writing errors that name the field, and handling NaN in comparisons.
- First step
- Write each rule as a check on one field: a station that is not empty, an hour in [0, 24), a value that is not NaN, a unit from a fixed list. Put all of them in one inner constructor that ends in new, so every way of building a reading passes through them.
- Follow-ups
-
- Why does lo > hi && throw(…) let NaN through?
- Where do the convenient constructors go?
- How do you load a file in which 2 % of the rows are invalid?
From the lesson Structs, constructors and redefining types (1.12+)
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
During a long Julia 1.12 session you add a unit field to a struct. Afterwards a function that worked before throws a MethodError for new values, and values in a cache print as @world(Sample, …). Explain what happened, and how you would work with struct redefinition safely.
- What it tests
- Knowing that redefinition creates a new type and moves the name, that values and methods stay with the old type, and a way of working that avoids stale state.
- First step
- Compare typeof of an old and a new value: they differ, because the second definition made a new type and moved the name Sample to it. The methods compiled earlier name the old type, which explains the MethodError for new values.
- Follow-ups
-
- Why does running an identical definition again cause no trouble?
- What happens to the same file on the 1.10 LTS?
- How do you keep a cache usable across such changes?
From the lesson Structs, constructors and redefining types (1.12+)
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
Two packages add methods to a shared function combine(a, b): one for (MyMatrix, AbstractMatrix), the other for (AbstractMatrix, TheirMatrix). Users who load both get "ambiguous" MethodErrors for some calls. How do you find every ambiguity, fix the one you have, and keep new ones from appearing?
- What it tests
- Seeing why two signatures overlap, deciding which package defines the method for the overlap, and preventing new ambiguities with Test.detect_ambiguities, Aqua.jl and design.
- First step
- Write down the overlap: combine(::MyMatrix, ::TheirMatrix) fits both methods and neither is more specific, so every call with that pair of types is ambiguous. Then run Test.detect_ambiguities with both packages loaded to list every such pair, not only the one users reported.
- Follow-ups
-
- Which package should define the method for the overlap?
- Why did each package's own tests pass?
- How can a package's design make such clashes rare?
From the lesson Methods and multiple dispatch
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A colleague adds Base.:+(v::Vector{Float64}, x::Float64) = v .+ x to a project's utilities so that v + 1.0 works. What is wrong with it, what can it break, and what would you do instead?
- What it tests
- Recognising type piracy, knowing what it does to other code in the same session, and the alternatives: broadcasting or a function of your own.
- First step
- Check who owns what: + belongs to Base, and Vector{Float64} and Float64 are Base types, so the method is type piracy and changes + for every package loaded in the session. Then ask what the colleague wanted, element-wise addition, which Julia already writes as v .+ 1.0.
- Follow-ups
-
- What can it break, concretely?
- Why does Julia not define vector plus scalar itself?
- When is a method of a Base function for Base types acceptable?
From the lesson Methods and multiple dispatch
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
A simulation keeps its state in struct Sim with the fields state::AbstractVector, dt::Real and params::Dict{Symbol, Any}. Profiling shows the time-step loop allocating on every iteration. Explain why, and redesign the struct without losing the ability to run on Float32 data or on GPU arrays.
- What it tests
- Recognising abstract and Any-valued fields as the cause, replacing them with type parameters or a named tuple, and checking the fix with @allocated or @code_warntype.
- First step
- Ask what the compiler can learn from typeof(sim): Sim is one type whatever it holds, so state, dt and every params lookup have unknown types inside the loop. Measure one step with @allocated, then make each field's type a parameter or a concrete type.
- Follow-ups
-
- Why not simply declare state::Vector{Float64}?
- What should replace the Dict of parameters?
- How do you keep the fix from being undone later?
From the lesson Parametric types and generic numeric code
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to senior
A numerical routine written with s = 0.0, 1/n and a tolerance of 1e-12 returns Float64 for Float32 inputs, loses exactness for Rational inputs, and never stops for one user's Float32 data. How do you make it generic, and how do you test that it stays generic?
- What it tests
- Spotting Float64 literals and tolerances that leak into generic code, rewriting them with zero, one, oftype and eps, and testing result types with typeof and @inferred.
- First step
- Find every constant that fixes a type: 0.0, 1/n and 1e-12. The first two turn the computation into Float64, and the third is a tolerance below the spacing of Float32 numbers. Replace them with zero(T), one(T)/n and a tolerance built from eps(T), where T comes from the input.
- Follow-ups
-
- How do you write 1/n so that it stays exact for Rational input?
- What should the default tolerance be?
- Which tests catch the problem coming back?
From the lesson Parametric types and generic numeric code
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
A Money type stores paise in an Int64 and defines + for two Money values. Colleagues want 2 * m, m + 100 and sum(Money[]) to work, and propose promote_rule and convert so that Money mixes with every number. Which of these should work, how do you implement them, and where is promotion the wrong tool?
- What it tests
- Choosing between promotion and explicit methods, spotting conversions that would change meaning, and giving a type a zero for empty sums.
- First step
- Ask what each operation means. 2 * m scales an amount and makes sense; m + 100 does not, because 100 has no unit: paise or rupees? Promotion would make every number silently convertible to money, so write explicit methods for scaling and a zero for the empty sum instead.
- Follow-ups
-
- What does sum(Money[]) need, and why?
- Should Money be a subtype of Real or of Number?
- How should an amount print?
From the lesson Conversion, promotion and custom number types
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
An Interval type is a subtype of Real. Tests show that sort works, but x <= y is false for some intervals that are equal, and a Dict with Interval keys holds what look like duplicates. What is wrong, and how do you define the comparisons of a type like this?
- What it tests
- Knowing how Base builds <=, isequal and hash from a few methods, keeping them consistent, and deciding what order and equality mean for an interval.
- First step
- List what the type defines: probably < on one bound and the default ==, which compares every field. Base's <= combines == and <, and a Dict uses isequal and hash, so the three disagree. Decide what equality and order mean for an interval, then derive ==, isless, < and hash from that one decision.
- Follow-ups
-
- Is there a total order on intervals at all?
- Why does the Dict hold apparent duplicates?
- Which tests would have caught this?
From the lesson Conversion, promotion and custom number types
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A colleague's iterator over the records of a large file works in for loops, but collect(records) returns a Vector{Any}, and every analysis of the collected records is slow and allocates. What is missing, how do you fix it, and how do you check the fix?
- What it tests
- Knowing the IteratorSize and eltype traits and their defaults, choosing between HasLength, SizeUnknown and EltypeUnknown, and checking the result with typeof and @inferred.
- First step
- Ask what collect was told. The default eltype is Any, so collect built a Vector{Any} even though every record has the same type. Declare Base.eltype on the iterator's type, and IteratorSize as SizeUnknown when the number of records is not known before the file is read.
- Follow-ups
-
- What if records can have different types?
- Why does a comprehension over the same iterator give a concrete type?
- Which tests keep the problem from coming back?
From the lesson Interfaces: iteration and custom arrays
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to staff
You wrap a matrix whose entries come from a formula as an AbstractMatrix with size and getindex. A * x is many times slower than Matrix(A) * x, and A \ b uses as much memory as the dense matrix. Explain both, and decide when to keep the lazy type and when to build the dense matrix.
- What it tests
- Knowing that generic fallbacks read entries one at a time, that factorizations copy into dense storage, and choosing between laziness, a dense copy and specialised methods.
- First step
- Separate the two effects. The product runs Base's generic fallback, which calls getindex for every entry and recomputes the formula each time; the solver factorizes a dense copy, so it needs that memory whatever the type. Then count how often the matrix is used.
- Follow-ups
-
- How do you make A * x fast without storing A?
- When is Matrix(A) the right choice?
- What does IndexStyle change here?
From the lesson Interfaces: iteration and custom arrays
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes