brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
language-family-idiomsImplements idiomatic code patterns for JVM, .NET, TypeScript, and Functional ecosystems — showing the right way to write code in each family using modern language features from 2025–2026.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: language-family-idioms description: Implements idiomatic code patterns for JVM, .NET, TypeScript, and Functional ecosystems — showing the right way to write code in each family using modern language features from 2025–2026. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: JVM idioms, Java records, Kotlin coroutines, C# primary constructors, TypeScript discriminated unions, Elixir GenServer, idiomatic code archetypes: - tactical - generation anti_triggers: - brainstorming - vague ideation - cross-language integration - language comparison - which language to use response_profile: verbosity: medium directive_strength: high abstraction_level: operational role: implementation scope: implementation output-format: code content-types: [code, guidance, do-dont, examples] related-skills: modern-language-comparison, polyglot-development, polyglot-developer-patterns, framework-selection
Senior engineer who writes idiomatic code in each programming language family using the features native to that ecosystem — not porting conventions from one family into another. When loaded, this skill makes the model produce code that follows the established conventions of JVM, .NET, TypeScript, and Functional families as they exist in 2025–2026.
Use this skill when:
Avoid this skill for:
modern-language-comparison insteadpolyglot-development insteadpolyglot-developer-patterns insteadframework-selection insteadIdentify the target language family — JVM (Java/Kotlin), .NET (C#/F#), JS/TS, or Functional (Erlang/Elixir). Checkpoint: Confirm the exact language version to select the correct feature set.
Determine the idiomatic approach for the task — Select patterns native to that family's conventions. Do not port from another family. Checkpoint: Verify you are using the modern feature set (Java 21+, C# 12+, TS 5.6+, Elixir 1.17+).
Write BAD → GOOD code pairs — Show the common anti-pattern first, then the idiomatic replacement with inline commentary explaining the difference. Checkpoint: Each pair must use the same problem domain (same inputs, same expected outputs).
Apply family-specific constraints — Enforce MUST DO / MUST NOT DO rules from the relevant family section. Checkpoint: No OOP anti-patterns in functional families, no mutable state where immutability is expected.
The JVM ecosystem has shifted toward value types, immutable data carriers, and structured concurrency. Java uses records for data carriers and sealed classes for type hierarchies. Kotlin leverages extension functions, data classes, and coroutines for asynchronous code.
// ❌ BAD — Verbose POJO hierarchy with manual equals/hashCode/toString, mutable fields
sealed abstract class PaymentResult permits PaymentResult.Success, PaymentResult.Failure {
private final String id;
protected PaymentResult(String id) { this.id = id; }
public String getId() { return id; }
// ❌ Must manually write equals(), hashCode(), toString() — boilerplate and error-prone
}
final class Success extends PaymentResult {
private final BigDecimal amount; // mutable!
private String transactionId; // mutable!
// ❌ Manual setter methods expose mutability
public void setTransactionId(String id) { this.transactionId = id; }
}
// ❌ BAD — instanceof + cast pattern matching (pre-Java 16 style)
if (result instanceof PaymentResult.Success) {
PaymentResult.Success s = (PaymentResult.Success) result; // unsafe cast, verbose
System.out.println(s.getAmount());
}
// ✅ GOOD — Record as data carrier + sealed interface for type hierarchy
record PaymentId(String value) {}
sealed interface PaymentResult permits PaymentResult.Success, PaymentResult.Failure {
record Success(PaymentId id, BigDecimal amount, String transactionId) implements PaymentResult {}
record Failure(PaymentId id, String reason) implements PaymentResult {}
}
// ✅ GOOD — Pattern matching with instanceof (Java 16+) AND pattern matching in switch (Java 21+)
if (result instanceof PaymentResult.Success s) { // type check + cast in one
System.out.println(s.amount()); // immutable access, no getter ceremony
}
// ✅ GOOD — Exhaustive pattern matching in switch expression (Java 21+)
String message = switch (result) {
case PaymentResult.Success(var id, var amount, _) ->
"Payment %s succeeded: $%s".formatted(id.value(), amount);
case PaymentResult.Failure(var id, var reason) ->
"Payment %s failed: %s".formatted(id.value(), reason);
}; // ✅ compiler enforces exhaustiveness — no missing cases
// ❌ BAD — Java-style template class with manual boilerplate
class User(val id: String, var name: String, var email: String) {
// ❌ var fields are mutable by default — easy to corrupt state
// ❌ Must manually implement equals/hashCode/toString or use IDE generation
override fun equals(other: Any?): Boolean { ... }
override fun hashCode(): Int { ... }
override fun toString(): String = "User($id, $name, $email)"
}
// ❌ BAD — Helper function attached to a utility class (Java static methods)
object EmailUtils {
fun validate(email: String): Boolean { ... }
fun mask(email: String): String { ... }
}
EmailUtils.validate(input) // ❌ procedural, breaks method chaining
// ❌ BAD — Callback hell or raw Thread usage
fun fetchUser(id: String): User { ... } // blocks the thread!
val user = fetchUser("1")
processUser(user)
// ✅ GOOD — Data class gives you value semantics for free
data class User(
val id: String,
val name: String,
val email: String
) // ✅ auto-generates equals(), hashCode(), toString(), copy(), componentN()
val original = User("1", "Alice", "alice@example.com")
val updated = original.copy(name = "Alice Smith") // ✅ immutable update via copy()
// ✅ GOOD — Extension functions that read like natural language
fun String.validateEmail(): Boolean =
Regex("^[\\w-.]+@[\\w-]+\\.[a-zA-Z]{2,}quot;).matches(this)
fun String.maskEmail(): String {
val parts = split("@")
return if (parts.size == 2) "${parts.first().first()}***@${parts[1]}" else this
}
// ✅ GOOD — Chained idiomatic usage
val email = "alice@example.com"
if (email.validateEmail()) {
println(email.maskEmail()) // outputs: a***@example.com
}
// ✅ GOOD — Coroutine-based async with structured concurrency
import kotlinx.coroutines.*
suspend fun fetchUser(id: String): User { ... } // suspends without blocking a thread
fun main() = runBlocking {
val user = fetchUser("1") // ✅ non-blocking suspend point
processUser(user) // ✅ sequential, readable code
}
// ✅ GOOD — Concurrent operations with async/await pattern
suspend fun fetchDashboard(userId: String): Dashboard = coroutineScope {
val userDeferred = async { fetchUser(userId) }
val preferencesDeferred = async { fetchPreferences(userId) }
val activityDeferred = async { fetchActivity(userId) }
Dashboard(
user = userDeferred.await(),
preferences = preferencesDeferred.await(),
activity = activityDeferred.await()
)
} // ✅ all three run concurrently; coroutineScope ensures cancellation propagation
.NET has embraced concise syntax with primary constructors, required members, collection expressions, and pattern matching. F# provides a pure functional alternative with pipeline operators, active patterns, and immutable-by-default semantics.
// ❌ BAD — Verbose property declarations, no compile-time guarantees on required fields
public class Order
{
// ❌ Requires manual null checks in constructor or at every access point
public string OrderId { get; set; } = default!;
public decimal Total { get; set; }
public DateTime OrderedAt { get; set; }
public List<OrderItem> Items { get; set; } = new();
// ❌ Manual equality comparison needed for value semantics
public override bool Equals(object? obj) => ...
}
// ❌ BAD — Verbose LINQ with anonymous types (less readable than method chain)
var result = from o in orders
where o.Total > 100
select new { o.OrderId, o.Total };
// ✅ GOOD — Primary constructor + required members (C# 12+)
public record Order(
string OrderId, // primary ctor param = readonly property
decimal Total,
DateTime OrderedAt,
List<OrderItem> Items // collection expression init below
);
// ✅ GOOD — Required properties with compile-time enforcement
public class ShippingAddress(
required string Street, // ✅ compiler error if omitted at construction
required string City,
string? Apartment = null // optional via default value
)
{
public string Street { get; init; } = Street;
public string City { get; init; } = City;
public string? Apartment { get; init; } = Apartment;
}
var address = new ShippingAddress { Street = "123 Main", City = "Boston" };
// var bad = new ShippingAddress { City = "Boston" }; // ❌ CS9035: Required member 'Street' must be set
// ✅ GOOD — Collection expressions (C# 12)
int[] primes = [2, 3, 5, 7, 11, 13]; // concise array literal
List<string> tags = ["java", "csharp", "go"]; // collection expression → List
// ✅ GOOD — Pattern matching with deconstruction (C# 7+ evolved in C# 12)
(string City, string State) GetLocation(Customer c) => (c.City, c.State);
var customer = new Customer("Alice", "Boston", "MA");
if (GetLocation(customer) is ("Boston", "MA")) // ✅ matches tuple via deconstruction
Console.WriteLine("Local customer");
// ✅ GOOD — Query comprehension with type inference + collection expressions
var highValueOrders = [.. orders.Where(o => o.Total > 100)
.OrderByDescending(o => o.Total)
.Select(o => o.OrderId)];
// ❌ BAD — Imperative style in F# (works, but fights the language)
let processCustomers customers =
let mutable results = []
for c in customers do
if c.IsActive && c.TotalSpent > 100m then
let name = c.Name.ToUpper()
results <- results @ [name] // ❌ list concatenation is O(n) per append!
results
// ✅ GOOD — Pipeline operator |> with immutable collections (F# idiomatic)
let processCustomers customers =
customers
|> List.filter (fun c -> c.IsActive && c.TotalSpent > 100m)
|> List.map (fun c -> c.Name.ToUpper()) // ✅ O(1) cons-based list construction
|> List.sortDescending // ✅ composable pipeline stages
// ✅ GOOD — Active patterns for multi-case pattern matching
let (|IsPremium|IsStandard|) customer =
if customer.TotalSpent > 500m then IsPremium
else IsStandard
let classifyCustomer c =
match c with
| IsPremium -> sprintf "%s is a VIP customer" c.Name
| IsStandard -> sprintf "%s is a standard customer" c.Name
// ✅ GOOD — Result pipeline for error handling (no exceptions)
type OrderResult =
| Valid of order: Order
| Invalid of errors: string list
let validateOrder (order: Order): OrderResult =
match order.Total > 0m, order.Items.Length > 0 with
| true, true -> Valid order
| false, _ -> Invalid ["Total must be positive"]
| _, false -> Invalid ["Must have at least one item"]
let processOrder (order: Order) =
validateOrder order
|> Result.map (fun o -> sprintf "Processing order %s" o.OrderId)
|> Result.mapError (fun errs -> String.concat "; " errs)
// ✅ Either "Processing order..." or an error string — no try/catch needed
// ✅ GOOD — Record types for immutable data (F# default)
type Customer = {
Id: Guid
Name: string
Email: string
IsActive: bool
}
let updateCustomerName customer newName =
{ customer with Name = newName } // ✅ creates a new record, original unchanged
The JS/TS ecosystem values structural typing, immutability through functional patterns, and async iteration. TypeScript 5.6+ brings refined branded types, discriminated unions for state machines, and improved template literal types. Node.js streaming patterns replace callback hell with async iterators.
// ❌ BAD — Plain string/number types are structurally identical and interchangeable
interface UserId { value: string }
interface PostId { value: string }
function getPost(userId: UserId): PostId { ... }
const postId: PostId = { value: "abc123" };
getPost(postId); // ❌ Compiles! UserId and PostId are structurally identical
// ❌ BAD — Callback hell with nested async operations
function fetchOrderHistory(userId: string, callback: (err: any, result: any) => void) {
getUser(userId, (err, user) => {
if (err) return callback(err);
getOrders(user.orgId, (err2, orders) => {
if (err2) return callback(err2);
getOrderDetails(orders.map(o => o.id), (err3, details) => {
if (err3) return callback(err3);
getCustomerFeedback(userId, (err4, feedback) => {
callback(null, { orders: details, feedback });
});
});
});
});
}
// ✅ GOOD — Branded types (nominal typing via intersection with unique symbol)
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
function userId(value: string): UserId {
return value as UserId; // ✅ single assertion at the boundary
}
function getPost(userId: UserId): PostId { ... }
const postId: PostId = { value: "abc123", __brand: "PostId" };
getPost(postId); // ❌ TypeScript error! UserId ≠ PostId
// ✅ GOOD — Discriminated union for finite state machine
type AppState =
| { status: "idle" }
| { status: "loading"; progress: number }
| { status: "success"; data: string }
| { status: "error"; error: { code: number; message: string } };
function renderApp(state: AppState): string {
switch (state.status) {
case "idle":
return "Ready to start";
case "loading":
return `Loading... ${state.progress}%`; // ✅ state.progress is guaranteed number
case "success":
return `Data loaded: ${state.data}`; // ✅ state.data is guaranteed string
case "error":
return `Error ${state.error.code}: ${state.error.message}`;
}
}
// ✅ Exhaustiveness check — add a new variant and compiler flags all unmatched locations
function _assertNever(state: AppState): never {
throw new Error(`Unexpected state: ${JSON.stringify(state)}`);
}
// ✅ GOOD — Async/await with proper error handling and typed responses
async function fetchOrderHistory(userId: string): Promise<OrderHistory> {
const user = await getUser(userId);
const orders = await getOrders(user.orgId);
const details = await Promise.all(orders.map(id => getOrderDetails(id)));
const feedback = await getCustomerFeedback(userId);
return { orders: details, feedback };
}
// ✅ GOOD — Async iteration for streaming data processing (Node.js)
import { pipeline } from "node:stream/promises";
import { createReadStream, createWriteStream } from "node:fs";
async function processLargeFile(inputPath: string, outputPath: string): Promise<void> {
const read = createReadStream(inputPath, { encoding: "utf-8" });
const write = createWriteStream(outputPath, { encoding: "utf-8" });
// ✅ pipeline auto-handles backpressure, cleanup, and errors
await pipeline(
read,
async function* (source) {
for await (const chunk of source) {
yield transformChunk(chunk); // ✅ async transformer in the pipeline
}
},
write
);
}
// ✅ GOOD — Template literal types for route matching (TypeScript 4.1+)
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Route<Suffix extends string> = `${HttpMethod} /api/${Suffix}`;
const getRoute: Route<"users/:id"> = "GET /api/users/:id";
// const badRoute: Route<"users/:id"> = "PATCH /api/users/:id"; // ❌ Type error!
Functional families treat immutability and message passing as foundational, not optional. Elixir on the BEAM virtual machine uses OTP supervision trees and GenServer processes for fault-tolerant systems. State is managed through message-passing actors, not mutable fields.
# ❌ BAD — Trying to use mutable state or a plain function module as a process
defmodule BadCounter do
@counter 0 # ❌ This is compile-time constant, NOT mutable state
def increment do
# ❌ No persistent state across calls; every invocation starts at 0
@counter + 1
end
# ❌ Attempting to "share" state via module attribute — doesn't work in BEAM
def get do
@counter
end
end
# ❌ BAD — No supervision tree; a crash means total loss of state
# defmodule MyApp do
# use Application
# def start(_type, _args) do
# # ❌ Starting GenServer directly with no supervisor
# Counter.start_link(0)
# end
# end
# ❌ BAD — Trying to mutate a map (Elixir maps are immutable; this creates confusion)
defmodule InventoryBad do
def bad_update(inventory, item, qty) do
# ❌ This does NOT modify inventory in place! Must rebind:
inventory = Map.put(inventory, item, Map.get(inventory, item, 0) + qty)
# ✅ Works but verbose and error-prone with missing keys
end
end
# ✅ GOOD — GenServer with proper state management and fault tolerance
defmodule Counter do
use GenServer
# --- Public API ---
@spec start_link(non_neg_integer()) :: GenServer.on_start()
def start_link(initial_value \\ 0) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
@spec increment(GenServer.server(), non_neg_integer()) :: :ok | {:error, term()}
def increment(server, amount \\ 1) when is_integer(amount) and amount > 0 do
GenServer.call(server, {:increment, amount})
end
@spec get(GenServer.server()) :: {:ok, non_neg_integer()} | {:error, term()}
def get(server) do
GenServer.call(server, :get)
end
# --- GenServer Callbacks ---
@impl true
def init(initial_value) when is_integer(initial_value) and initial_value >= 0 do
{:ok, initial_value}
end
@impl true
def handle_call({:increment, amount}, _from, state) do
new_state = state + amount
{:reply, :ok, new_state}
end
@impl true
def handle_call(:get, _from, state) do
{:reply, {:ok, state}, state}
end
@impl true
def terminate(_reason, _state) do
# ✅ Cleanup resources if needed on shutdown
:ok
end
end
# Usage:
{:ok, _pid} = Counter.start_link(0)
Counter.increment(self(), 5)
{ok, value} = Counter.get(self()) # value = 5
# ✅ State persists in the GenServer process; multiple callers share it safely
# ✅ GOOD — Proper OTP supervision tree (Elixir 1.17+ style)
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
# ✅ Counter is supervised — if it crashes, supervisor restarts it with initial value
{Counter, 0},
# ✅ Other processes under the same supervisor tree
{Registry, keys: :unique, name: MyApp.Registry}
]
opts = [
strategy: :one_for_one, # ✅ Restart only the crashed process
name: MyApp.Supervisor
]
Supervisor.start_link(children, opts)
end
end
# ✅ GOOD — Immutable data manipulation with pattern matching and Map.update!
defmodule Inventory do
# ✅ GOOD — Pattern matching + Map.update!/3 for clean immutable updates
def good_update(inventory, item, qty) do
inventory
|> Map.update!(item, fn current -> current + qty end)
# ✅ Raises KeyError if item doesn't exist (fail-fast for unexpected state)
# Or use Map.update/4 for safe handling:
# |> Map.update(item, qty, &(&1 + qty)) # defaults to 0 if missing
end
end
# ✅ GOOD — Immutable data cascading with struct-based records
defmodule Product do
defstruct [:sku, :name, :price, :stock] :: [sku: String.t(), name: String.t(), price: Decimal.t(), stock: non_neg_integer()]
# ✅ Immutable update via struct construction
def restock(%Product{sku: sku} = product, quantity) do
%Product{product | stock: product.stock + quantity}
end
def price_change(%Product{sku: sku} = product, new_price) when is_struct(product, Product) do
%{product | price: new_price}
end
end
# Usage with pattern matching for immutability
product = %Product{sku: "ABC", name: "Widget", price: Decimal.new("9.99"), stock: 100}
restocked = Product.restock(product, 50)
# product.stock is still 100 ✅ immutable!
# restocked.stock is 150 ✅ new struct created
# ✅ GOOD — Enum/LazyEnum for lazy pipeline transformations (memory efficient)
defmodule DataProcessor do
def process_large_dataset(file_path) do
file_path
|> File.stream!() # ✅ Lazy: reads line-by-line, no full file in memory
|> Stream.map(&String.trim/1) # ✅ Pipeline transformation
|> Stream.filter(&(not String.contains?(&1, "#"))) # ✅ Skip comments
|> Enum.map(&parse_record/1) # ✅ Materialize at the end (terminal operation)
|> Enum.group_by(& &1.category) # ✅ Group results
end
defp parse_record(line) do
line |> String.split(",") |> build_record()
end
defp build_record([sku, name, price, stock]), do: %Product{sku: sku, name: name, price: Decimal.new(price), stock: String.to_integer(stock)}
defp build_record(_), do: nil
end
record for immutable data carriers; prefer sealed interface over deep class hierarchiesswitch expressions (Java 21+) instead of if/else chainsval by default; use var only when mutability is requiredsuspend functions for async; never block a dispatcher thread with blocking callsList.of(), Set.of()) over mutable onesequals(), hashCode(), and toString() on value objects when record sufficesThread.sleep() or raw Thread objects in Kotlin; use coroutines or structured concurrencyrequired modifier for compile-time safety[...] for array and collection literals|>) over imperative loops; embrace immutability as the defaultpipeline() from node:stream/promises for streaming data processingany type; prefer unknown when the type is truly undeterminedFile.stream!() with lazy pipelines for large files; never read entire files into memoryWhen this skill is active, the model's output must contain:
// ❌ BAD comments explaining WHY it is wrong for that family// ✅ GOOD commentary explaining WHY it is better| Skill | Purpose |
|---|---|
modern-language-comparison | Benchmark comparison and language selection decisions before committing to a family |
polyglot-development | Cross-language integration, monorepo patterns, gRPC/Protobuf boundaries between families |
polyglot-developer-patterns | Learning strategies for building fluency across multiple language families |
framework-selection | Choosing specific frameworks within a family (e.g., Quarkus vs. Spring Boot in JVM) |
Authoritative documentation links for each language family covered by this skill. These references are resolved at load time to provide additional context on modern features and idiomatic patterns.