Section D · Coding

Coding Fundamentals

The Python and SQL patterns FDE-DE technical screens actually reach for. Less algorithmic than a SWE loop, more practical — write something you'd actually ship to a customer next week.

What to expect

FDE-DE coding rounds are typically 45–60 minutes, one or two problems. The problems are framed in domain language (contracts, extractions, suppliers) rather than abstract algorithms. The interviewer is looking for:

  • Clean Python that someone at the customer would accept — types, small functions, clear names.
  • Fast SQL over messy data — handles NULLs, fanouts, dedupe deliberately.
  • Narrating tradeoffs as you write — "I could use a dict here, or a set, here's why I'm picking dict."
  • Domain-credible reasoning — when given a contract / supplier / spend prompt, you understand what the data actually represents.
  • Sensible follow-up handling — when the interviewer adds a wrinkle, you adapt rather than rewrite.

Python patterns

Dict-based aggregation

aggregate by key
from collections import defaultdict, Counter

# Sum spend by supplier
spend_by_supplier: dict[str, float] = defaultdict(float)
for row in invoices:
    spend_by_supplier[row["supplier_id"]] += row["amount"]

# Count documents per class
counts = Counter(d["doc_class"] for d in documents)

# Group rows for further processing
by_supplier: dict[str, list[dict]] = defaultdict(list)
for inv in invoices:
    by_supplier[inv["supplier_id"]].append(inv)

Pure functions with types

pure_function.py
from dataclasses import dataclass

@dataclass(frozen=True)
class Extraction:
    document_id: str
    field: str
    value: str
    confidence: float

def above_threshold(
    extractions: list[Extraction],
    threshold: float = 0.85,
) -> list[Extraction]:
    """Keep extractions with confidence at or above threshold."""
    return [e for e in extractions if e.confidence >= threshold]

Type hints, frozen dataclass, narrow function. This shape is what FDE-DE reviewers want to see — your interviewer can imagine pasting this into a deployment.

Generators for streams

When the document corpus doesn't fit in memory, use generators.

stream extractions
from collections.abc import Iterator
import json

def stream_extractions(path: str) -> Iterator[dict]:
    """Yield one extraction at a time from a JSON Lines file."""
    with open(path) as f:
        for line in f:
            yield json.loads(line)

# Compose with itertools / functions, never load everything at once
total = sum(e["confidence"] for e in stream_extractions("extractions.jsonl"))

Cleaning & reshaping

The "messy input" pattern

The most common FDE-DE coding problem shape: "here's a CSV / JSON / Excel of contract data, write a script that cleans and reshapes it for downstream use."

clean_contracts.py
import csv
from datetime import datetime
from typing import Iterable

def clean_date(s: str | None) -> str | None:
    """Normalize various date formats to ISO. Return None if unparseable."""
    if not s or not s.strip():
        return None
    s = s.strip()
    for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%d-%b-%Y", "%B %d, %Y"):
        try:
            return datetime.strptime(s, fmt).date().isoformat()
        except ValueError:
            continue
    return None  # unparseable; downstream should flag

def normalize_supplier(name: str | None) -> str | None:
    """Strip whitespace, collapse internal whitespace, title-case."""
    if not name: return None
    cleaned = " ".join(name.split()).strip()
    # Strip common corp suffixes for canonical name
    for suffix in (", Inc.", " Inc.", ", LLC", " LLC", ", Ltd", " Ltd"):
        if cleaned.endswith(suffix):
            cleaned = cleaned[:-len(suffix)]
            break
    return cleaned or None

def clean_contract_row(row: dict) -> dict:
    return {
        "contract_id": row["id"].strip(),
        "supplier": normalize_supplier(row.get("supplier_name")),
        "effective_date": clean_date(row.get("effective_date")),
        "value_usd": float(row["value"]) if row.get("value") else None,
    }

The senior touches:

  • Returning None on unparseable input rather than crashing — let the caller decide.
  • Small focused functions per field type.
  • Multiple date formats handled — real customer data has all of them.
  • Supplier suffix stripping — domain-specific normalization.

Fuzzy matching

Almost every FDE-DE round has a fuzzy-join wrinkle. Memorize a working implementation.

fuzzy_supplier_match.py
from difflib import SequenceMatcher

def similarity(a: str, b: str) -> float:
    """Normalized similarity between 0 and 1."""
    return SequenceMatcher(None, a.lower(), b.lower()).ratio()

def best_match(query: str, candidates: list[str], threshold: float = 0.8) -> str | None:
    """Return the best candidate match if above threshold, else None."""
    scored = [(c, similarity(query, c)) for c in candidates]
    scored.sort(key=lambda x: x[1], reverse=True)
    if not scored or scored[0][1] < threshold:
        return None
    return scored[0][0]

What to mention unprompted:

  • "For 10k+ candidates, I'd preprocess into n-gram or trigram buckets first to avoid O(N) per query."
  • "For names specifically, RapidFuzz is the production-grade library — much faster than SequenceMatcher."
  • "For truly large-scale name matching, I'd use embedding similarity + a vector index."

SQL patterns

Refer to 07-sql-and-data-modeling for the contract-data-specific patterns. The reusable patterns:

  • Window functions for "latest per group"ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ... DESC) then filter WHERE rn=1.
  • Anti-joins for "missing"LEFT JOIN ... WHERE right.key IS NULL for orphans, or NOT EXISTS for cleaner semantics.
  • CTEs as outline — one CTE per logical step, named descriptively.
  • JSON / JSONB navigation — Postgres ->>, Snowflake :, BigQuery JSON_VALUE. The customer's warehouse dictates the syntax.
  • QUALIFY clause — Snowflake / BigQuery support QUALIFY ROW_NUMBER() OVER (...) = 1 directly, avoiding the subquery.

The senior SQL signal

Use CTEs aggressively. Name them like prose. Comment when the logic isn't obvious. The interviewer reads the CTE names as the outline of your thinking — bad names ("t1", "step2") read junior.

Live-coding tips for FDE-DE

  1. Clarify before coding. Restate the problem. Ask about input format, expected output, edge cases. FDE rounds reward this disproportionately.
  2. State approach + tradeoffs out loud. "I'm going to use a dict because we'll have N keys and look them up M times. Could also sort and binary-search; not worth it for our N."
  3. Write small functions with types. Even at 15-minute scale, types and small functions read better than monolithic blocks.
  4. Handle NULLs deliberately. Customer data has nulls everywhere. Show you've thought about which fields can be null and what to do when they are.
  5. Trace one example through. After you write it, walk a sample row through the code out loud. Catches off-by-ones immediately.
  6. Anticipate the production follow-up. "What if the input is 100M rows?" "What if the customer wants this hourly instead of daily?" Have answers.
  7. For SQL: name your CTEs descriptively. The interviewer reads CTE names as outline.
  8. Cite customer context when asked about tradeoffs. "If this is a one-off audit for the customer, I'd just do X. If it'll run weekly in production, I'd refactor to Y." The contextual answer signals FDE thinking.