All chapters in this track
- 01Chapter 1: Why Coding Standards Matter: Readability, Onboarding, Defects, Consistency at Scale
- 02Chapter 2: Naming Conventions
- 03Chapter 3: Formatting and Style
- 04Chapter 4: Comments and Self-Documenting Code
- 05Chapter 5: Error Handling Conventions
- 06Chapter 6: Code and Project Organization (Files, Modules, Packages)
- 07Chapter 7: Writing Testable Code
- 08Chapter 8: Commit Hygiene: Atomic Commits, Messages, PR-Sized Changes
- 09Chapter 9: Language-Specific Idioms and Anti-Patterns Across TypeScript and Python
- 10Chapter 10: Security-Conscious Basics: Input Validation, Secrets Handling, Injection Risks
- 11Chapter 11: Enforcing Standards Automatically: Linters, Formatters, CI Gates
- 12Chapter 12: Creating, Documenting, and Evolving a Team Style Guide (Exceptions and Legacy Code)
- 13Chapter 13: Worked Example: Refactoring a Messy Function to Standard
- 14Chapter 14: Worked Example: Drafting a Style Guide Section From Scratch
- 15Chapter 15: Common Interview Questions About Coding Standards and Code Quality
Chapter 9 of 15
Chapter 9: Language-Specific Idioms and Anti-Patterns Across TypeScript and Python
Why this matters
Every principle covered so far in this book is language-agnostic, but how each principle is idiomatically expressed differs meaningfully between TypeScript and Python — and writing "translated" code (Python that reads like transliterated Java, or TypeScript that ignores its type system's guarantees) is a real, common code smell that experienced reviewers notice immediately. This chapter covers the idioms specific to each language, and the anti-patterns that come from not using them.
TypeScript idioms and anti-patterns
[BEGINNER] Idiom: let the type system do validation work.
// Anti-pattern: using `any`, discarding the entire point of a static type system
function processOrder(order: any): any {
return order.total * 1.08;
}
// Idiomatic: precise types let the compiler catch entire categories of bugs before runtime
interface Order {
total: number;
taxRate: number;
}
function processOrder(order: Order): number {
return order.total * (1 + order.taxRate);
}
Idiom: prefer union types and discriminated unions over loose booleans/strings for representing state.
// Anti-pattern: a string that could be any value, with no compile-time safety
function setStatus(status: string) { ... }
setStatus("pnding"); // typo -- compiles fine, fails silently at runtime
// Idiomatic: a union type makes invalid values a COMPILE-TIME error
type OrderStatus = "pending" | "shipped" | "delivered" | "cancelled";
function setStatus(status: OrderStatus) { ... }
setStatus("pnding"); // COMPILE ERROR -- caught before the code ever runs
Anti-pattern: overusing non-null assertions (!) to silence the compiler instead of handling null/undefined properly:
// Anti-pattern: `!` tells the compiler "trust me," discarding its safety check entirely
function getFirstItem(items: Item[] | null): Item {
return items![0]; // if items is actually null, this throws at RUNTIME --
// exactly the bug the type system was trying to prevent
}
// Idiomatic: handle the null case explicitly, keeping the compiler's guarantee intact
function getFirstItem(items: Item[] | null): Item | undefined {
return items?.[0];
}
Python idioms and anti-patterns
[BEGINNER] Idiom: use context managers (with) for resource management, not manual acquire/release.
# Anti-pattern: manual resource management -- easy to forget the close() on an exception path
f = open("data.txt")
data = f.read()
f.close() # skipped entirely if f.read() raises an exception above it
# Idiomatic: `with` guarantees cleanup, even if an exception occurs inside the block
with open("data.txt") as f:
data = f.read()
# f.close() is called automatically, unconditionally, when the block exits
Idiom: use list/dict comprehensions for simple transformations, but not at the expense of readability for complex logic.
# Idiomatic for SIMPLE transformations:
active_user_emails = [user.email for user in users if user.is_active]
# ANTI-PATTERN: cramming complex, multi-condition logic into a comprehension
# specifically because it's "more Pythonic" -- this is LESS readable, not more:
result = [transform(x) for x in items if condition_a(x) and condition_b(x) and not condition_c(x) if x.value > threshold]
# Idiomatic for COMPLEX logic: a regular loop is clearer, and just as "Pythonic"
result = []
for x in items:
if condition_a(x) and condition_b(x) and not condition_c(x) and x.value > threshold:
result.append(transform(x))
Anti-pattern: using a mutable default argument — one of Python's most infamous, specific footguns:
# ANTI-PATTERN: the default list is created ONCE, at function definition time,
# and SHARED across every call that doesn't pass its own list explicitly:
def add_item(item, items=[]):
items.append(item)
return items
add_item("a") # returns ["a"]
add_item("b") # returns ["a", "b"] -- the SAME list object, silently accumulating!
# Idiomatic: use None as the default, creating a fresh list inside the function
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
The meta-lesson: idioms exist because they encode hard-won, language-specific knowledge
[SENIOR INSIGHT] Every idiom in this chapter exists because engineers were repeatedly bitten by the anti-pattern it replaces — TypeScript's discriminated unions exist because loose string/boolean state representations caused real, recurring bugs; Python's mutable-default-argument warning exists because that specific footgun has surprised nearly every Python engineer at least once. This is exactly this book's opening chapter's point (standards encode accumulated, hard-won lessons) applied at the language-idiom level specifically — a code reviewer flagging "don't use a mutable default argument" isn't enforcing arbitrary taste, they're preventing a bug class with a well-documented history.
When NOT to force an idiom
[IN PRACTICE] Idioms should serve readability and correctness, not be applied reflexively as a badge of language fluency. A list comprehension crammed with four chained conditions "because comprehensions are Pythonic" is a worse outcome than a clear loop — the idiom's value (conciseness for simple cases) doesn't transfer to a case where conciseness comes at the cost of clarity. The same discipline from the OOP book's Chapter 14 (recognizing when a general principle stops applying) is relevant here: know why an idiom is usually better, so you can recognize the specific cases where it isn't.
Common mistakes
- Using
anyin TypeScript to avoid dealing with a typing challenge, discarding the compiler's entire safety net for that piece of code and reintroducing exactly the runtime error categories static typing exists to prevent. - Using Python's mutable default argument pattern, unaware of the shared-state footgun — one of the most common, most surprising bugs new (and experienced) Python engineers encounter.
- Forcing a comprehension/idiom onto genuinely complex logic "to be more idiomatic," when a plain loop or explicit structure would be more readable — idioms serve readability, not the reverse.
- Not recognizing a language-specific anti-pattern in code review because it "runs fine" — many of these anti-patterns (mutable defaults, non-null assertions) work correctly in the common case and fail only under specific, sometimes rare conditions, exactly like the concurrency bugs from the companion Concurrency book.
Discussion / interview questions
-
"Why is Python's mutable default argument considered a common footgun, and how do you avoid it?" Strong answer: A mutable default argument (like
items=[]) is created once, at function definition time, and shared across every call that doesn't explicitly pass its own value — leading to silent, cross-call state accumulation; the fix is defaulting toNoneand creating a fresh mutable object inside the function body. -
"What's the risk of overusing TypeScript's
anytype or non-null assertion operator (!)?" Strong answer: Both discard the compiler's safety checks for that specific code —anydisables type checking entirely, and!asserts a value isn't null/undefined without verification, reintroducing exactly the runtime error categories (type mismatches, null reference errors) that TypeScript's type system exists to catch at compile time instead. -
"Is a list comprehension always more 'Pythonic' or preferable to a regular loop?" Strong answer: No — comprehensions are idiomatic for simple, single-condition transformations; cramming complex, multi-condition logic into a comprehension for its own sake often makes code less readable than an equivalent explicit loop, and readability should take priority over reflexively applying an idiom.
-
"Why do language-specific idioms matter as a coding standard, beyond just 'writing more X-like code'?" Strong answer: Idioms encode accumulated, hard-won knowledge about specific bug classes and pitfalls particular to that language (discriminated unions preventing invalid-state bugs,
withstatements preventing resource leaks) — following them isn't about style conformity, it's about avoiding well-documented, recurring failure modes specific to that language's semantics.
Chapter summary / cheat sheet
- TypeScript: let the type system validate for you (avoid
any), use discriminated unions over loose strings/booleans for state, handlenull/undefinedexplicitly rather than silencing the compiler with!. - Python: use
withfor resource management, use comprehensions for simple transformations only, never use a mutable default argument. - Every idiom in this chapter exists because its corresponding anti-pattern caused real, recurring, well-documented bugs — treating idioms as arbitrary style preference misses their actual purpose.
- Idioms should serve readability and correctness — apply them where they genuinely help, and recognize when forcing one (a crammed comprehension) hurts more than it helps.