Příprava na backendový pohovor z C#

Otázky a odpovědi na pohovor z C# 2026

Ucelená sada otázek na pohovor z C# pro backendové vývojáře. Každá odpověď obsahuje zdůvodnění, které tazatelé očekávají, a mnohé obsahují krátké ukázky kódu pro ústní diskusi.

Spustit zkušební pohovor z C#Kreditní karta není potřeba. K dispozici je 1 bezplatná lekce.
Procvičování technických pohovorů v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Typy a základy jazyka

1Jak se liší hodnotové typy (value types) od referenčních typů (reference types)?

Proměnné hodnotového typu (value type) obvykle obsahují samotnou hodnotu, takže přiřazení tuto hodnotu zkopíruje. Proměnné referenčního typu (reference type) obsahují odkaz na objekt, takže přiřazení zkopíruje tento odkaz a obě proměnné mohou sledovat změny (mutace) stejného objektu. Důležitou nuancí je, že způsob uložení závisí na životnosti (lifetime) a kontextu, nikoliv pouze na samotném typu: hodnota lokální pro danou metodu může žít na zásobníku (stack), zatímco hodnotový typ uložený jako položka třídy (class field), prvek pole, zachycená proměnná v uzávěru (closure capture), zabalená hodnota (boxed value) nebo položka stavového automatu async žije spolu s tímto objektem.

int a = 10;
int b = a;
b = 20;

// a is still 10

var first = new User { Name = "Ann" };
var second = first;
second.Name = "Kate";

// first.Name is also "Kate"
Zkusit odpovědět na tuto otázku s trenérem AI

2Jaký je rozdíl mezi `class`, `struct` a `record`?

Třída (`class`) je referenční typ s identitou objektu. Struktura (`struct`) je hodnotový typ a je nejvhodnější pro malé neměnné hodnoty, jako jsou souřadnice, rozsahy nebo identifikátory. Záznamová třída (`record class`) je referenční typ s rovností založenou na hodnotách; záznamová struktura (`record struct`) je její hodnotová verze. Záznamy (`record`) jsou pouze syntaktický cukr kompilátoru nad běžnými typy CLR (Common Language Runtime), který generuje kontrolu rovnosti, metody `GetHashCode`, `ToString`, podporu pro dekonstrukci a kopírování, včetně výrazů `with` pro nedestruktivní modifikaci. V backendovém kódu jsou záznamy užitečné pro modely typu DTO (Data Transfer Object), zatímco velké měnitelné struktury jsou riskantní, protože jejich kopírování a modifikace kopií mohou čtenáře kódu překvapit.

public record User(int Id, string Name);

var first = new User(1, "Alice");
var second = new User(1, "Alice");

Console.WriteLine(first == second); // true
Zkusit odpovědět na tuto otázku s trenérem AI

3Jak se liší parametry `ref`, `out` a `in`?

Parametr `ref` předává argument odkazem (by reference) a vyžaduje, aby byl inicializován před samotným voláním; metoda jej může číst i měnit. Parametr `out` se také předává odkazem, ale metoda mu musí přiřadit hodnotu před svým návratem. Parametr `in` předává argument jako odkaz určený pouze pro čtení (readonly byref), což může zabránit kopírování u velkých struktur, ale nejedná se o záruku hluboké neměnnosti: měnitelné struktury a členy, které nejsou označeny jako `readonly`, si mohou vynutit vytváření defenzivních kopií. Na úrovni jazyka IL (Intermediate Language) jsou `ref`, `out` i `in` spravované parametry předávané odkazem (byref), nad kterými C# uplatňuje odlišná pravidla.

void Increment(ref int value) => value++;

bool TryRead(string text, out int value) =>
    int.TryParse(text, out value);

double Length(in Vector vector) =>
    Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y);
Zkusit odpovědět na tuto otázku s trenérem AI

4Co je to zabalení (boxing) a rozbalení (unboxing)?

