Types · OOP · LINQ · Async · JVM
A technical primer for backend fundamentals
Value types, reference types, nullability, IDisposable
Properties, inheritance, interfaces, abstract classes
List, Dictionary, LINQ pipelines, delegates, lambdas
Async/await, exceptions, records, pattern matching
JVM, types, OOP, collections, streams, generics
Runtime, generics, async, properties — side by side
Quiz, concept match, language classifier
3 hands-on exercises — open on your own laptop, write code, run the tests
Open coding.htmlTypes · OOP · LINQ · Async · Modern Features
int, double, bool, char, struct, enum. Stored where declared — stack for locals, inline in arrays. Assignment creates an independent copy. Changing one copy does not change another.
class, string, array, interface. Variable holds a reference to a heap object. Assignment copies the reference — both variables point to the same object.
int? is shorthand for Nullable<int>. Lets a value type represent the absence of a value. Check with .HasValue or use the null-coalescing operator ??.
String is a reference type that behaves like a value type because it never changes after creation. All "modifications" create a new string. Use StringBuilder in loops.
The CLR GC automatically reclaims heap memory when objects have no more references. You never call delete or free. GC runs on a background thread with generational collection.
For unmanaged resources: files, sockets, DB connections. Implement IDisposable and provide a Dispose method. The GC handles memory; Dispose handles everything else.
using var x = ... calls x.Dispose() automatically when the variable goes out of scope, even if an exception is thrown. The C# equivalent of RAII.
struct = value type, stack or inline, copied by value, no inheritance, no GC. class = reference type, heap, reference semantics, supports inheritance. Use struct for small data-only types.
public string Name { get; set; } — the compiler generates the private backing field. No separate field declaration needed. The cleanest way to expose data.
Write explicit get and set bodies when you need logic — validation in the setter, computed values in the getter. Validation runs transparently on every assignment.
public string Name { get; init; } can be set during object initialisation but never after. Perfect for creating immutable or semi-immutable objects without constructors.
=> syntax for single-expression properties and methods. Cleaner than a full block for simple computed values.
Bundle data and methods. Use private, public, protected, internal. Properties expose data cleanly with validation built in.
class Lab : Dog. Single base class. Call base constructor with base(). Use override and virtual. Use sealed to prevent further subclassing.
Mark base method virtual. Derived uses override. The compiler enforces correct signatures. Abstract forces derived classes to implement.
interface defines a contract. A class can implement multiple interfaces. abstract class provides partial implementation with shared state.
Defines what, not how. No fields, no constructors. A class can implement multiple interfaces. The foundation of testable, loosely-coupled code in C#.
Can have fields, constructors, concrete methods, and abstract methods. Use when subclasses share real implementation. Cannot be instantiated directly.
sealed class cannot be inherited from. sealed override prevents further overriding. Use when a class is a concrete final implementation.
Accept an interface in the constructor instead of a concrete class. Enables swapping real implementations for test fakes. Standard practice in all C# backend frameworks.
Unlike Java, C# generics retain full type information at runtime. typeof(T) works inside generic methods. The JIT generates specialised code per value type — real performance benefit.
where T : class — reference type only.where T : struct — value type only.where T : new() — must have parameterless constructor.where T : IComparable — must implement interface.
List<T> — dynamic array. Dictionary<K,V> — hash map. HashSet<T> — unique elements. Queue<T> and Stack<T> for ordered access.
The base interface for all sequences. Anything that implements it gets all LINQ operators. Your own types can implement it to participate in LINQ pipelines.
A set of extension methods on IEnumerable that brings SQL-style querying to any collection in C#. The C# equivalent of Java Streams — same concepts, different syntax.
Chain extension methods: Where (filter), Select (map/transform), OrderBy (sort), GroupBy, Aggregate. Returns IEnumerable until a terminal is called.
from x in list where x.Age > 18 select x.Name — compiles to the same method calls. Some developers prefer it for complex multi-collection queries.
LINQ queries are lazy — they do not execute until you call a terminal operator like ToList(), First(), Count(), or iterate with foreach.
A type that represents a method signature. Variables of delegate type can hold any method with matching parameters and return type. The foundation of events and LINQ.
Func<T,R> — takes T, returns R. Action<T> — takes T, returns void. Predicate<T> — takes T, returns bool. Cover 95% of use cases without defining custom delegates.
x => x * 2 single expression. (x, y) => x + y multiple params. x => { ... } statement body. Closures capture outer variables by reference.
Delegate fields declared with event. Subscribers add handlers with +=, remove with -=. Only the declaring class can invoke. Observer pattern built into the language.
A thread blocked on a database query is wasted. With async/await, the thread is returned to the thread pool during I/O and resumed when the response arrives. Scales backend services dramatically.
Task represents an ongoing operation. Task<T> represents a future value of type T. Equivalent to JavaScript's Promise or Java's CompletableFuture.
Suspends the current method and returns the thread to the caller. Resumes automatically when the awaited Task completes. Does not block any thread.
Calling these in a synchronous context causes deadlocks in ASP.NET. Always use await. Never use async void — exceptions become unobservable. Use async Task.
catch (Exception e) when (e.Message.Contains("timeout")) — the catch only activates if the condition is true. Stack does not unwind if filter fails — more efficient than catch-and-rethrow.
Bare throw inside catch rethrows and preserves the original stack trace. throw ex resets the stack trace to the current line — you lose where the exception originated. Always use bare throw.
Always runs — normal exit, exception, or return. With using handling resource cleanup in most cases, you need finally mainly for state resets that are not resource-related.
Inherit from Exception. Pass a clear message to base(message). Add extra properties for context. Use specific types rather than raw Exception in catch blocks.
A class where equality is by content, not reference. Automatically generates ToString, Equals, GetHashCode, and a copy constructor. Perfect for DTOs and immutable data.
var b = a with { Name = "Bob" } creates a copy of a with only the specified properties changed. All other properties are copied. The standard pattern for immutable updates.
if (obj is string s) — checks type AND binds the variable in one step. if (n is > 0 and < 100) — relational patterns. Cleaner and safer than explicit casting.
Returns a value based on pattern matching. Compiler warns if cases are not exhaustive. Cleaner than switch statement for transformations.
JVM · OOP · Collections · Streams
Bytecode is OS-agnostic. Same dot class file runs on Windows, Linux, macOS, Android. No recompilation per platform — the JVM handles the differences.
Both generational, both low-pause. CLR advantage: value types avoid GC pressure — an array of int in C# stores ints directly. In Java every element in a List of Integer is a boxed heap object.
Lightweight JVM-managed threads. Millions can exist concurrently. Makes blocking code scale like async code without the async syntax overhead. Game changer for backend services.
One parent class with extends. Multiple interfaces with implements. @Override annotation enforces correct overriding at compile time — same role as C#'s override keyword.
Java has no property syntax. Encapsulation uses private fields with public getName() and setName() methods. More verbose than C# properties but the same concept.
final class cannot be subclassed. final method cannot be overridden. final field cannot be reassigned after init. Equivalent to C#'s sealed and readonly.
Same concept as C# records — immutable data carriers with value-based equality, auto-generated equals, hashCode, toString. Both languages solved the same problem identically.
Added in Java 8. Same lazy pipeline: filter, map, reduce, collect. Concepts identical to LINQ, syntax different. Java Streams, C# LINQ — learn one and the other makes immediate sense.
Unlike C#, Java generics erase type info at runtime. List<String> becomes List in bytecode. Cannot do new T() or instanceof T. Affects performance for value types — requires boxing.
Optional<T> is Java's answer to nullable reference types. Wraps a value that might be absent. Use orElse, map, ifPresent — never call get() without checking.
Collectors.toList(), groupingBy(), joining() — the Java equivalent of LINQ's ToList, GroupBy, and string joins. Used as the terminal argument to collect().
| Java | C# Equivalent | Access | Ordered? | Notes |
|---|---|---|---|---|
| ArrayList<T> | List<T> | O(1) | Insertion | Dynamic array — default sequence choice |
| LinkedList<T> | LinkedList<T> | O(n) | Insertion | Fast insert/remove at known positions |
| HashMap<K,V> | Dictionary<K,V> | O(1) avg | No | Hash map — fastest key-value lookup |
| TreeMap<K,V> | SortedDictionary<K,V> | O(log n) | Sorted | Keeps keys in sorted order |
| HashSet<T> | HashSet<T> | O(1) avg | No | Unique elements, fast membership test |
| PriorityQueue<T> | PriorityQueue<T,P> | O(1) top | Heap | Always retrieve smallest/largest first |
C# has value types and reference types — structs avoid heap allocation and GC pressure entirely
Properties give C# clean encapsulation with get/set syntax — more concise than Java's explicit methods
LINQ brings SQL-style querying to any collection — the same pipeline model as Java Streams but with C# syntax
async/await lets I/O-bound code scale without blocking threads — essential for backend services
C# generics preserve runtime type information — no type erasure unlike Java generics
C# and Java are closer to each other than any other major language pair — learn one and the other is very accessible
NEXT STEPS
Quiz · Concept Match · Language Sort · coding.html
8 questions · C# and Java fundamentals
Click a term, then click its matching definition · Find all 8 pairs