C++ Module 2
Types, values and expressions interview questions
Interview questions on types, values and expressions: 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 to mid
Why do C++ programmers use unsigned types for bit manipulation?
Answered in the lesson C++ bitwise operators and the <bit> header
-
Fresher to mid
What is the difference between const and constexpr?
Answered in the lesson C++ const, constexpr, consteval and constinit
-
Fresher to mid
What does auto deduce for auto x = r; when r is a const int&?
-
Fresher to mid
When should a function take std::string_view instead of const std::string&?
-
Fresher to mid
Why does std::string::size() not return the number of characters in a Hindi word?
Answered in the lesson UTF-8, Unicode and text encodings in C++
-
Fresher
What is the difference between enum and enum class?
Answered in the lesson C++ enums: enum class and std::to_underlying
-
Fresher
What is the difference between int x(2.9); and int x{2.9};?
Answered in the lesson Variables and initialisation: =, () and {}
-
Fresher
Is an int always 32 bits in C++?
Answered in the lesson C++ fundamental types: integers, floats, char, bool
-
Fresher
What happens when an unsigned int overflows, and when an int does?
Answered in the lesson Integer overflow and signed/unsigned conversions
-
Fresher
Why is 0.1 + 0.2 == 0.3 false in C++?
Answered in the lesson Floating-point precision and comparison in C++
-
Fresher
What are -7 / 2 and -7 % 2 in C++, and why?
Answered in the lesson C++ operators, precedence and integer division
-
Fresher
What is the difference between undefined, unspecified and implementation-defined behaviour?
Answered in the lesson Why i++ + ++i has no single answer in C++
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
Feature flags live in a std::uint64_t and are set with flags |= 1 << id. Flags 0 to 31 work, but ids from 32 upwards misbehave, and differently in debug and release builds. Explain what happens and fix it.
- What it tests
- Integral promotion, the shift-count rule of the standard, why undefined behaviour differs between builds, and validating a bit number that comes from outside.
- First step
- Look at the type of 1: it is an int, so 1 << id is computed in 32 bits and only then widened for the |=. For an id of 32 or more the count is at least the width of the promoted left operand, which is undefined behaviour, not a value that wraps or becomes zero.
- Follow-ups
-
- Why can a debug build and a release build behave differently?
- How would you catch this in tests?
- What changes if the id comes from a configuration file?
From the lesson C++ bitwise operators and the <bit> header
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 3 minutes
-
Mid to senior
Each delivery route visits some of 50 cities, and you keep ten million routes in memory. You must answer, many times a second, how many cities two routes have in common. Choose the representation and the operations, and estimate the memory.
- What it tests
- Representing small sets as bit masks, counting with std::popcount, memory estimates, and knowing when a mask stops being the right representation.
- First step
- Fifty cities fit in one std::uint64_t, with bit i for city i. The common cities of two routes are then a & b, and their number is std::popcount(a & b), a few instructions per pair. Ten million routes take 80 MB as masks, against several times that as vectors of city numbers.
- Follow-ups
-
- What changes at 500 cities?
- How do you list the common cities in order?
- How do you check that std::popcount became a hardware instruction?
From the lesson C++ bitwise operators and the <bit> header
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A function returns n! as a std::int64_t. Its run-time tests compare n up to 25 with hard-coded values; they pass with one compiler and fail with another. What is wrong, and how could constexpr and static_assert have caught it?
- What it tests
- Recognising signed overflow as undefined behaviour, why compilers may then differ, and using constant evaluation as a check that refuses undefined behaviour.
- First step
- Find the largest n whose factorial fits: 20! is about 2.4 × 10¹⁸ and 21! about 5.1 × 10¹⁹, above the int64 maximum of about 9.2 × 10¹⁸. Every test above 20 depends on undefined behaviour, so any result, and a different one per compiler, is possible.
- Follow-ups
-
- Why does a constant expression refuse the same code?
- How do you make the function safe for every n?
- What does -fsanitize=undefined add to the run-time tests?
From the lesson C++ const, constexpr, consteval and constinit
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A global logger defined in one source file is used while a global cache in another file is initialised, and the program sometimes crashes before main starts. Explain the failure, and how constinit and other techniques prevent it.
- What it tests
- The static initialisation order problem, constant against dynamic initialisation, and what constinit does and does not guarantee.
- First step
- Both globals are initialised by code that runs at start-up, and between source files the standard leaves the order of that code open. If the cache runs first, it uses a logger whose constructor has not run yet. A constant- initialised object has no such order, because the compiler sets it up before any code runs.
- Follow-ups
-
- Which kinds of object can be constinit?
- What does a function-local static offer instead, and at what cost?
- Does constinit make the logger safe to use from several threads?
From the lesson C++ const, constexpr, consteval and constinit
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
The loop for (auto order : orders) order.status = Status::shipped; compiles, but no order is ever marked as shipped, and a profile shows the loop spending most of its time copying. Explain both symptoms and the fix.
- What it tests
- How auto deduces in a range-based for loop, values against references, the cost of copies, and which warnings do and do not catch it.
- First step
- auto deduces Order, so each order is a copy of an element: the assignment changes the copy, which is destroyed at the end of the iteration, and every iteration copies a whole Order. Writing auto& order makes order refer to each element, which fixes both symptoms.
- Follow-ups
-
- What happens if orders is a const std::vector<Order>?
- When is for (auto x : v) the right choice?
- Which compiler warning hints at the bug?
From the lesson C++ auto, decltype and type aliases
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A function returns std::vector<bool>, and a caller writes auto ok = check_all()[0]; and uses ok a few lines later. It seems to work in tests, but AddressSanitizer reports a heap-use-after-free. Explain why, and how to make such code safe.
- What it tests
- Proxy types, the lifetime of temporaries, what auto deduces from std::vector<bool>, and reading an AddressSanitizer report.
- First step
- check_all() returns a temporary vector, and std::vector<bool>::operator[] returns a proxy that points into the vector's storage. auto deduces the proxy, not bool, and the temporary is destroyed at the end of the statement, so the later read uses freed memory.
- Follow-ups
-
- Why did it seem to work in tests?
- How does writing bool instead of auto fix it?
- Which other types hand out proxies or views that auto can keep alive too long?
From the lesson C++ auto, decltype and type aliases
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A class stores a std::string_view member set from a constructor argument. Objects made from string literals work, but objects made from lines read in a loop print garbage later. Explain what happens and how you would fix the class.
- What it tests
- The lifetime of the text behind a view, owning against non-owning members, and why the bug depends on the input.
- First step
- A view owns nothing. A string literal lives for the whole program, but a line read in a loop is overwritten on the next iteration or destroyed when the loop ends, so every stored view points at characters that changed or were freed. A member that outlives the constructor call must own its text: store a std::string.
- Follow-ups
-
- When is a std::string_view member acceptable?
- Why do short lines sometimes still look right?
- Which tools would have found the bug?
From the lesson std::string and std::string_view
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A log parser splits each 300-byte line into 12 fields stored as std::string, at 50,000 lines a second, and a profile shows most of the time in malloc. How would you reduce the cost, and what new risk do you take on?
- What it tests
- Estimating allocations, the small-string buffer of each standard library, std::string_view for parsing, and the lifetime risk that views add.
- First step
- Count first: each field longer than the inline buffer (15 characters with libstdc++, 22 with libc++) costs an allocation, so up to 12 per line, 600,000 a second, plus the vector. Splitting into std::string_view pieces of the line allocates nothing for the fields, as long as the line outlives the views.
- Follow-ups
-
- How do you avoid allocating the vector of fields for every line?
- What if some fields must be kept after the line is gone?
- How do you show that the change helped?
From the lesson std::string and std::string_view
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A sign-up form allows names of up to 20 characters, and the C++ server checks name.size() <= 20 and truncates longer names. Users with Hindi and Tamil names say that short names are rejected and that stored names end in a broken letter. Explain both and design the fix.
- What it tests
- Bytes against code points against grapheme clusters, cutting text at a safe boundary, and choosing what a limit should count.
- First step
- size() counts UTF-8 bytes, and a Devanagari or Tamil letter takes 3, so a name of 7 letters is already over 20. The broken letters come from cutting after a byte count, which can split a code point or a grapheme cluster. Decide whether the limit protects storage (bytes) or describes what people see (grapheme clusters).
- Follow-ups
-
- How do you truncate a name safely?
- Where should the limit be checked, and in which unit?
- Why compare names only after normalising them?
From the lesson UTF-8, Unicode and text encodings in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
A C++ service receives text that should be UTF-8 from many clients and passes it to a JSON writer and a database. Why must it validate the text, what exactly should it reject, and what should it do with invalid input?
- What it tests
- The definition of well-formed UTF-8, the security history of lenient decoders, and a policy for invalid data at a system boundary.
- First step
- Validate once, where the text enters. Well-formed UTF-8 means complete sequences in their shortest form, no surrogates and nothing above U+10FFFF. RFC 3629 describes attacks in which overlong forms slipped past checks that looked only for the short ones.
- Follow-ups
-
- Should the service reject the request or replace bad bytes with U+FFFD?
- How do you test the validator?
- Does valid UTF-8 make the text safe to show in a web page?
From the lesson UTF-8, Unicode and text encodings in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An order system has enum class Status { placed, paid, shipped }, used in 40 switch statements across the code base. You must add a refunded status without missing any place that needs it. How do you make the compiler find every one?
- What it tests
- -Wswitch and -Wswitch-enum, the cost of default cases, warnings as errors, and testing code that handles states.
- First step
- Let the compiler search: build with -Wall -Werror=switch, so that every switch without a default that misses refunded stops the build. Then deal with the switches that have a default, which hide the new value, by turning on -Wswitch-enum for the change or searching for them.
- Follow-ups
-
- Why is a default case risky in these switches?
- What about statuses stored as integers in a database?
- How would tests cover the new state?
From the lesson C++ enums: enum class and std::to_underlying
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A network message carries a one-byte message type, which your code converts with static_cast to enum class MessageType : std::uint8_t. What can go wrong with untrusted input, and how should the conversion be written?
- What it tests
- Which values an enumeration with a fixed underlying type can hold, validation at the boundary, and handling of unknown values.
- First step
- static_cast accepts every std::uint8_t, so a hostile or newer client can produce a MessageType that names no enumerator, and switches or tables indexed by it misbehave. Convert through a function that checks the value against the known enumerators and returns std::optional<MessageType>.
- Follow-ups
-
- Is the cast itself undefined behaviour here?
- How do you stay compatible with clients that send newer message types?
- What would change with a plain enum without a fixed underlying type?
From the lesson C++ enums: enum class and std::to_underlying
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A service fills struct Limits { int max_connections; int timeout_ms; }; from a configuration parser that skips missing keys. The struct is declared as Limits limits; inside a function. Tests pass, but in production timeout_ms is sometimes a huge random number. What is happening, and how do you make it impossible?
- What it tests
- Default-initialisation of aggregates, why the bug hides in tests, which tools find it on which platform, and a design that leaves no member without a value.
- First step
- Limits limits; default-initialises the struct, and for int members that means no initialisation at all: a field the parser never assigns keeps whatever bytes were on the stack, and reading it is undefined behaviour. List the keys that can be missing, then give every member a value where it is declared.
- Follow-ups
-
- Why do the tests pass while production fails?
- Which tools would have caught it, on Linux and on macOS?
- What changes when the code is built as C++26?
From the lesson Variables and initialisation: =, () and {}
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A code review changes std::vector<int> slots(count, 0); to std::vector<int> slots{count, 0}; "to use uniform initialisation everywhere". count is a std::size_t. What does the change do with Clang and with GCC, and what rule would you write into the team's style guide?
- What it tests
- How braces prefer an initializer_list constructor, narrowing inside braces, the different reactions of Clang and GCC, and turning that into a rule a reviewer can apply.
- First step
- Read the braces the way the compiler does: std::vector has a constructor taking std::initializer_list<int>, so {count, 0} asks for a vector holding the two values count and 0, not count zeros. Then notice that count is a std::size_t, and converting it to int inside braces is a narrowing conversion.
- Follow-ups
-
- What happens if count is an int instead?
- How would you catch this kind of change before it ships?
- When are braces still the right choice for a container?
From the lesson Variables and initialisation: =, () and {}
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A tool writes struct Header { long timestamp; char kind; unsigned size; }; to disk with one fwrite on 64-bit Linux, and the Windows build of the same tool reads garbage from those files. List every reason, and design a header layout that any compiler reads the same way.
- What it tests
- Data models (LP64 and LLP64), padding and alignment, plain char and byte order, and serialising fields explicitly instead of copying memory.
- First step
- Compare the layouts before guessing. long is 8 bytes on 64-bit Linux and 4 on 64-bit Windows, so every later field moves and the padding moves with it: sizeof(Header) is 16 on Linux and 12 on Windows, and kind sits at offset 8 on one and 4 on the other. That alone explains the garbage.
- Follow-ups
-
- Why is copying a struct to a file fragile even on one platform?
- Does byte order matter between these two machines?
- How would you let the format change later without breaking old files?
From the lesson C++ fundamental types: integers, floats, char, bool
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A timing helper returns the milliseconds since start-up as a long. It works on Linux servers, but on Windows machines that stay on for a few weeks, timeouts suddenly fire at once or never fire. When exactly does it break, and how do you fix the type?
- What it tests
- Knowing that long is 32 bits on Windows, working out when a millisecond counter overflows, and choosing std::chrono or a 64-bit type instead.
- First step
- Work out the limit: a 32-bit signed long holds at most 2,147,483,647, and that many milliseconds is about 24.86 days (2,147,483,647 ÷ 86,400,000). Counting past it overflows a signed type, which is undefined behaviour; in practice the value wraps to a large negative number, and every comparison with a deadline flips.
- Follow-ups
-
- What changes if the counter is an unsigned long instead?
- How would you express the deadline with std::chrono?
- How would you test the fix without waiting 25 days?
From the lesson C++ fundamental types: integers, floats, char, bool
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
This loop prints a vector backwards: for (std::size_t i = v.size() - 1; i >= 0; --i) std::cout << v[i]; It prints the elements and then garbage until the program crashes, and with an empty vector it crashes at once. Explain both, with the numbers involved, and give two correct ways to write it.
- What it tests
- Unsigned wrap-around, size() - 1 on an empty container, a condition that is always true, which warnings flag it, and idiomatic reverse iteration.
- First step
- Two unsigned facts explain everything. i >= 0 is always true for an unsigned i, so the loop has no exit; after i reaches 0, --i wraps to 18,446,744,073,709,551,615. And for an empty vector, v.size() - 1 is that same value from the start.
- Follow-ups
-
- Which compiler options would have reported the bug?
- Is changing i to int a good fix?
- How do you write the loop with C++20 ranges?
From the lesson Integer overflow and signed/unsigned conversions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A binary search over a sorted array of 1.5 billion entries computes int mid = (lo + hi) / 2; and crashes, but only on the largest inputs. Why, what would UBSan report, and what are the correct ways to compute the midpoint?
- What it tests
- Spotting signed overflow in index arithmetic, reading a sanitizer report, and choosing between lo + (hi - lo) / 2, std::midpoint and wider index types.
- First step
- Put numbers in: with lo = 1,000,000,000 and hi = 1,500,000,000, lo + hi is 2,500,000,000, more than the largest int, 2,147,483,647. The addition overflows before the division, which is undefined behaviour; the wrapped sum halves to a negative index, and reading there crashes.
- Follow-ups
-
- Why does lo + (hi - lo) / 2 not overflow?
- What does std::midpoint return for an odd distance?
- Would std::size_t indices remove the problem?
From the lesson Integer overflow and signed/unsigned conversions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A simulation steps its clock with for (double t = 0.0; t != 1.0; t += 0.1), and the loop never stops. Explain why, and write a loop that runs exactly eleven times, for t = 0.0, 0.1 … 1.0, with each t as accurate as possible.
- What it tests
- Accumulated rounding error, why != on computed doubles is fragile, integer loop counters, and the difference between step * 0.1 and step / 10.0.
- First step
- Print t with 17 significant digits after ten additions: it is 0.99999999999999989, not 1.0, so t != 1.0 stays true and t keeps growing past 1. Drive the loop with an integer that counts steps, and compute t from it on every iteration.
- Follow-ups
-
- Why is step / 10.0 better than step * 0.1?
- Would t < 1.0 fix the original loop?
- How do you choose a tolerance for a stopping test that really needs one?
From the lesson Floating-point precision and comparison in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Sensor readings arrive as doubles, and a failed reading is stored as NaN. After std::sort, the vector is not even sorted, and the result differs between a Linux build and a Mac build. Explain why, and sort the readings with the failed ones at the end.
- What it tests
- Strict weak ordering and why NaN breaks it, why the outcome differs between standard libraries, and correct alternatives such as partitioning or a comparator that orders NaN.
- First step
- Check the comparator against the rules std::sort relies on: < is not a strict weak ordering once NaN is present, because NaN is "equal" to every value, yet those values are not equal to each other. Breaking that requirement is undefined behaviour, so each library's algorithm goes wrong in its own way.
- Follow-ups
-
- Why do libc++ and libstdc++ produce different orders?
- What does std::partial_ordering have to do with it?
- Which library function orders NaN without a custom comparator?
From the lesson Floating-point precision and comparison in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Permissions are bits: READ is 1, WRITE is 2, EXEC is 4. A check reads if (flags & WRITE == WRITE) allow_write();. Which users does it let write, which does it refuse, and how would you write and protect the check?
- What it tests
- Precedence of == over &, working the wrong expression through with numbers, compiler warnings as a gate, and safer ways to express flag tests.
- First step
- Group the expression the way the compiler does: == binds more tightly than &, so it is flags & (WRITE == WRITE), which is flags & 1. The check therefore tests the READ bit. Then try a read-only user, flags = 1, and a write-only user, flags = 2.
- Follow-ups
-
- Why does the bug survive a quick manual test?
- How do you test a mask of several bits?
- What would an enum class bring here?
From the lesson C++ operators, precedence and integer division
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A ring buffer of size slots moves its read position backwards with pos = (pos - 1) % size;. With pos an int, it reads outside the buffer when pos is 0. With pos a std::size_t, it works when size is 16 but not when size is 10. Explain both, and step backwards correctly.
- What it tests
- The sign of % with negative operands, unsigned wrap-around combined with %, why powers of two hide the bug, and a correct backward step.
- First step
- Evaluate each case at pos = 0. As an int, 0 - 1 is -1, and -1 % size is -1, an index before the buffer. As a std::size_t, 0 - 1 wraps to 2⁶⁴ − 1, and the remainder of that by size is correct only when size divides 2⁶⁴.
- Follow-ups
-
- What does (2⁶⁴ − 1) % 10 give, and why is 16 lucky?
- How do you step backwards without any remainder at all?
- When is a power-of-two size with a mask the better design?
From the lesson C++ operators, precedence and integer division
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Old code calls submit(std::unique_ptr<Job>(new Job(config)), next_priority()); and next_priority() can throw. Could this leak memory before C++17? Can it still leak today? What would you write instead, and why?
- What it tests
- How C++17 changed the sequencing of function arguments, exception safety across argument evaluation, and why std::make_unique is preferred anyway.
- First step
- List the steps the call involves: allocate and construct the Job, construct the unique_ptr, call next_priority. Before C++17 the steps of different arguments could interleave, so the allocation could happen, then the throwing call, before any unique_ptr owned the Job.
- Follow-ups
-
- What exactly did C++17 change in the rules for arguments?
- Is the same call safe with std::shared_ptr<Job>(new Job(config))?
- When is a plain new still the right tool?
From the lesson Why i++ + ++i has no single answer in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Since C++17, std::cout << f() << g() calls f before g. Does the same hold for your own Logger with an overloaded operator<<, for a + b where both operands are calls, and for target() = make() with a user-defined operator=? What about target().operator=(make())?
- What it tests
- The rule that overloaded operators keep the sequencing of the built-in ones, which built-in operators have an order, and how explicit call syntax changes it.
- First step
- Recall the rule from [over.match.oper]: written with operator notation, an overloaded operator's operands are sequenced as the built-in operator prescribes. So answer each case by asking what the built-in <<, + and = do, and notice that the last example is not operator notation at all.
- Follow-ups
-
- Why do the two assignment spellings evaluate in different orders?
- Is a + b with two calls undefined or unspecified?
- What does this mean for fluent interfaces and builders?
From the lesson Why i++ + ++i has no single answer in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes