TypeScript Structural Typing
Compatibility based on shape, not name.
type User = { name: string };
type Employee = { name: string; role: string };const emp: Employee = { name: "Ana", role: "dev" };
const user: User = emp; // ✅ structural match
function greet(u: User) { console.log(u.name); }
greet(emp); // ✅ Employee satisfies User shape
greet({ name: "Ana" }); // ✅ inline object matches
❌ Does Not Work
const partial = { role: "dev" };
const u: User = partial;
// ❌ Property 'name' is missing
greet({ name: "Ana", age: 30 });
// ❌ Object literal may only specify known properties
Java Nominal Typing
Compatibility requires explicit declared relationship.
class User { String name; }
class Employee { String name; String role; }User user = new User();
user.name = "Ana"; // ✅
// With explicit relationship
class Employee extends User { String role; }
User u = new Employee(); // ✅ subclass
❌ Does Not Work
class A { String name; }
class B { String name; }
A a = new A();
B b = a;
// ❌ incompatible types — no relationship declared
C# Nominal Typing
Nominal like Java, but with implicit interface implementation via duck typing in some contexts.
class User { public string Name { get; set; } }
class Employee { public string Name { get; set; } public string Role { get; set; } }// With explicit relationship
class Employee : User { public string Role { get; set; } }
User u = new Employee(); // ✅
// foreach works on any type with GetEnumerator() — duck typing
class MyList {
public IEnumerator<int> GetEnumerator() => /* ... */;
}
foreach (var x in new MyList()) { } // ✅ no interface needed
❌ Does Not Work
class A { public string Name { get; set; } }
class B { public string Name { get; set; } }
A a = new A();
B b = a;
// ❌ Cannot implicitly convert type 'A' to 'B'
Go Structural (Interfaces)
Interfaces are structural — no "implements" keyword. Concrete types are nominal.
type User struct { Name string }
type Namer interface { GetName() string }
func (u User) GetName() string { return u.Name }// Implicit interface satisfaction
var n Namer = User{Name: "Ana"} // ✅ no "implements"
fmt.Println(n.GetName()) // ✅
// Any type with GetName() satisfies Namer
type Bot struct{}
func (b Bot) GetName() string { return "Bot" }
var n2 Namer = Bot{} // ✅
❌ Does Not Work
type A struct { Name string }
type B struct { Name string }
var a A = A{Name: "x"}
var b B = a
// ❌ cannot use a (type A) as type B
// Missing method → interface not satisfied
type Broken struct{}
var n3 Namer = Broken{}
// ❌ Broken does not implement Namer (missing GetName)
TypeScript Discriminated Unions
Tagged unions narrowed via literal discriminant field.
type Success = { status: "success"; data: string };
type Failure = { status: "error"; message: string };
type ApiResponse = Success | Failure;function handle(r: ApiResponse) {
if (r.status === "success") {
console.log(r.data); // ✅ narrowed to Success
} else {
console.log(r.message); // ✅ narrowed to Failure
}
}
handle({ status: "success", data: "OK" }); // ✅
❌ Does Not Work
function broken(r: ApiResponse) {
console.log(r.data);
// ❌ 'data' does not exist on type 'Failure'
}
handle({ status: "maybe", data: "x" });
// ❌ '"maybe"' not assignable to '"success"|"error"'
Java Sealed Types (17+)
Sealed class/interface hierarchy with exhaustive pattern matching.
sealed interface ApiResponse
permits Success, Failure {}
final class Success implements ApiResponse { String data; }
final class Failure implements ApiResponse { String message; }String result = switch (r) {
case Success s -> s.data; // ✅
case Failure f -> f.message; // ✅
}; // exhaustive check by compiler
❌ Does Not Work
final class Timeout implements ApiResponse {}
// ❌ not allowed in sealed hierarchy
System.out.println(r.data);
// ❌ cannot find symbol — must narrow first
C# Abstract Records + Pattern Matching
Abstract records with recursive pattern matching and switch expressions.
abstract record ApiResponse;
record Success(string Data) : ApiResponse;
record Failure(string Message) : ApiResponse;string Handle(ApiResponse r) => r switch {
Success s => s.Data, // ✅
Failure f => f.Message, // ✅
_ => throw new Exception()
};
// Recursive patterns
if (r is Success { Data: "OK" }) { } // ✅ nested match
❌ Does Not Work
Console.WriteLine(r.Data);
// ❌ 'ApiResponse' does not contain 'Data'
// Not sealed by default — anyone can subclass
// Use 'file' access modifier or internal to limit
Go Interface + Type Switch
Interfaces with marker methods and runtime type switches.
type ApiResponse interface{ isResponse() }
type Success struct{ Data string }
type Failure struct{ Message string }
func (Success) isResponse() {}
func (Failure) isResponse() {}func handle(r ApiResponse) string {
switch v := r.(type) {
case Success: return v.Data // ✅
case Failure: return v.Message // ✅
default: return ""
}
}
❌ Does Not Work
fmt.Println(r.Data)
// ❌ r.Data undefined (type ApiResponse has no field Data)
// No exhaustiveness check — compiler won't warn
// if you add a new variant and forget to handle it
TypeScript Compile-time String Composition
Compose string literal types at the type level using template syntax.
type Entity = "rates" | "services";
type Path = `/${Entity}`;
type Event = `user:${"created" | "deleted"}`;const p: Path = "/rates"; // ✅
const e: Event = "user:created"; // ✅
function navigate(path: Path) { }
navigate("/services"); // ✅
❌ Does Not Work
const bad: Path = "/users";
// ❌ '"/users"' not assignable to '"/rates"|"/services"'
navigate("/rates/123");
// ❌ not assignable to type Path
Java No Equivalent
No compile-time string type composition.
✅ Works (runtime only)Set<String> VALID = Set.of("/rates", "/services");
void navigate(String path) {
if (!VALID.contains(path))
throw new IllegalArgumentException(path);
}
navigate("/rates"); // ✅ at runtime
❌ Does Not Work
navigate("/users"); // compiles ✅ → ❌ runtime exception
// No way to catch at compile time
C# No Equivalent
No compile-time string literal types.
✅ Works (runtime only)void Navigate(string path) {
if (path is not "/rates" and not "/services")
throw new ArgumentException(path);
}
Navigate("/rates"); // ✅ at runtime
❌ Does Not Work
Navigate("/users"); // compiles ✅ → ❌ runtime exception
// Same limitation as Java — String is String
Go No Equivalent
No compile-time string literal types.
✅ Works (runtime only)func navigate(path string) error {
switch path {
case "/rates", "/services": return nil
default: return fmt.Errorf("invalid: %s", path)
}
}
navigate("/rates") // ✅
❌ Does Not Work
navigate("/users") // compiles ✅ → ❌ returns error
// Go has no string literal types
TypeScript Programmatic Type Transformation
Transform types programmatically using keyof, in, and conditional types.
type User = { id: string; name: string; email: string };
type ReadonlyUser = { readonly [K in keyof User]: User[K] };
type PartialUser = { [K in keyof User]?: User[K] };function update(id: string, patch: PartialUser) { /* ... */ }
update("1", { name: "Ana" }); // ✅ only name
update("1", {}); // ✅ empty
const frozen: ReadonlyUser = { id:"1", name:"A", email:"a@b" };
console.log(frozen.name); // ✅
❌ Does Not Work
frozen.name = "B";
// ❌ Cannot assign to 'name' — read-only property
update("1", { name: "X", age: 30 });
// ❌ 'age' does not exist in type PartialUser
Java Manual Duplication
No type-level transformations.
✅ Worksrecord User(String id, String name, String email) {}
// Must create a separate DTO class manually
record UserPatch(String name, String email) {} // "partial"
var p = new UserPatch("Ana", null); // ✅
❌ Does Not Work
// Cannot derive Partial<User> automatically
// No compile-time meta-programming at type level
C# Source Generators (partial)
No mapped types natively, but source generators can auto-generate variants.
✅ Worksrecord User(string Id, string Name, string Email);
// "with" expressions create modified copies
var user = new User("1", "Ana", "a@b.com");
var updated = user with { Name = "Ana" }; // ✅ immutable update
❌ Does Not Work
// Cannot derive Partial<User> at type level
// Must write separate DTO or use nullable props manually
user.Name = "X";
// ❌ init-only property — cannot assign after init
Go Manual Structs
No type-level transformation. Use pointer fields for optional.
type User struct { ID, Name, Email string }type User struct { ID, Name, Email string }
// "Partial" via pointer fields
type UserPatch struct {
Name *string
Email *string
}
name := "Ana"
patch := UserPatch{Name: &name} // ✅ only name set
❌ Does Not Work
// No Readonly wrapper — must use unexported fields
// No way to derive types from other types programmatically
TypeScript Built-in Type Helpers
Standard library of type transformations: Pick, Omit, Exclude, Extract, etc.
type User = { id: string; name: string; email: string };
type Preview = Pick<User, "id" | "name">;
type Create = Omit<User, "id">;
type Entity = "rates" | "services" | "all";
type Single = Exclude<Entity, "all">;const p: Preview = { id: "1", name: "Ana" }; // ✅
const c: Create = { name: "Ana", email: "a@b" }; // ✅
function fetchOne(e: Single) {}
fetchOne("rates"); // ✅
❌ Does Not Work
const bad: Preview = { id:"1", name:"X", email:"e" };
// ❌ 'email' does not exist in Pick<...>
fetchOne("all");
// ❌ '"all"' not assignable to '"rates"|"services"'
Java No Equivalent
Must define each projection as a separate record/class.
record User(String id, String name, String email) {}
record UserPreview(String id, String name) {} // manual Pick
record CreateUser(String name, String email) {} // manual Omit
C# No Equivalent
No type-level Pick/Omit. Separate DTOs or anonymous types.
✅ Works// Anonymous types for ad-hoc projections
var preview = new { user.Id, user.Name }; // ✅
Console.WriteLine(preview.Name); // ✅
// LINQ projection
var previews = users.Select(u => new { u.Id, u.Name }); // ✅
❌ Does Not Work
// Anonymous types can't be passed across method boundaries easily
void Show(??? preview) { } // ❌ no way to declare the type
Go No Equivalent
Must define separate structs. No generics-based type transformation.
type User struct { ID, Name, Email string }
type UserPreview struct { ID, Name string } // manual
type CreateUser struct { Name, Email string } // manual
TypeScript Full Generics
Structural generics with full type inference and constraint support.
function identity<T>(value: T): T { return value; }
interface Repo<T> { find(id: string): T | undefined; }const n = identity(42); // ✅ inferred as number
function len<T extends { length: number }>(x: T) { return x.length; }
len("abc"); // ✅
len([1,2]); // ✅
❌ Does Not Work
len(42); // ❌ number has no 'length'
function bad<T>(v: T): T { return v.toString(); }
// ❌ Type 'string' not assignable to 'T'
Java Generics (Type Erasure)
Generics erased at runtime. Bounded type parameters for constraints.
public <T> T identity(T value) { return value; }String s = identity("hello"); // ✅
<T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
max(3, 5); // ✅ → 5
❌ Does Not Work
if (list instanceof List<String>) {} // ❌ erasure
T[] arr = new T[10]; // ❌ generic array
List<int> nums; // ❌ must use Integer
C# Reified Generics
Generic type info preserved at runtime — no erasure.
T Identity<T>(T value) => value;var n = Identity(42); // ✅ inferred as int
var s = Identity("x"); // ✅
// Runtime type available — no erasure!
Type t = typeof(List<string>);
Console.WriteLine(t.GenericTypeArguments[0]); // ✅ → String
// Primitives work directly
List<int> nums = new() { 1, 2, 3 }; // ✅ no boxing
❌ Does Not Work
T Create<T>() => new T();
// ❌ Cannot create instance of T — need 'where T : new()' constraint
void Bad<T>(T val) { int x = (int)val; }
// ❌ Cannot convert type 'T' to 'int'
Go Generics (1.18+)
Type parameters with interface constraints. Monomorphized at compile time.
func Identity[T any](value T) T { return value }n := Identity(42) // ✅ inferred
s := Identity("hi") // ✅
// Constraints
func Max[T constraints.Ordered](a, b T) T {
if a > b { return a }
return b
}
Max(3, 5) // ✅ → 5
❌ Does Not Work
func Bad[T any](v T) { fmt.Println(v.Name) }
// ❌ v.Name undefined — 'any' has no fields
// No generic methods on types (only functions and types)
// No variance/wildcards
TypeScript Structural / Implicit
Variance inferred from structural assignability.
type Animal = { name: string };
type Dog = Animal & { breed: string };
type Logger<T> = (item: T) => void;type Animal = { name: string };
type Dog = Animal & { breed: string };
let dogs: Dog[] = [{ name: "Rex", breed: "Lab" }];
let animals: Animal[] = dogs; // ✅ structural covariance
type Logger<T> = (item: T) => void;
const logAnimal: Logger<Animal> = (a) => console.log(a.name);
const logDog: Logger<Dog> = logAnimal; // ✅ contravariant param
❌ Does Not Work
const logDogOnly: Logger<Dog> = (d) => console.log(d.breed);
const logAny: Logger<Animal> = logDogOnly;
// ❌ strictFunctionTypes — Dog fn can't handle all Animals
Java Explicit Wildcards
Use-site variance with ? extends (covariant) and ? super (contravariant).
class Animal { String name; }
class Dog extends Animal { String breed; }
// ? extends T = covariant read
// ? super T = contravariant writeList<Dog> dogs = List.of(new Dog());
List<? extends Animal> animals = dogs; // ✅ covariant read
Animal a = animals.get(0); // ✅
List<? super Dog> sink = new ArrayList<Animal>();
sink.add(new Dog()); // ✅ contravariant write
❌ Does Not Work
List<Animal> a2 = dogs; // ❌ invariant by default
animals.add(new Dog()); // ❌ can't write to covariant
Dog d = sink.get(0); // ❌ returns Object from contravariant
C# Declaration-site (in/out)
Variance declared on the interface, not at use-site.
// IEnumerable<out T> → covariant (producer)
// Action<in T> → contravariant (consumer)
// List<T> → invariant (both in and out)// IEnumerable<out T> — covariant
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs; // ✅
// Action<in T> — contravariant
Action<Animal> logAnimal = a => Console.WriteLine(a.Name);
Action<Dog> logDog = logAnimal; // ✅
❌ Does Not Work
List<Animal> a = new List<Dog>();
// ❌ List<T> is invariant (not IEnumerable<out T>)
// Cannot use 'out' on mutable interfaces
interface IBad<out T> { void Add(T item); }
// ❌ 'T' is covariant but used as input
Go No Variance
Go has no covariance or contravariance for generics.
type Animal interface { GetName() string }
type Dog struct { Name, Breed string }
func (d Dog) GetName() string { return d.Name }// Interfaces provide implicit "variance" via satisfaction
type Animal interface { GetName() string }
type Dog struct { Name, Breed string }
func (d Dog) GetName() string { return d.Name }
var a Animal = Dog{Name: "Rex"} // ✅
❌ Does Not Work
var dogs []Dog = []Dog{{}}
var animals []Animal = dogs
// ❌ cannot use []Dog as []Animal
// Must convert element by element
TypeScript First-class Functions
Functions are values with typed signatures. Overloads via declaration merging.
type Handler = (v: string) => void;
function greet(name: string): string;
function greet(age: number): string;type Handler = (v: string) => void;
const log: Handler = v => console.log(v); // ✅
log("hi"); // ✅
// Overloads
function greet(name: string): string;
function greet(age: number): string;
function greet(v: string | number) { return String(v); }
greet("Ana"); // ✅
greet(30); // ✅
❌ Does Not Work
log(42); // ❌ number not assignable to string
greet(true); // ❌ no overload matches
Java Functional Interfaces + True Overloads
SAM interfaces for lambdas. True method overloading at compile time.
Consumer<String> log = System.out::println;
Function<String, String> toUpper = String::toUpperCase;Consumer<String> log = v -> System.out.println(v); // ✅
log.accept("hi"); // ✅
Function<String,String> toUpper = String::toUpperCase; // ✅
// True overloads — separate implementations
String greet(String name) { return "Hi " + name; }
String greet(int age) { return "Age: " + age; }
❌ Does Not Work
log.accept(42); // ❌ int → String
var fn = (String s) -> s.length(); // ❌ can't infer type
int greet(String n) { return n.length(); }
// ❌ already defined — can't overload by return type only
C# Delegates + True Overloads
Delegate types and lambda expressions. True method overloading plus local functions.
Action<string> log = Console.WriteLine;
Func<string, string> toUpper = s => s.ToUpper();Action<string> log = v => Console.WriteLine(v); // ✅
log("hi"); // ✅
Func<string, string> toUpper = s => s.ToUpper(); // ✅
// True overloads
string Greet(string name) => $"Hi {name}";
string Greet(int age) => $"Age: {age}";
Greet("Ana"); // ✅
Greet(30); // ✅
// Local functions
void Process() {
int Square(int x) => x * x; // ✅ local function
}
❌ Does Not Work
log(42); // ❌ cannot convert int to string
int Greet(string name) => name.Length;
// ❌ already defines Greet(string)
Go First-class Functions, No Overloads
Functions are first-class values. No overloading — each name must be unique.
type Handler func(string)
var log Handler = func(v string) { fmt.Println(v) }type Handler func(string)
var log Handler = func(v string) { fmt.Println(v) } // ✅
log("hi") // ✅
// Functions as values
apply := func(fn func(int) int, v int) int { return fn(v) }
apply(func(x int) int { return x * 2 }, 5) // ✅ → 10
❌ Does Not Work
// No function overloading at all
func greet(name string) string { return name }
func greet(age int) string { return "" }
// ❌ greet redeclared in this block
log(42) // ❌ cannot use int as string
TypeScript Union Null Types
Null/undefined tracked in the type system via union types and strictNullChecks.
let name: string | null = null;
let age: number | undefined;let name: string | null = null;
if (name !== null) {
console.log(name.toUpperCase()); // ✅ narrowed
}
const city = user?.address?.city; // ✅ optional chain
const display = name ?? "Anon"; // ✅ nullish coalescing
❌ Does Not Work
console.log(name.toUpperCase());
// ❌ 'name' is possibly 'null'
function len(s: string) { return s.length; }
len(name); // ❌ string|null not assignable to string
Java Optional (no compile enforcement)
Optional wrapper class. Compiler does not prevent null dereferences.
Optional<String> opt = Optional.ofNullable(value);
String name = null; // allowed — no compile errorOptional<String> opt = Optional.ofNullable(name);
opt.ifPresent(n -> System.out.println(n.toUpperCase())); // ✅
String display = opt.orElse("Anon"); // ✅
if (name instanceof String s) {
System.out.println(s.toUpperCase()); // ✅ null-safe
}
❌ Does Not Work
String name = null;
name.toUpperCase(); // ❌ NullPointerException at runtime
Optional.of(null); // ❌ NPE — use .ofNullable()
// Compiler does NOT prevent null dereference
C# Nullable Reference Types (8.0+)
Compile-time warnings for null misuse when enabled.
#nullable enable
string? name = null; // explicitly nullable
string safe = "hello"; // non-nullable by default#nullable enable
string? name = null; // explicitly nullable
if (name is not null) {
Console.WriteLine(name.ToUpper()); // ✅ narrowed
}
string display = name ?? "Anon"; // ✅
int? age = null;
int safe = age ?? 0; // ✅ value type nullable
❌ Does Not Work
string? name = null;
Console.WriteLine(name.Length);
// ⚠️ CS8602: Dereference of a possibly null reference
string nonNull = name;
// ⚠️ CS8600: Converting null literal to non-nullable
Go Zero Values + nil
No null — uses zero values. Pointers/interfaces can be nil.
var s string // zero value: ""
var n int // zero value: 0
var p *string // zero value: nilvar name string // zero value: "" (not nil)
fmt.Println(len(name)) // ✅ → 0, no crash
// Pointers for optional values
var p *string = nil
if p != nil {
fmt.Println(*p) // ✅ safe dereference
}
❌ Does Not Work
var p *string = nil
fmt.Println(*p) // ❌ runtime panic: nil pointer dereference
var m map[string]int // nil map
m["key"] = 1 // ❌ runtime panic: assignment to nil map
// Compiler does NOT catch nil dereferences
TypeScript Union Types / const
String literal unions preferred over enum keyword. Fully type-safe.
type Status = "OPEN" | "CLOSED";
const STATUS = { OPEN: "OPEN", CLOSED: "CLOSED" } as const;type Status = "OPEN" | "CLOSED";
function close(t: { status: Status }) {
if (t.status === "OPEN") console.log("Closing"); // ✅
}
close({ status: "OPEN" }); // ✅
❌ Does Not Work
close({ status: "PENDING" });
// ❌ '"PENDING"' not assignable to '"OPEN"|"CLOSED"'
Java Full Enum Classes
Enums are full classes with methods, fields, and exhaustive switch support.
enum Status { OPEN, CLOSED }
// Enums can have fields, methods, constructorsenum Status { OPEN, CLOSED }
String label = switch (s) {
case OPEN -> "Open"; // ✅
case CLOSED -> "Closed"; // ✅
};
Status.valueOf("OPEN"); // ✅
❌ Does Not Work
close("OPEN"); // ❌ String ≠ Status
Status.valueOf("PENDING"); // ❌ IllegalArgumentException
C# Enums (Value Types)
Int-backed value types. Flags attribute for bitwise combinations.
enum Status { Open, Closed }
[Flags] enum Perms { Read = 1, Write = 2, Exec = 4 }enum Status { Open, Closed }
string Label(Status s) => s switch {
Status.Open => "Open", // ✅
Status.Closed => "Closed", // ✅
_ => "Unknown"
};
// Flags enum
[Flags] enum Perms { Read=1, Write=2, Exec=4 }
var rw = Perms.Read | Perms.Write; // ✅ bitmask
❌ Does Not Work
Status s = (Status)99; // ⚠️ compiles but invalid value!
// C# enums are backed by int — no compile-time safety
// for out-of-range casts
Go iota Constants
Typed constants with iota auto-increment. No real enum type safety.
type Status int
const (
Open Status = iota // 0
Closed // 1
)type Status int
const (
Open Status = iota // 0
Closed // 1
)
func label(s Status) string {
switch s {
case Open: return "Open" // ✅
case Closed: return "Closed" // ✅
}
return "Unknown"
}
❌ Does Not Work
label(42) // compiles ✅ → produces "Unknown"
// ❌ No restriction on arbitrary int values
// Go "enums" are just typed ints — not exhaustive
TypeScript Type Aliases
Lightweight object shapes. No runtime presence — erased to plain objects.
type User = { id: string; name: string; email: string };type User = { id: string; name: string; email: string };
const u: User = { id:"1", name:"Ana", email:"a@b" }; // ✅
const { name } = u; // ✅ destructure
const copy: User = { ...u, name: "Ana" }; // ✅ spread
❌ Does Not Work
const bad: User = { id: "1", name: "X" };
// ❌ Property 'email' is missing
console.log(typeof u); // "object" — no runtime type
Java Records (16+)
Immutable data carriers with auto-generated equals, hashCode, toString.
record User(String id, String name, String email) {}record User(String id, String name, String email) {}
var u = new User("1", "Ana", "a@b"); // ✅
u.name(); // ✅ → "Ana"
u.equals(new User("1","Ana","a@b")); // ✅ → true
❌ Does Not Work
new User("1", "Ana"); // ❌ all fields mandatory
u.name = "X"; // ❌ record components are final
C# Records (9.0+)
Immutable records with value equality, with-expressions, and deconstruction.
record User(string Id, string Name, string Email);record User(string Id, string Name, string Email);
var u = new User("1", "Ana", "a@b"); // ✅
var copy = u with { Name = "Ana" }; // ✅ non-destructive mutation
Console.WriteLine(u == copy); // ✅ → false (value equality)
// Deconstruct
var (id, name, email) = u; // ✅
❌ Does Not Work
u.Name = "X"; // ❌ init-only property
Go Structs
Plain value-type structs. No built-in immutability or generated methods.
type User struct { ID, Name, Email string }type User struct { ID, Name, Email string }
u := User{ID: "1", Name: "Ana", Email: "a@b"} // ✅
fmt.Println(u.Name) // ✅
// Structs are value types — copy by default
copy := u // ✅ deep copy
copy.Name = "Ana" // does not affect u
❌ Does Not Work
u := User{ID: "1", Name: "Ana"}
// ⚠️ compiles — Email is "" (zero value), not an error
// No way to enforce "all fields required" at compile time
TypeScript Unchecked Throw
All exceptions are unchecked. Catch type is unknown/any.
throw new Error("message");
// catch variable is unknowntry {
throw new Error("fail");
} catch (e) {
if (e instanceof Error) console.log(e.message); // ✅
}
❌ Does Not Work
try {} catch (e: Error) {}
// ❌ Catch variable must be 'any' or 'unknown'
// Compiler does NOT force try/catch
Java Checked + Unchecked Exceptions
Checked exceptions enforced by compiler. Unchecked for runtime errors.
void read() throws IOException { }
// Checked: must handle or declare
// Unchecked: RuntimeException subclassesvoid read() throws IOException { throw new IOException(); }
try { read(); }
catch (IOException e) { e.printStackTrace(); } // ✅
// Compiler FORCES handling of checked exceptions
❌ Does Not Work
void caller() { read(); }
// ❌ unreported exception IOException
C# Unchecked Exceptions
All exceptions unchecked. Exception filters with when clause.
throw new InvalidOperationException("fail");
// All exceptions unchecked — no forced handlingtry { throw new InvalidOperationException("fail"); }
catch (InvalidOperationException ex) {
Console.WriteLine(ex.Message); // ✅
}
// Exception filters
try { DoWork(); }
catch (Exception ex) when (ex.Message.Contains("timeout")) {
// ✅ only catches timeout exceptions
}
❌ Does Not Work
// No checked exceptions — compiler never forces handling
DoRiskyWork(); // compiles ✅ → may throw at runtime
// Must rely on documentation and convention
Go Error Values (no exceptions)
Errors are values, not exceptions. No try/catch.
func read() (string, error) {
return "", fmt.Errorf("fail")
}func read() (string, error) {
return "", fmt.Errorf("fail")
}
data, err := read()
if err != nil {
log.Println(err) // ✅ explicit error check
}
// errors.Is / errors.As for wrapping
if errors.Is(err, os.ErrNotExist) { } // ✅
❌ Does Not Work
data, _ := read() // compiles ✅ — silently ignores error
// ❌ Bad practice but compiler allows it
// No try/catch — cannot use Java/C# style
try { read() } // ❌ syntax error
TypeScript Types Erased
All type information removed at compile time. Runtime uses JS typeof/instanceof.
// All types removed at compile time
typeof val // JS: "string", "number", "object"
val instanceof Class // only for JS classes// User-defined type guard
function isUser(o: unknown): o is User {
return typeof o === "object" && o !== null && "name" in o;
}
if (isUser(val)) console.log(val.name); // ✅
❌ Does Not Work
if (val instanceof User) {} // ❌ 'User' is a type, not a value
typeof val === "User"; // ❌ typeof returns JS primitives
Java Full Runtime Metadata
Class metadata preserved. instanceof and reflection available. Generic erasure.
obj.getClass() // → Class<?>
obj instanceof User // pattern matching
User.class.getDeclaredFields() // reflectionif (obj instanceof User u) { u.name(); } // ✅
Class<?> c = obj.getClass();
c.getName(); // ✅ → "User"
User.class.getDeclaredConstructor().newInstance(); // ✅
❌ Does Not Work
if (obj instanceof List<String>) {} // ❌ generic erasure
C# Full Runtime Metadata (Reified)
Full runtime metadata including reified generics. Rich reflection and dynamic instantiation.
obj.GetType() // → Type
obj is User u // pattern matching
typeof(List<string>) // generic info preservedif (obj is User u) { Console.WriteLine(u.Name); } // ✅
Type t = obj.GetType();
Console.WriteLine(t.Name); // ✅ → "User"
// Generics preserved at runtime!
typeof(List<string>).GenericTypeArguments[0]; // ✅ → String
// Dynamic instantiation
Activator.CreateInstance<User>(); // ✅
❌ Does Not Work
// Unlike Java, almost everything works at runtime.
// Main limitation: anonymous types are internal
Go reflect Package
Type switches for interface dispatch. reflect package for structural inspection.
reflect.TypeOf(val) // → reflect.Type
reflect.ValueOf(val) // → reflect.Value
switch v := val.(type) {} // type switch// Type switches
switch v := obj.(type) {
case User: fmt.Println(v.Name) // ✅
}
// reflect package
t := reflect.TypeOf(User{})
fmt.Println(t.Name()) // ✅ → "User"
t.NumField() // ✅ → field count
❌ Does Not Work
// No generic type info at runtime (monomorphized)
reflect.TypeOf(Identity[int]) // ❌ doesn't carry [int]
TypeScript Decorators (Library)
Experimental decorators on classes/members. Requires reflect-metadata library.
@Log
class Service {
@Validate method() {}
}
// Requires experimentalDecorators + reflect-metadataclass UserService {
@Log getUser(id: string) { return { id }; }
}
// ✅ with decorator + reflect-metadata library
❌ Does Not Work
@Log function standalone() {} // ❌ decorators only on classes/members
Object.keys(User); // ❌ type-only — no runtime presence
Java Built-in Annotations + Reflection
First-class annotations with configurable retention. Full reflection API.
@Retention(RetentionPolicy.RUNTIME)
@interface Log {}
// Annotations inspectable via reflection API@Retention(RetentionPolicy.RUNTIME)
@interface Log {}
Method m = Svc.class.getMethod("getUser", String.class);
m.isAnnotationPresent(Log.class); // ✅
Field[] fields = User.class.getDeclaredFields(); // ✅
❌ Does Not Work
@Retention(RetentionPolicy.SOURCE) @interface X {}
m.isAnnotationPresent(X.class); // → false
// ❌ SOURCE retention discarded after compilation
C# Attributes + Rich Reflection
Compile-time attributes with runtime reflection. Source generators for code gen.
[AttributeUsage(AttributeTargets.Method)]
class LogAttribute : Attribute {}
// Attributes are compile-time constant metadata[AttributeUsage(AttributeTargets.Method)]
class LogAttribute : Attribute {}
class Svc { [Log] public void Run() {} }
var attr = typeof(Svc).GetMethod("Run")!
.GetCustomAttribute<LogAttribute>(); // ✅
// Source generators — compile-time code gen
[JsonSerializable(typeof(User))]
partial class MyContext : JsonSerializerContext {} // ✅
❌ Does Not Work
// Attributes must be compile-time constants
[Log(DateTime.Now)] // ❌ must be constant expression
Go Struct Tags + reflect
String-based struct tags parsed at runtime. No decorator/attribute system.
type User struct {
Name string `json:"name" validate:"required"`
}
// Tags are string metadata on struct fieldstype User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email"`
}
t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Name")
fmt.Println(field.Tag.Get("json")) // ✅ → "name"
❌ Does Not Work
// Tags are strings — no compile-time validation
type Bad struct {
X string `json:"x" jsson:"typo"` // compiles ✅ — no warning
}
// ❌ No decorator/attribute system — struct tags are the only option
TypeScript ES Modules
ES module syntax with type-only imports. Tree-shakeable by bundlers.
import { greet } from "./utils";
import type { User } from "./models";
export function hello() {}import { greet } from "./utils"; // ✅
import type { User } from "./models"; // ✅ type-only
export { greet } from "./utils"; // ✅ re-export
❌ Does Not Work
greet = () => "x"; // ❌ imports are read-only
import { missing } from "./utils"; // ❌ no such export
Java Packages + JPMS
Package hierarchy plus Java Platform Module System for encapsulation.
package com.example.app;
import com.example.utils.Greeter;
module com.app { requires com.lib; }import com.example.utils.Greeter; // ✅
import static com.example.utils.Greeter.greet; // ✅
module com.app { requires com.lib; exports com.api; } // ✅
❌ Does Not Work
import com.example.internal.Secret;
// ❌ not exported by module
C# Namespaces + Assemblies
Namespace organization with assembly-level access control.
namespace MyApp.Services;
using System.Text.Json;
global using System.Linq; // C# 10using System.Text.Json; // ✅ namespace import
using static Math; // ✅ static import
global using System.Linq; // ✅ file-scoped (C# 10)
using JsonAlias = System.Text.Json.JsonSerializer; // ✅ alias
❌ Does Not Work
// Cannot access internal types from another assembly
new SomeLibrary.InternalHelper();
// ❌ inaccessible due to its protection level
Go Packages + Go Modules
Directory-based packages. Case-based visibility. Strict unused import enforcement.
package main
import "fmt"
import "github.com/user/repo/pkg"import "fmt" // ✅ stdlib
import "github.com/user/repo/pkg" // ✅ external
import _ "embed" // ✅ side-effect only
// Exported = starts with uppercase
func Greet() string { return "Hi" } // ✅ public
❌ Does Not Work
import "fmt" // ❌ imported and not used (compile error!)
// Go enforces all imports must be used
pkg.helper() // ❌ lowercase = unexported/private
TypeScript Promise / async-await
Single-threaded event loop. Promises for async operations.
async function fetch(): Promise<Data> { }
const result = await fetch();
Promise.all([p1, p2]); // parallelasync function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json(); // ✅ typed Promise
}
// Promise.all for concurrency
const [a, b] = await Promise.all([fetchUser("1"), fetchUser("2")]); // ✅
❌ Does Not Work
const user: User = fetchUser("1");
// ❌ Type 'Promise<User>' not assignable to 'User'
// Must await
Java CompletableFuture / Virtual Threads
CompletableFuture for async composition. Virtual threads for lightweight concurrency.
CompletableFuture<T> future = CompletableFuture
.supplyAsync(() -> compute());
Thread.startVirtualThread(() -> { }); // Java 21+CompletableFuture<User> future = CompletableFuture
.supplyAsync(() -> fetchUser("1")); // ✅
User u = future.join(); // ✅
// Virtual threads (Java 21+)
Thread.startVirtualThread(() -> {
var user = fetchUser("1"); // ✅ blocking but lightweight
});
❌ Does Not Work
User u = CompletableFuture.supplyAsync(() -> fetchUser("1"));
// ❌ CompletableFuture<User> ≠ User — must .join() or .get()
C# Task / async-await + Channels
Task-based async model with compiler-generated state machines. Channel support.
async Task<T> FetchAsync() { }
var result = await FetchAsync();
var ch = Channel.CreateUnbounded<int>();async Task<User> FetchUser(string id) {
var res = await httpClient.GetAsync($"/api/users/{id}");
return await res.Content.ReadFromJsonAsync<User>(); // ✅
}
// Parallel
var tasks = ids.Select(FetchUser);
User[] users = await Task.WhenAll(tasks); // ✅
// Channels (like Go)
var ch = Channel.CreateUnbounded<int>();
await ch.Writer.WriteAsync(42); // ✅
❌ Does Not Work
User u = FetchUser("1");
// ❌ Cannot convert Task<User> to User — must await
async void BadMethod() { } // ⚠️ fire-and-forget — exceptions lost
Go Goroutines + Channels
Built-in concurrency primitives — no async/await needed.
ch := make(chan int)
go func() { ch <- 42 }()
result := <-chch := make(chan User)
go func() {
ch <- fetchUser("1") // ✅ send to channel
}()
user := <-ch // ✅ receive (blocks until ready)
// Select for multiplexing
select {
case u := <-ch1: fmt.Println(u) // ✅
case u := <-ch2: fmt.Println(u) // ✅
case <-time.After(5 * time.Second): fmt.Println("timeout")
}
❌ Does Not Work
ch := make(chan int)
ch <- 1 // ❌ deadlock — unbuffered channel, no receiver
// Goroutine leaks — no built-in cancellation
go func() { for { /* ... */ } }() // ⚠️ runs forever if not cancelled
TypeScript All Reference (JS)
Objects are always references. Primitives (number, string, boolean) are value types at JS level.
// Primitives: number, string, boolean (value copy)
// Objects: always reference
const obj = { x: 1 }; // reference typeconst a = { x: 1 };
const b = a;
b.x = 2;
console.log(a.x); // ✅ → 2 (same reference)
const n = 42;
let m = n;
m = 99;
console.log(n); // ✅ → 42 (primitive = value copy)
Java Primitives + Reference Types
Primitives (int, double, etc.) are values. Objects are references.
int n = 42; // primitive (stack)
Integer boxed = n; // autoboxed (heap)
User u = new User(); // reference (heap)int a = 42;
int b = a; // value copy
b = 99;
System.out.println(a); // ✅ → 42
// Objects are references
User u1 = new User("1","A","a@b");
User u2 = u1; // same ref
❌ Does Not Work
// Cannot create custom value types (until Valhalla)
// Everything non-primitive lives on the heap
C# struct (Value) vs class (Reference)
C# lets you choose value or reference semantics.
struct Point { public int X, Y; } // value type
class User { } // reference type
Span<int> s = stackalloc int[3]; // stack-onlystruct Point { public int X, Y; }
Point a = new(1, 2);
Point b = a; // ✅ full copy
b.X = 99;
Console.WriteLine(a.X); // ✅ → 1 (independent copy)
// Span<T> for stack-allocated slices
Span<int> span = stackalloc int[] { 1, 2, 3 }; // ✅ no heap
// ref struct — guaranteed stack-only
ref struct FastBuffer { public Span<byte> Data; }
❌ Does Not Work
ref struct Bad { }
Task.Run(() => { Bad b; });
// ❌ ref struct cannot be captured in lambda/async
Go Structs = Values, Pointers Explicit
All structs are value types. Use pointers for reference semantics.
type Point struct { X, Y int } // value type
var p *Point = &Point{1, 2} // pointer for ref semantics
// All structs copied by defaulttype Point struct { X, Y int }
a := Point{1, 2}
b := a // ✅ full copy
b.X = 99
fmt.Println(a.X) // ✅ → 1
// Pointer for shared reference
p := &a
p.X = 50
fmt.Println(a.X) // ✅ → 50
// Slices are reference-like (header + pointer to array)
s := []int{1, 2, 3} // ✅ backed by array on heap
❌ Does Not Work
// Large structs copied on every function call — can be expensive
func process(u User) { } // copies entire User struct
// Use *User for large structs to avoid copy overhead
TypeScript Intersection Types
Combine types with & operator. Extend interfaces with extends.
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;
const p: Person = { name: "Ana", age: 30 }; // ✅
// Extend interfaces
interface Animal { name: string; }
interface Pet extends Animal { owner: string; }
❌ Does Not Work
type Conflict = { x: string } & { x: number };
// ❌ x becomes 'never' — string & number is impossible
Java Interface extends
Interfaces extend multiple interfaces. Classes implement multiple interfaces.
interface HasName { String getName(); }
interface HasAge { int getAge(); }
interface Person extends HasName, HasAge {}interface HasName { String getName(); }
interface HasAge { int getAge(); }
interface Person extends HasName, HasAge {} // ✅
// Class implements multiple interfaces
class User implements HasName, HasAge {
public String getName() { return "Ana"; } // ✅
public int getAge() { return 30; } // ✅
}
❌ Does Not Work
class A { void run() {} }
class B { void run() {} }
class C extends A, B {} // ❌ no multiple inheritance of classes
C# Interface + Default Methods
Multiple interface inheritance with default method implementations.
interface IHasName { string Name { get; } }
interface IHasAge { int Age { get; } }
interface IPerson : IHasName, IHasAge {}interface IHasName { string Name { get; } }
interface IHasAge { int Age { get; } }
interface IPerson : IHasName, IHasAge {} // ✅
// Default interface methods (C# 8+)
interface ILogger {
void Log(string msg);
void LogError(string msg) => Log($"ERROR: {msg}"); // ✅ default
}
❌ Does Not Work
class A { } class B { }
class C : A, B { } // ❌ no multiple class inheritance
Go Embedding (Composition over Inheritance)
No inheritance. Composition via struct and interface embedding.
type Reader interface { Read(p []byte) (int, error) }
type Writer interface { Write(p []byte) (int, error) }
type ReadWriter interface { Reader; Writer }// Interface embedding
type Reader interface { Read(p []byte) (int, error) }
type Writer interface { Write(p []byte) (int, error) }
type ReadWriter interface { Reader; Writer } // ✅ composed
// Struct embedding — "inherits" methods
type Animal struct { Name string }
func (a Animal) Speak() string { return a.Name }
type Dog struct {
Animal // ✅ embedded — Dog gets Speak() for free
Breed string
}
d := Dog{Animal{"Rex"}, "Lab"}
d.Speak() // ✅ → "Rex"
❌ Does Not Work
// Ambiguous embedding — two embedded types with same method
type A struct{}; func (A) Do() {}
type B struct{}; func (B) Do() {}
type C struct { A; B }
c := C{}
c.Do() // ❌ ambiguous selector C.Do
TypeScript Conditional Type Expressions
Types that branch based on assignability checks. Distributive over unions.
type IsString<T> = T extends string ? "yes" : "no";
type NonNullable<T> = T extends null | undefined ? never : T;
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;type A = IsString<string>; // ✅ → "yes"
type B = IsString<number>; // ✅ → "no"
// infer keyword extracts types
type Unpacked<T> = T extends Promise<infer U> ? U : T;
type C = Unpacked<Promise<string>>; // ✅ → string
// Distributive over unions
type D = NonNullable<string | null | number>; // ✅ → string | number
// Flatten arrays recursively
type Flat<T> = T extends Array<infer U> ? Flat<U> : T;
type E = Flat<number[][][]>; // ✅ → number
❌ Does Not Work
type Bad = string extends T ? "y" : "n";
// ❌ T not in scope — conditional types need a generic context
type Loop<T> = T extends any ? Loop<T> : never;
// ❌ Type instantiation is excessively deep
Java No Equivalent
No type-level conditionals. Solved via overloads, instanceof, or sealed hierarchies.
// No type-level conditional — use runtime dispatch
sealed interface Result<T> {}
record Ok<T>(T value) implements Result<T> {}
record Err<T>(Exception error) implements Result<T> {}String unwrap(Result<String> r) {
return switch (r) {
case Ok<String> ok -> ok.value(); // ✅
case Err<String> e -> "error"; // ✅
};
}
❌ Does Not Work
// Cannot express: "if T extends X then Y else Z" at type level
// Must duplicate code or use runtime checks
C# No Equivalent (Constraints Only)
Generic constraints (where T : ...) limit but don't branch types.
// Constraints restrict, not branch
T Process<T>(T val) where T : IComparable<T> => val;
// No type-level conditional — use overloads
string Format(int n) => n.ToString();
string Format(string s) => s;Format(42); // ✅ → calls int overload
Format("hello"); // ✅ → calls string overload
// Static abstract interface members (C# 11) — closer to type-level dispatch
interface IAddable<T> where T : IAddable<T> {
static abstract T operator +(T a, T b);
}
❌ Does Not Work
// Cannot express: type X = T is string ? A : B
// No type-level branching or infer keyword
Go No Equivalent
No type-level conditionals. Use type switches or interface constraints.
// Constraint unions (closest to conditional)
type Number interface { int | float64 }
func Double[T Number](v T) T { return v + v }Double(42) // ✅ → 84
Double(3.14) // ✅ → 6.28
// Runtime type switch
func describe(v any) string {
switch v.(type) {
case string: return "string" // ✅
case int: return "int" // ✅
default: return "other"
}
}
❌ Does Not Work
// Cannot express type-level if/else
// Cannot infer inner types from generics
// No way to extract element type from []T at type level
TypeScript Control-flow Narrowing
Compiler tracks type through control flow. Custom type predicates.
function isString(v: unknown): v is string {
return typeof v === "string";
}
// typeof, instanceof, in, ===, custom predicatesfunction process(val: string | number | null) {
if (val === null) return; // narrowed: string | number
if (typeof val === "string") {
console.log(val.toUpperCase()); // ✅ narrowed to string
} else {
console.log(val.toFixed(2)); // ✅ narrowed to number
}
}
// "in" operator narrows
type A = { kind: "a"; x: number };
type B = { kind: "b"; y: string };
function handle(v: A | B) {
if ("x" in v) console.log(v.x); // ✅ narrowed to A
}
// Discriminated union exhaustiveness
type Shape = { kind: "circle"; r: number } | { kind: "rect"; w: number; h: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.r ** 2; // ✅
case "rect": return s.w * s.h; // ✅
// compiler warns if case missing (with never check)
}
}
❌ Does Not Work
function bad(val: string | number) {
console.log(val.toUpperCase());
// ❌ 'toUpperCase' does not exist on type 'number'
}
// Narrowing doesn't survive callbacks
function tricky(v: string | null) {
if (v !== null) {
setTimeout(() => {
v.toUpperCase(); // ⚠️ may warn — closure doesn't preserve narrowing
});
}
}
Java Pattern Matching instanceof (16+)
instanceof with binding variable. Switch pattern matching (21+).
if (obj instanceof String s) { /* s is String */ }
// switch patterns (Java 21+)
switch (obj) {
case String s -> s.length();
case Integer i -> i * 2;
}Object val = "hello";
if (val instanceof String s) {
System.out.println(s.toUpperCase()); // ✅ bound + narrowed
}
// Guarded patterns
if (val instanceof String s && s.length() > 3) {
System.out.println(s); // ✅ both narrowed and filtered
}
// Sealed type exhaustiveness (21+)
sealed interface Shape permits Circle, Rect {}
String desc(Shape s) {
return switch (s) {
case Circle c -> "r=" + c.r(); // ✅
case Rect r -> r.w() + "x" + r.h(); // ✅
}; // exhaustive — compiler checks
}
❌ Does Not Work
Object val = "hello";
val.toUpperCase(); // ❌ Object has no toUpperCase
// Must pattern match or cast first
// No flow-based narrowing like TS
if (val != null) {
val.toString(); // ✅ but val is still typed as Object
}
C# Pattern Matching Expressions
Rich pattern syntax: type, property, relational, list patterns.
if (obj is string s) { /* s is string */ }
// Property patterns
if (obj is User { Age: > 18 } adult) { }
// Switch expressions
var result = obj switch { string s => s, int i => i.ToString(), _ => "" };object val = "hello";
if (val is string s) {
Console.WriteLine(s.ToUpper()); // ✅ narrowed
}
// Relational + logical patterns
string Classify(int n) => n switch {
< 0 => "negative", // ✅
0 => "zero", // ✅
> 0 and < 100 => "small", // ✅ combined
_ => "large"
};
// List patterns (C# 11)
int[] arr = { 1, 2, 3 };
if (arr is [1, _, 3]) { } // ✅ matches first=1, any, last=3
// Nested property patterns
if (user is { Address: { City: "Paris" } }) { } // ✅ deep match
❌ Does Not Work
object val = "hello";
Console.WriteLine(val.Length);
// ❌ 'object' does not contain 'Length' — must pattern match first
Go Type Assertions & Switches
Runtime type assertions on interfaces. No compile-time narrowing.
// Type assertion
s := val.(string) // panics if wrong
s, ok := val.(string) // safe — ok is bool
// Type switch
switch v := val.(type) {
case string: // v is string
case int: // v is int
}func describe(val any) string {
switch v := val.(type) {
case string: return "string: " + v // ✅ v is string
case int: return fmt.Sprintf("int: %d", v) // ✅ v is int
case nil: return "nil" // ✅
default: return "unknown"
}
}
// Safe assertion with comma-ok
if s, ok := val.(string); ok {
fmt.Println(len(s)) // ✅ s is string
}
❌ Does Not Work
var val any = "hello"
fmt.Println(val + " world")
// ❌ operator + not defined on any
s := val.(int) // ❌ panic: interface conversion: string, not int
// No flow-based narrowing — type check doesn't narrow outside switch
if _, ok := val.(string); ok { }
// val is still 'any' here, not string
TypeScript Module Augmentation / No Operator Overloading
Extend existing types via declaration merging. No operator overloading.
// Declaration merging — extend existing interface
declare global {
interface Array<T> {
first(): T | undefined;
}
}
Array.prototype.first = function() { return this[0]; };const arr = [1, 2, 3];
arr.first(); // ✅ → 1 (prototype extension)
// Module augmentation
declare module "express" {
interface Request { userId?: string; }
}
// Now req.userId is typed ✅
❌ Does Not Work
// No operator overloading
class Vec { constructor(public x: number, public y: number) {} }
const v = new Vec(1,2) + new Vec(3,4);
// ❌ operator + cannot be applied to Vec
// Prototype pollution risk
Array.prototype.first = /* ... */; // ⚠️ affects all arrays globally
Java No Extensions / No Operator Overloading
Cannot add methods to existing types. No operator overloading.
// Static utility methods instead
class StringUtils {
static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
}StringUtils.isBlank(" "); // ✅ → true
StringUtils.isBlank("hi"); // ✅ → false
// Default methods on interfaces (Java 8+)
interface Greetable {
String name();
default String greet() { return "Hi " + name(); } // ✅
}
❌ Does Not Work
"hello".isBlank(); // ⚠️ only in Java 11+ (added to String class)
// Cannot add custom methods to String or any existing class
// No operator overloading
BigDecimal a = new BigDecimal("1.5");
BigDecimal b = a + a; // ❌ must use a.add(a)
C# Extension Methods + Operator Overloading
Add methods to any type without modifying it. Full operator overloading.
// Extension method
static class StringExt {
public static bool IsBlank(this string s) =>
string.IsNullOrWhiteSpace(s);
}
// Operator overloading
record Vec(double X, double Y) {
public static Vec operator +(Vec a, Vec b) =>
new(a.X + b.X, a.Y + b.Y);
}" ".IsBlank(); // ✅ → true (extension method)
"hi".IsBlank(); // ✅ → false
var v = new Vec(1, 2) + new Vec(3, 4); // ✅ → Vec(4, 6)
// LINQ is built entirely on extension methods
var names = users.Where(u => u.Age > 18).Select(u => u.Name); // ✅
❌ Does Not Work
// Extension methods can't access private members
static class Bad {
public static int GetSecret(this User u) => u._secret;
// ❌ '_secret' is inaccessible due to its protection level
}
// Can't override existing methods via extensions
// The real method always wins over the extension
Go Methods on Own Types / No Operator Overloading
Add methods only to types in the same package. No operator overloading.
// Methods on your own types
type MyString string
func (s MyString) IsBlank() bool {
return strings.TrimSpace(string(s)) == ""
}var s MyString = " "
s.IsBlank() // ✅ → true
// Type alias trick for extending
type Strings []string
func (ss Strings) First() string {
if len(ss) == 0 { return "" }
return ss[0]
}
Strings{"a", "b"}.First() // ✅ → "a"
❌ Does Not Work
// Cannot add methods to types from other packages
func (s string) IsBlank() bool { }
// ❌ cannot define new methods on non-local type string
// No operator overloading
type Vec struct{ X, Y float64 }
v := Vec{1,2} + Vec{3,4}
// ❌ operator + not defined on struct type Vec
TypeScript readonly & const Assertions
Compile-time immutability via readonly modifier and const assertions.
type User = { readonly name: string; readonly age: number };
const ROLES = ["admin", "user"] as const;
// typeof ROLES = readonly ["admin", "user"]const u: User = { name: "Ana", age: 30 };
console.log(u.name); // ✅
// Deep readonly via utility
type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> };
// ReadonlyArray
function process(items: ReadonlyArray<string>) {
items[0]; // ✅ read
}
// Object.freeze at runtime
const config = Object.freeze({ host: "localhost", port: 3000 });
❌ Does Not Work
u.name = "Bob";
// ❌ Cannot assign to 'name' — readonly property
const arr: ReadonlyArray<number> = [1, 2];
arr.push(3); // ❌ Property 'push' does not exist on ReadonlyArray
// ⚠️ readonly is compile-time only — no runtime enforcement
// Object.freeze is shallow
Java final + Unmodifiable Collections
final prevents reassignment. Records are shallow-immutable. Collections wrapped.
final int x = 42; // cannot reassign
record Point(int x, int y) {} // immutable components
List<String> list = List.of("a", "b"); // unmodifiablerecord Config(String host, int port) {}
var c = new Config("localhost", 3000); // ✅ immutable
c.host(); // ✅ → "localhost"
// Unmodifiable collections
var names = List.of("Ana", "Bob"); // ✅
names.get(0); // ✅ → "Ana"
// Collections.unmodifiableList wraps mutable list
var safe = Collections.unmodifiableList(mutableList); // ✅
❌ Does Not Work
final int x = 42;
x = 99; // ❌ cannot assign to final variable
names.add("C"); // ❌ UnsupportedOperationException at runtime
// ⚠️ final on reference doesn't make object immutable
final List<String> list = new ArrayList<>();
list.add("x"); // ✅ reference is final, but contents aren't
C# init-only, readonly, Records
Multiple immutability levels: init, readonly, records with non-destructive mutation.
record Config(string Host, int Port); // immutable record
readonly struct Point { public readonly int X, Y; }
class Options { public string Name { get; init; } } // init-onlyvar c = new Config("localhost", 3000); // ✅
var c2 = c with { Port = 8080 }; // ✅ non-destructive mutation
// ImmutableArray / ImmutableList
var list = ImmutableList.Create("a", "b"); // ✅
var list2 = list.Add("c"); // ✅ new list, original unchanged
// Frozen collections (NET 8)
var frozen = names.ToFrozenSet(); // ✅ optimized for reads
❌ Does Not Work
c.Host = "other";
// ❌ init-only property — cannot set after construction
var opt = new Options { Name = "X" };
opt.Name = "Y"; // ❌ init-only
Go No Built-in Immutability
No const for composite types. Use unexported fields and getter methods.
// const only for primitives
const MaxRetries = 3
// Immutability via encapsulation
type Config struct {
host string // unexported = read-only from outside
port int
}
func (c Config) Host() string { return c.host }c := Config{host: "localhost", port: 3000}
c.Host() // ✅ → "localhost" (via getter)
// Value semantics — copies are independent
c2 := c // full copy
c2.port = 8080 // doesn't affect c ✅
// Convention: return new value instead of mutating
func withPort(c Config, port int) Config {
c.port = port
return c // ✅ returns new copy
}
❌ Does Not Work
const config = Config{} // ❌ const only for basic types
// No way to make struct truly immutable at compile time
// Slices/maps are reference-like even in "immutable" struct
type Bad struct { items []string }
b := Bad{items: []string{"a"}}
copy := b
copy.items[0] = "x" // ⚠️ mutates original too!