Zabalení (boxing) obalí hodnotový typ do objektu referenčního typu tím, že alokuje objekt a zkopíruje do něj danou hodnotu. Rozbalení (unboxing) extrahuje hodnotu zpět a vyžaduje přesně kompatibilní typ. Časté provádění boxingu přidává alokace a zvyšuje zátěž pro GC (Garbage Collector). Generika se vyhýbají mnoha případům boxingu, protože například `List<int>` ukládá hodnoty typu `int`, aniž by každou hodnotu převáděl na `object`.

int number = 42;
object boxed = number;      // boxing
int restored = (int)boxed;  // unboxing

object value = 42;
// long wrong = (long)value; // InvalidCastException
long ok = (long)(int)value;
Zkusit odpovědět na tuto otázku s trenérem AI

5Co jsou hodnotové typy umožňující null (nullable value types) a referenční typy umožňující null (nullable reference types)?

Hodnotové typy umožňující null využívají `Nullable<T>` nebo `T?` a představují skutečný obal typu za běhu programu (runtime wrapper) pro hodnotové typy. Referenční typy umožňující null slouží převážně pro analýzu kompilátoru: zápis `string?` říká kompilátoru, že reference může být null, ale nevytváří nový typ za běhu. Operátor `!` (null-forgiving) pouze potlačuje varování kompilátoru; nechrání před výjimkou `NullReferenceException`.

int? age = null;

string name = "Alice";
string? optionalName = null;

var length = optionalName?.Length;
var actualName = optionalName ?? "Unknown";
optionalName ??= "Default";

string trusted = optionalName!;
Zkusit odpovědět na tuto otázku s trenérem AI

6Jaký je rozdíl mezi `const`, `readonly` a `static readonly`?

Klíčové slovo `const` představuje konstantní hodnotu v době překladu (compile-time constant), která je omezena na primitivní typy, hodnoty `enum`, `string` nebo `null`. Její hodnota je během kompilace dosazena přímo do sestavení (assemblies), která ji využívají. Modifikátor `readonly` označuje instanční proměnnou, které lze přiřadit hodnotu pouze při deklaraci nebo v konstruktoru. Modifikátor `static readonly` představuje jednu proměnnou pro celý typ, které je hodnota přiřazena při deklaraci nebo ve statickém konstruktoru. Veřejné hodnoty, které se mohou měnit napříč verzemi knihovny, jsou bezpečnější jako veřejné `static readonly` než veřejné `const`, protože klienti si mohou ponechat starou vloženou (inlined) hodnotu `const`, dokud nejsou znovu zkompilováni.

public const int MaxAttempts = 3;

private readonly Guid _id = Guid.NewGuid();

public static readonly TimeSpan Timeout =
    TimeSpan.FromSeconds(30);
Zkusit odpovědět na tuto otázku s trenérem AI

7Jak spolu fungují `Equals`, `==` a `GetHashCode`?

U běžných tříd `Equals` a `==` ve výchozím nastavení porovnávají reference. Mnoho vestavěných typů a záznamů (`record`) porovnává hodnoty. Pokud je metoda `Equals` přepsána, musí s ní být `GetHashCode` konzistentní: pokud je `a.Equals(b)` pravda, oba objekty musí mít stejný hash kód. Opačně to zaručeno není. Nikdy neměňte pole, která se používají pro porovnávání rovnosti nebo výpočet hashe, dokud je objekt klíčem v `Dictionary` nebo prvkem v `HashSet`.

public sealed class User : IEquatable<User>
{
    public required int Id { get; init; }

    public bool Equals(User? other) =>
        other is not null && Id == other.Id;

    public override bool Equals(object? obj) =>
        obj is User other && Equals(other);

    public override int GetHashCode() =>
        Id.GetHashCode();
}
Zkusit odpovědět na tuto otázku s trenérem AI

8Jak se override liší od new?

