FDEInterviews logo
Coding & DSA / 08
easyAnthropicRetoolOpenAI

Build a wc-lite: count lines, words, and characters in text, with flags, factored for extension

Anthropic-style screens open with deceptively simple builds like this, then extend them three times. The grade isn't the counting; it's whether your first version survives the extensions without a rewrite.

Updated Aug 2026 · Grounded in real Forward Deployed Engineer interview loops and written to a senior-engineer editorial bar.

TL;DR: Separate the pure counting core (text to a Counts dataclass) from the thin I/O shell that reads files and formats output. The structure is the answer: it absorbs new flags, stdin, and multiple files without a rewrite.

How to approach it On a practical screen (Anthropic explicitly says "not LeetCode"), this question is about structure under extension. The interviewer will add flags, then multiple files, then maybe grep-like filtering. So separate three concerns from the start and say so: counting (pure function on text), option handling (which counts to show), and I/O (files/stdin). Pure core + thin shell is the pattern every later multi-part question rewards.

A strong answer Start with the pure core and tests, then wrap it:

from dataclasses import dataclass

@dataclass
class Counts:
    lines: int
    words: int
    chars: int

def count_text(text: str) -> Counts:
    return Counts(
        lines=text.count("\n"),          # wc semantics: newline count
        words=len(text.split()),          # any-whitespace runs
        chars=len(text),
    )

Narrate the two semantic decisions; they're the real content. wc counts newlines, so a file without a trailing newline has its last line uncounted; you flag the discrepancy and pick a convention out loud. And split() with no args handles tabs/multiple spaces correctly where split(" ") would not.

def test_count_text():
    c = count_text("hello world\nsecond line\n")
    assert (c.lines, c.words, c.chars) == (2, 4, 24)
    assert count_text("").words == 0                  # empty input
    assert count_text("no-newline").lines == 0        # the convention we chose
    assert count_text("a\t b   c\n").words == 3       # whitespace runs

Then the thin shell, where flags select which fields to print, defaulting to all, mirroring real wc:

import sys

def run(paths: list[str], show: set[str]) -> str:
    out = []
    for path in paths:
        try:
            text = open(path, encoding="utf-8").read()
        except OSError as e:
            out.append(f"wc-lite: {path}: {e.strerror}")
            continue                                   # keep going, like real wc
        c = count_text(text)
        fields = [str(getattr(c, f)) for f in ("lines", "words", "chars") if f in show]
        out.append("\t".join(fields + [path]))
    return "\n".join(out)

A missing file produces an error line and processing continues: small touch, strong "I've shipped CLIs" signal.

Extension they will ask forWhat your structure must already allow
A new flag, say -L for longest lineAdding one counter without touching the others
Reading from stdin as well as filesThe counting logic never knowing where bytes came from
Multiple files with a total lineCounting returning a value rather than printing
A different definition of a wordThe tokenizer being one replaceable function
Enormous filesStreaming line by line, never read()

This question is not about counting. It is about whether your first version has a shape that survives the second requirement, which is why the grader adds one the moment you finish.

The multiple-files-with-totals extension is worth showing at full size, because "the structure absorbs it" is a claim and the diff is the proof. The entire change is four lines on the dataclass (written and executed: two files counting 1/2/12 and 2/4/19 sum to 3/6/31):

    def __add__(self, other: "Counts") -> "Counts":
        return Counts(self.lines + other.lines,
                      self.words + other.words,
                      self.chars + other.chars)

and one line in the shell: accumulate total = total + c in the loop, emit a total row after it. Nothing in count_text changed, nothing in flag handling changed, and the reason is visible in hindsight: the core returns a value, and values compose. Compare the counterfactual: if count_text printed its results, the totals extension would mean threading an accumulator through print statements, and the grader would watch the rewrite eat your remaining minutes. This tiny before-and-after is the entire pure-core-thin-shell argument compressed into one extension, and narrating it in those terms is worth more than the four lines themselves.

What interviewers probe next The extensions are the interview. (1) -l/-w/-c flags: already handled by show; you anticipated the API, which is the point. (2) Multiple files + totals row: sum Counts objects; adding __add__ to the dataclass keeps it clean. (3) grep-lite, print lines matching a pattern: the I/O shell is reused; only the core changes, which is exactly the refactorability being scored. (4) 2 GB file? Stream line-by-line (for line in f) instead of read(); the Counts accumulation already works incrementally. (5) Encoding errors? errors="replace" vs failing loudly: discuss, pick, justify.

Common mistakes One 40-line main() mixing parsing, counting, and printing: works for L1, collapses at the first extension, and the rewrite eats your remaining time. Reading whole files when asked about large inputs. Silently choosing word/line semantics instead of stating the convention. No tests until prompted: on Anthropic-style screens, tests-unprompted is close to a hard requirement. And clever one-liners that resist modification: this format scores boring, extendable code.

That one was free — and so are 10 answers per topic without an account. Signing in doubles that to 20, opens the Plus lessons in the courses, and remembers which topics you keep getting wrong.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 10 to 20 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

The hidden rubric is extensibility: separate reading input from counting from formatting on the first pass, because the interviewer is about to ask for a new flag, then stdin support, then multiple files with a total line, and a tangled single function forces a rewrite each time. The candidates who over-engineer abstractions before the first extension lose just as surely as the ones who hard-code everything; the win is the smallest structure that bends rather than breaks.

DISCUSSION · 0

No comments yet — be the first to share your approach.