cswork / libraryOpen practice workspace

Articles

Duolingo Code Review Interview: Reviewing Changes, Finding Bugs, and Explaining Trade-Offs

Prepare for the Duolingo Code Review interview with a tested review workflow for finding bugs, prioritizing feedback, and explaining engineering trade-offs.

Duolingo has publicly described a Code Review interview in which candidates identify problems and suggest improvements to an existing change. The company also says that every role has its own interview plan. That combination matters: the round is real, but a table from a dated public guide is not a promise that your 2026 or 2027 invitation will use the same language, tool, duration, or task.

The best preparation is to practice an evidence loop: state the intended behavior, trace the changed path, construct the smallest counterexample, rank the impact, and recommend a focused fix with a regression test. This guide gives you that workflow with an original learning-session example. It is not a leaked Duolingo question or Duolingo production code.

If you want another realistic review after completing the exercise, try How do you perform a thorough code review?. Treat it as related practice, not evidence about your exact Duolingo prompt.

What Duolingo Has Published About the Code Review Round

Official fact: Duolingo's engineering interview guide, published August 20, 2024, says the Code Review session asks candidates to identify issues and suggest improvements to a set of code changes. It lists specialized Backend, iOS, and Android versions. The same guide separates Code Review from Pair Programming: the former centers on reviewing a change, while the latter asks the candidate to write code in an existing codebase.

The guide's table lists CodeSignal, Python or Java for the general version, Swift for iOS, Kotlin for Android, and 60 minutes. Read those as dated public guidance. The page itself says each role has a unique plan and that the recruiter supplies specifics. Your invitation and recruiter are therefore the current authority.

Candidate report: a Glassdoor report published September 3, 2026 describes a May 2026 Senior Software Engineer process in New York that included a code-review round. Two late-2025 New Grad posts also mention Backend Code Review. These reports support that candidates have recently encountered the label; they do not establish one format across levels, offices, or teams. We found only one completed same-cycle 2026 report with enough context to cite, so this article deliberately avoids claiming a stable 2027 sequence.

Use this confirmation table before choosing your practice environment:

Ask your recruiterWhy the answer changes preparationWhat you can prepare now
Is this Code Review, Pair Programming, or another technical session?Reviewing and implementing require different pacing.Practice explaining an unfamiliar change before editing it.
Which language or platform applies to my role?Backend, iOS, and Android reviews may expose different failure modes.Review one small change in the language you will read fastest.
What editor or collaboration tool will we use?Navigation and test execution vary by environment.Be able to search, run tests, and inspect a diff without IDE-only shortcuts.
Am I expected to edit code or only discuss it?A verbal review and a repair exercise produce different artifacts.Prepare both a concise comment and a minimal patch.
Which libraries, documentation, and AI tools are allowed?Tool rules are session-specific.Rehearse once with only the stated resources.

Do not infer the answer from another candidate's role. Send one short message listing the unresolved items. That reduces avoidable uncertainty without asking the recruiter to reveal content.

Start With the Contract, Not With Style

A diff is a compressed view of a behavior change. Before reading line by line, reconstruct four things:

  1. Input contract: Which values are valid, and can requests repeat?
  2. State transition: What should change, and what must remain unchanged?
  3. Failure behavior: Should invalid input raise, return an error, or be ignored?
  4. Invariant: What must remain true across every path?

For a learning-session event, a plausible contract is: each event has a unique ID, learner ID, and integer score from 0 through 100; replaying an event must not count another lesson; the best score must never decrease. This contract is part of our practice scenario. It is not an official Duolingo specification.

Now review this intentionally defective change:

def record_session_buggy(state, event):
    learner = state.setdefault(
        event["learner_id"],
        {"lessons": 0, "best": None, "events": set()},
    )
    learner["lessons"] += 1
    if event["score"]:
        learner["best"] = event["score"]
    if event["event_id"] in learner["events"]:
        return state
    learner["events"].add(event["event_id"])
    return state

Resist naming every concern immediately. Trace one normal event, one duplicate, a score of zero, a lower score after a higher score, and invalid input. Write the observed result next to the expected result. That turns intuition into evidence.

Find the Three Blocking Bugs

The first blocking issue is mutation before duplicate detection. If event e1 is already present, the function increments lessons before returning. The smallest counterexample starts with one lesson and replays e1; the count becomes two. A useful review comment names the impact and proof:

Blocking: a retried event increments lessons before the duplicate check. Starting with events={"e1"} and lessons=1, replaying e1 returns lessons=2. Could we move the idempotency check before every state update and add a replay test?

The second issue is the truthiness check. Zero is a valid score under our practice contract, but if event["score"] skips it. Avoid saying only "this condition is wrong." State the boundary value, the observed path, and the desired behavior.

The third issue is best-score regression. A learner whose best is 80 can submit 60 and have best replaced by 60. The update needs max(previous, current), with a separate path when no previous score exists.