override mění chování virtuální metody, takže volaná metoda závisí na typu objektu za běhu (runtime). new skrývá člen ze základní třídy a vybraný člen závisí na statickém typu proměnné. Pro polymorfismus obvykle chcete použít virtual a override; skrývání pomocí new je méně běžné a může způsobit nepřehledné chování.

class Base
{
    public virtual void First() => Console.WriteLine("Base.First");
    public void Second() => Console.WriteLine("Base.Second");
}

class Derived : Base
{
    public override void First() => Console.WriteLine("Derived.First");
    public new void Second() => Console.WriteLine("Derived.Second");
}

Base value = new Derived();
value.First();  // Derived.First
value.Second(); // Base.Second
Zkusit odpovědět na tuto otázku s trenérem AI

9Jak se liší abstraktní třída od rozhraní?

Abstraktní třída může obsahovat stav, konstruktory, členské proměnné (fields), implementované metody, abstraktní metody a chráněné členy. Rozhraní popisuje kontrakt; moderní rozhraní mohou obsahovat výchozí implementace a statické abstraktní členy, ale stále to není obvyklé místo pro stav instance. Třída může dědit z jedné základní třídy, ale může implementovat více rozhraní. Abstraktní třídu použijte pro sdílený základ a sdílenou implementaci; rozhraní použijte pro schopnosti (capabilities) a vymezení hranic komponent.

public interface IAsyncRepository<T>
{
    Task<T?> FindAsync(
        int id,
        CancellationToken cancellationToken);
}
Zkusit odpovědět na tuto otázku s trenérem AI

Generické typy, kolekce a LINQ

10Co jsou to generika (generics) a proč jsou užitečná?

Generika (generics) umožňují psát znovu použitelný a typově bezpečný kód. Přesouvají mnoho kontrol do doby překladu (compile time), odstraňují nutnost ručního přetypování a dokážou zabránit zabalování (boxing) u hodnotových typů. Generická omezení (generic constraints) definují, co musí typový parametr podporovat, například že se jedná o třídu (class), strukturu (struct), implementaci rozhraní, základní třídu, neřízený typ (unmanaged type) nebo že má veřejný konstruktor bez parametrů.

public T? Find<T>(
    IEnumerable<T> items,
    Predicate<T> predicate)
{
    return items.FirstOrDefault(item => predicate(item));
}

public T Create<T>() where T : class, new()
{
    return new T();
}
Zkusit odpovědět na tuto otázku s trenérem AI

11Co je to kovariance a kontravariance?

Kovariance umožňuje kódu použít specifičtější typ tam, kde se očekává obecnější vyprodukovaný typ, například `IEnumerable<string>` místo `IEnumerable<object>`. V rozhraních a delegátech se označuje klíčovým slovem `out`. Kontravariance funguje opačným směrem pro konzumované hodnoty a označuje se pomocí `in`. Variancí (variance) se lze řídit pouze u rozhraní a delegátů pracujících s referenčními typy; `IEnumerable<int>` nelze převést na `IEnumerable<object>`, protože by to vyžadovalo zabalení (boxing), a `List<string>` nelze přiřadit do `List<object>`.

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // covariance

Action<object> printObject = Console.WriteLine;
Action<string> printString = printObject; // contravariance

interface IProducer<out T> { T Produce(); }
interface IConsumer<in T> { void Consume(T value); }
Zkusit odpovědět na tuto otázku s trenérem AI

12Jak se liší `IEnumerable<T>` od `IQueryable<T>`?

`IEnumerable<T>` představuje iteraci v .NET a operátory LINQ (Language Integrated Query) obvykle přijímají delegáty, jako je `Func<T, bool>`. `IQueryable<T>` uchovává stromy výrazů (expression trees), například `Expression<Func<T, bool>>`, které může poskytovatel, jako je Entity Framework Core, přeložit do SQL (Structured Query Language). Předčasné volání `ToList` materializuje data a přesouvá následné filtrování do paměti. Navíc ne každý výraz v C# může být poskytovatelem dotazu přeložen. Vyhněte se prosakování `IQueryable` přes všechny aplikační vrstvy, pokud to není záměrné architektonické rozhodnutí.

IEnumerable<User> loaded = loadedUsers;
var inMemoryAdults = loaded.Where(x => x.Age >= 18);

IQueryable<User> query = dbContext.Users;
var adults = await query
    .Where(x => x.Age >= 18)
    .ToListAsync();
Zkusit odpovědět na tuto otázku s trenérem AI

13Co je odložené spuštění (deferred execution) v LINQ (Language Integrated Query)?

Mnoho operátorů LINQ sestavuje dotaz, aniž by jej okamžitě vykonalo. Vykonávání začíná až ve chvíli, kdy je dotaz iterován, nebo když je zavolána koncová (terminální) operace, jako je `ToList`, `Count` nebo `First`. To znamená, že změny ve zdroji před samotnou iterací mohou ovlivnit výsledky, opakovaná iterace může vést k opakovanému provádění výpočtů nebo SQL (Structured Query Language) dotazů a výjimky se mohou objevit až během iterování namísto fáze sestavování dotazu.

var query = numbers.Where(x => x > 10);

numbers.Add(42);

foreach (var number in query)
{
    Console.WriteLine(number);
}

var materialized = query.ToList();
Zkusit odpovědět na tuto otázku s trenérem AI

14Jak se od sebe liší pole, List<T>, Dictionary<TKey,TValue> a HashSet<T>?

Pole má pevnou velikost a rychlý indexovaný přístup. List<T> je dynamické pole s indexovaným přístupem v čase O(1); metoda Add na konec má amortizovanou časovou složitost O(1), protože při zvětšování kapacity se alokuje větší pole a existující prvky se do něj zkopírují, zatímco vkládání doprostřed má složitost O(n). Dictionary<TKey,TValue> mapuje klíče na hodnoty a obvykle poskytuje vyhledávání podle klíče v čase O(1). HashSet<T> ukládá unikátní prvky a je ideální pro kontrolu členství. U kolekcí založených na hashi jsou zásadní správné implementace metod Equals a GetHashCode.

var ids = new HashSet<int> { 1, 2, 3 };

if (ids.Contains(userId))
{
    // Fast membership check
}
Zkusit odpovědět na tuto otázku s trenérem AI

15Jaký je rozdíl mezi IEnumerable<T>, ICollection<T>, IList<T> a IReadOnlyCollection<T>?

Rozhraní IEnumerable<T> slibuje pouze sekvenční procházení. Rozhraní ICollection<T> přidává vlastnost Count a metody pro mutaci, jako jsou Add a Remove. Rozhraní IList<T> navíc umožňuje indexovaný přístup. Rozhraní IReadOnlyCollection<T> zpřístupňuje procházení a vlastnost Count, ale neposkytuje přes toto rozhraní možnosti mutace. Při návrhu rozhraní (API - Application Programming Interface) by se měl vracet ten nejužší užitečný kontrakt, ale rozhraní určená pouze pro čtení (read-only) nezaručují hlubokou neměnnost (deep immutability) podkladového objektu.

private readonly List<Order> _orders = new();

public IReadOnlyCollection<Order> Orders => _orders;
Zkusit odpovědět na tuto otázku s trenérem AI

16Jak funguje yield return?

`yield return` vytváří iterátor s odloženým spuštěním (deferred execution). Kompilátor transformuje metodu na stavový automat (state machine) a hodnoty jsou produkovány jedna po druhé při volání `MoveNext`. Tím se zamezí vytváření zprostředkujících kolekcí, což je velmi vhodné pro rozsáhlé sekvence. Nástrahy spočívají v životnosti a vícenásobné enumeraci: nevracejte línou (lazy) sekvenci, která závisí na uvolněném (disposed) `DbContext` nebo proudu (stream), a pamatujte, že volání `Count` následované `foreach` může spustit iterátor dvakrát, čímž dojde k opakování vedlejších efektů nebo I/O (Input/Output) operací.