There is also a design risk: the function mutates the caller's state before fully validating the event. If an exception occurs later, the caller may hold a partial update. Whether immutability is required depends on the real codebase, so frame it as a question unless the surrounding contract makes it blocking.

This ordering shows judgment. Correctness and data integrity come before naming, formatting, or a speculative refactor. Google's public code-review guidance similarly distinguishes required improvements from optional polish and recommends marking minor comments as non-blocking. That guidance is general engineering practice, not Duolingo's private scoring rubric.

Turn Each Finding Into a Regression Test

The minimum repair validates first, copies state, checks idempotency, then applies updates:

def record_session(state, event):
    required = {"event_id", "learner_id", "score"}
    if set(event) != required or not isinstance(event["score"], int) \
            or not 0 <= event["score"] <= 100:
        raise ValueError("invalid event")

    result = deepcopy(state)
    learner = result.setdefault(
        event["learner_id"],
        {"lessons": 0, "best": None, "events": set()},
    )
    if event["event_id"] in learner["events"]:
        return result

    learner["events"].add(event["event_id"])
    learner["lessons"] += 1
    learner["best"] = event["score"] if learner["best"] is None \
        else max(learner["best"], event["score"])
    return result

The repository accompanying this article runs six checks: duplicate idempotency, zero-score recording, non-regressing best score, immutability after an accepted event, rejection of an invalid score, and no mutation after rejected input. All six passed locally with Python 3. That verifies this exercise only; it does not simulate CodeSignal or Duolingo infrastructure.

When you cannot run code, still express the test precisely: initial state, input, expected output, and the actual path you predict. An interviewer can evaluate that reasoning without execution. If execution is allowed, start with the smallest failing test instead of building an elaborate harness.

Write Feedback the Author Can Act On

A strong comment has four parts: severity, observed behavior, evidence, and a next step. Keep the causal chain short.

FindingSeverityEvidenceRecommended action
Replay increments lesson countBlockingExisting e1 changes count from 1 to 2Check duplicate before mutation; add replay test
Score 0 is skippedBlocking if zero is validTruthiness branch does not executeCompare against None or validate explicitly
Best score falls from 80 to 60BlockingLater lower score overwrites aggregatePreserve maximum; test decreasing sequence
Function mutates caller inputClarify or blocking by contractSame object changes in placeConfirm ownership; copy or document mutation
Variable naming preferenceNon-blockingNo behavioral impactMark as Nit or omit under time pressure

Ask questions when the contract is missing: "Can scores be zero?" or "Are event IDs globally unique or unique per learner?" Do not disguise a demand as a question when you have already proved a correctness failure. Conversely, do not call every preference a bug. Explain the trade-off: copying protects callers but costs memory; in-place mutation may be acceptable if ownership is exclusive and validation is atomic.

GitHub's official review documentation supports grouping comments into a final decision such as comment, approve, or request changes. In an interview, mirror that structure verbally: summarize the change, name the blocking findings in priority order, mention one meaningful non-blocking improvement, and state what evidence would make you comfortable approving.

Practice Without Memorizing a Script

Use three 35-minute rehearsals rather than rereading lists. In the first, review for correctness and write two counterexamples. In the second, review a different change for state ownership, error handling, and tests. In the third, explain your findings aloud while another person challenges one assumption.

After each rehearsal, score the evidence rather than the number of comments:

  • Did you state the contract before judging the code?
  • Did every blocking comment include a reproducible input or execution path?
  • Did you separate observed behavior from an assumption?
  • Did your fix match the scope of the bug?
  • Did you leave enough time for a summary and questions?
Practice questionSkill to rehearseDeliverable
How do you perform a thorough code review?Systematic coveragePrioritized review summary
Resolve a Disagreement in Code ReviewEvidence-based trade-offsDecision and escalation path
How do you conduct a code review exercise?Live narrationReview sequence
Review a JavaScript flatten PRBoundary casesInline findings and tests
Prioritize Critical Findings in a High-Load Batch Processor ReviewSeverity under loadBlocking versus follow-up list

For broader technique, use the Code Review Interview Guide after you finish this article's learning-session exercise.

A Clear Interview Narration

Begin with: "I want to confirm the contract before reviewing implementation details." Restate inputs, output, retry behavior, and invariants. Then say which path you will trace first and why. When you find a defect, provide a concrete example before proposing code.

Finish with a compact decision: "I found three correctness issues that can corrupt the lesson count or aggregate score. I would request changes until the duplicate, zero, and regression cases pass. The mutation question depends on the surrounding ownership contract; I would confirm that separately." This demonstrates prioritization without pretending you know missing context.

The public evidence does not reveal Duolingo's grading rubric, and no preparation guide can guarantee a specific 2027 format. What you can control is the quality of your reasoning: establish the contract, prove important failures, communicate their impact, and propose a testable repair.

Sources and Further Reading