IEnumerable<int> GetEvenNumbers(IEnumerable<int> source)
{
    foreach (var number in source)
    {
        if (number % 2 == 0)
            yield return number;
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

Delegáti, události a výjimky

17Co jsou delegáty (delegates) a lambda výrazy?

Delegát je typově bezpečná reference na metodu. Lambda výrazy poskytují stručné inline funkce a běžně se přiřazují k delegátům `Func`, `Action` nebo `Predicate`. Lambda výrazy zachycují (capture) proměnné, nikoliv snímky jejich hodnot, takže lambda může pozorovat pozdější stav cyklu nebo hodnotu proměnné. Pokud lambda nezachytává nic, kompilátor může cachovat statický delegát a vyhnout se alokaci uzávěru (closure); v kódu, který je kritický z hlediska výkonu, může záležet na uzávěrech a prodloužené životnosti objektů.

public delegate int Operation(int x, int y);

Operation operation = (x, y) => x + y;
Console.WriteLine(operation(2, 3));

int factor = 10;
Func<int, int> multiply = x => x * factor;
Zkusit odpovědět na tuto otázku s trenérem AI

18Jak se liší událost (`event`) od delegátu (`delegate`)?

Událost (`event`) je obvykle založena na delegátu, ale omezuje to, co může provádět vnější kód. Externí kód se může přihlásit a odhlásit k odběru, ale událost může vyvolat pouze vlastnící třída. Veřejné pole typu delegát by volajícím umožnilo nahradit seznam volání (invocation list) nebo jej přímo zavolat. Události také vytvářejí reference od vydavatele k odběrateli, takže dlouho žijící vydavatelé mohou způsobit úniky paměti (memory leaks), pokud se odběratelé z odběru neodhlásí.

public event EventHandler<OrderEventArgs>? OrderCreated;

service.OrderCreated += Handler;
service.OrderCreated -= Handler;

OrderCreated?.Invoke(this, args);
Zkusit odpovědět na tuto otázku s trenérem AI

19Jak by měly být zpracovávány výjimky v C#?

Používejte výjimky pro výjimečné situace, nikoli pro běžné řízení toku programu. Zachytávejte pouze výjimky, které dokážete zpracovat, vyhněte se prázdným blokům `catch`, při obalování výjimek přidejte kontext a neprozrazujte interní detaily uživatelům. K opětovnému vyvolání použijte `throw;`, čímž zachováte původní zásobník volání (stack trace). Pokud výjimku obalujete, předejte původní výjimku jako `InnerException`, například `throw new DomainException("...", ex)`. Pro očekávaná selhání upřednostňujte API ve stylu `TryParse`.

try
{
    await service.ProcessAsync();
}
catch (ValidationException exception)
{
    // Expected domain error
}
finally
{
    // Cleanup
}

catch
{
    throw; // preserves stack trace
}
Zkusit odpovědět na tuto otázku s trenérem AI

20Co jsou to filtry výjimek (exception filters)?

Filtry výjimek umožňují, aby se blok `catch` spustil pouze tehdy, když je splněna dodatečná podmínka. Filtr se vyhodnocuje před rozvinutím zásobníku (stack unwinding) a před vstupem do těla bloku `catch`, takže když vrátí `false`, původní zásobník zůstane nedotčený pro diagnostiku a výpisy paměti při pádu (crash dumps). Filtry jsou užitečné pro stavové kódy HTTP, chybové kódy databáze, rozlišení přechodných (transient) chyb a podmíněné logování. Udržujte filtry jednoduché a vyhněte se složitým vedlejším efektům.

try
{
    await SendAsync();
}
catch (HttpRequestException ex)
    when (ex.StatusCode == HttpStatusCode.NotFound)
{
    // Handle only 404
}
Zkusit odpovědět na tuto otázku s trenérem AI