← Home
.NETOOP and C# basicsReported by candidates

Explain the four pillars of OOP, with an example of each from your project.

The most-asked CS question in the index: 74 reports, from Infosys, Deloitte, EPAM, Cognizant, Capgemini, HCLTech and Luxoft.

Say this first · about 30 seconds
Make it yours · BMW FS go-live checks (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next to walk deal D-1042, an Operating Lease, through its check.

BMW.sln · where these 4 files sit
  • Checks/
Diagram · step 1Deal D-1042 is an Operating Lease, so builds an .
GoLiveService.cs · line 3 · 1 of 5Starts the check. The deals controller calls it with deal D-1042, loaded from the database: Operating Lease, 36 months, no return conditions.

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • OOP builds code from : each object holds its data and the methods that keep that data right. I'll show all four on BMW FS , where a and a are both a .
  • Abstraction means showing what an object can do and hiding how. For example, our calls on any contract and never sees how each kind works it out.
  • Encapsulation means an object guards its own data. For example, has a private setter, so the only way to change it is , which refuses a negative amount.
  • Inheritance means a class reuses another class's code and adds its own. For example, and inherit and from , and adds a service fee.
  • Polymorphism means one call runs different code depending on the real object. For example, the loops a list of contracts and calls : a loan returns its monthly amount, a lease adds its fee.
  • What I add: I keep inheritance short, a parent and its children, and mark classes so nobody inherits from them by accident. When only one behaviour differs, I pass in a small object behind an interface instead of making a subclass.
Check your answer: tick what you said

You said 0 of 8

Then add your example: BMW FS go-live checks (invented example)
  • Situation: at BMW FS a deal must pass checks before it goes live. One rule applies to every product: the term must be above 0 months. Each product also has its own rule: an Operating Lease, where the car goes back at the end, must state its return conditions.
  • What I did: one abstract class, , with one child class per product. The shared rule is written once in (inheritance). The problems sit in a private list that outside code can only read (encapsulation). Each child overrides with its own rule (polymorphism).
  • Result: picks the class with one if, then uses only and its method (abstraction). is , so no child can replace it for callers using the type, and none can skip the shared rule.
  • What I'd say: all four pillars in one small feature. A base method that always runs the shared rule, then calls the step each child writes, is the template method pattern.

See it

1/10Before: one class for every kind. A Kind text says if it is a loan or a lease.

In plain words

The senior answer · about 2 minutes
The four, in my words
  • Abstraction: the caller only knows what an object can do, not how it does it. Our just calls on each contract and asks an , the class that loads contracts from the database, for the list. It doesn't know the pricing rules or which database sits behind it.
  • Encapsulation: the object protects its own data. has a , so outside code can read it but not set it. The only way to change it is , which checks the amount first. And the throws if the balance is zero or negative, so a contract can never even be created in a broken state.
  • Inheritance: I use it only when the child really is a kind of the parent and they share fields and methods. A lease really is a contract: it has a and takes payments the same way, so it gets those from instead of copying them.
  • Polymorphism: the loops a mixed list and calls the same method on every item, and each object runs its own version. So adding a new kind of contract means adding a class, not adding another if somewhere.
Where inheritance hurts
  • When you change the parent, every child changes with it, even the ones you never looked at. That's called the fragile base class problem.
  • When a child can't really do what the parent promises. We had a car subscription paid by card, with no balance. Someone made it inherit , its threw an error, and the nightly payment job stopped. That breaks Liskov's rule: a child must work anywhere the parent works. Full story under Production bugs.
  • When the chain gets deep, a child of a child of a child, nobody can tell which version of a method actually runs.
How I choose
  • First I ask: is it really a kind of this, and can it keep every promise the parent makes? If yes, and they share data, I use an , a parent you can't build on its own, like . That's is-a.
  • If they only share an ability, like being billed, I use an , like .
  • If one behaviour changes on its own, like late fees that differ per client, I use , which is has-a: the contract holds a late-fee rule and calls it, so I don't need a subclass for every kind and client pair. The composition page draws it step by step.
  • By default I mark classes , so nobody can inherit from them by accident, and I keep inheritance to two levels: a parent and its children.
In production
  • Encapsulation matters in production, not just in theory. A public list once let a preview screen put a fake late fee on a real bill (see Production bugs).
  • I also keep one test that loops every kind of contract through a variable and checks each answer. We added it after the BMW FS incident further down: a child class wrote on a method, so its version was skipped. That test would have caught it before release.

All four in one day

One day at BMW FS, in time order. Each step names the pillar doing the work.

1/7Abstraction. 02:00: the gets its due contracts. It only sees .

The story

  1. Before

    Before the classes, every BMW FS contract was one class with a Kind text, and each feature asked it with a switch (a block that picks code by a value). This is how that breaks when a new kind joins.

    1/5A switch picks code by a value. Old billing worked out each bill with one, on the Kind text.

  2. Why it had to change

    A rule copied into many switch blocks gets missed in one of them. Nothing fails at build time, and nobody notices until a customer's bill and statement disagree.

  3. How it works now

    Each kind becomes a class that overrides . The billing loop is foreach (Contract c in due) c.AmountDue();. .NET picks the code from the object, while the app runs. Step by step (the is on its page):

    1/5At build time the compiler sees only Contract c. Contract has , so the line builds.

  4. What happens next

    In the real API the classes sit at the bottom of every request. The one place the kind is still checked is where a database row becomes an object. EF Core is told once, at start-up, that Kind "Loan" means a and "Lease" a ; then it builds the right class for every row.

    1/6A payment arrives as an HTTP request. The controller reads it and hands it to the service.

  5. What we gained

    One place per kind's rules. guarded by the class that owns it. A new kind is one new class plus its tests. And a fake contract in a test is one line.

In the lending API

The shared part every contract inherits. The comments mark the pillars; polymorphism happens when the calls , as in the diagram.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Domain/Contracts/
⋯ 5 lines in Deep⋯ 16 lines in Deep⋯ 2 lines in Deep
Contract.cs · line 4 · 1 of 4: nobody can write new Contract(...). Every real contract is a Loan or a Lease, so a plain Contract would be a bill with no rule. .

The code, file by file

The contracts in the lending API: one abstract base, two kinds, the that uses them, and where a payment enters from HTTP. Tap Next to walk the diagram with the code.

Lending.sln · where these 6 files sit
  • src/
    • Domain/Contracts/
    • Application/
      • Billing/
      • Contracts/
    • Api/Controllers/
Contract.cs · line 1 · 1 of 9The folder for business rules. These classes know nothing about HTTP or the database, so a test can build them in one line.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: Lease bills sent without the service fee

P2 · serious, has a workaroundBMW FScame with a deployBilling run in the servicing API

Composed, not a real event at BMW FS. The mechanism is real C#: `new` on a method hides the parent's version, so a call through the parent type runs the parent's code. The counts and amounts are composed. This is the earlier design: later `AmountDue()` became `abstract`, as on the card.

The story in 5 lines
  1. On 1 Oct the made 4,200 lease bills, each 60 short: the monthly fee was missing. BMW FS's billing lead spotted it in the daily income report.
  2. No errors anywhere. Every bill was a believable number, just too low.
  3. Release 7.3 added a to , so the could loop one list of contracts. already had its own ; the build warned, and was added to clear the warning.
  4. hides instead of replacing, and the calls through , so every lease ran Contract's version.
  5. Fix: , a test that bills every kind through , and a review rule that on a method needs a written reason.
Who's who (4 people)
You
.NET developer on the vendor team. Finds the cause, writes the fix.
Tech lead
Vendor senior. Reviews every change, runs the postmortem.
L2 pod
Vendor support. First look, runs the log queries.
BMW FS billing lead
Owns customer bills. Decides how the missed fees are collected.

Tap a step. The map lights the place where it happens.

1/4 Detect

Detect: 4,200 lease bills, all 60 short. Nothing threw; the average lease bill fell from 460 to 400.

Wed 1 Oct, 19:30 IST (10:00 ET)BMW FS billing lead, then the L2 pod

  • What: income for 1 Oct shows $0. Last month it was about $252,000. The billing lead raises a ticket.
  • Why it hid: nothing failed. The finished, every bill was a valid amount, and no alert watches amounts.
  • How we saw it: runs the query below. Loan bills look normal; the average lease bill fell by exactly 60, the fee.
App Insights: average bill by kind
customEvents
| where timestamp > ago(40d)
| where name == "BillCreated"
| extend kind = tostring(
    customDimensions.Kind),
  amount = todouble(
    customDimensions.Amount)
| summarize avg(amount)
    by kind, bin(timestamp, 1d)
In theory

An alert on the average bill per kind: page someone if it moves more than 5% between runs.

In practice

No alert. A person reading a report found it the same evening, before bills were emailed.

Try it yourself · 10 minutes, on your Mac
  1. dotnet new console -n HideLab. Write with public virtual decimal AmountDue() => 400m;.
  2. Write Lease : Contract with public new decimal AmountDue() => 460m;.
  3. Contract c = new Lease(); then print c.AmountDue(): 400. Print ((Lease)c).AmountDue(): 460.
  4. Change to and run again: both print 460.
You should see

With , the variable's type picks the method. With , the object does.

The senior move

"When a refactor makes a method virtual, I search every child that already has that name. The compiler only warns, and one keyword silences it."

Sources Sourced: method hiding, vs and warning CS0114 (C# docs, "Knowing when to use override and new keywords"). Composed: BMW FS's systems, dates, counts, amounts, names, ticket and numbers, and the (standard columns).

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 20 minutes

Model contracts in C#: an abstract with a guarded balance, and and each overriding . Prove in a test that a bad payment is refused and changes nothing.

Question index (Must, easy): model loans in code; same exercise as Microsoft Learn's inheritance tutorial
Stuck? Hints
  1. Which method has no body in the base? Mark it .
  2. How can outside code read the balance but not set it?
  3. Where do you check that a payment is above 0?
Show a solution
Lending.sln · where this file sits
  • tests/Domain.Tests/
ContractTests.cs · line 5 · 1 of 3Every kind in one list, typed as the base. A new kind is one more line.
What the interviewer checks
  • base; is ; each child uses .
  • Balance { get; private set; } plus that checks first.
  • The base constructor checks its values; children call .
  • decimal for money.
  • Children are , and you can say why.
  • Senior extra: one theory test that runs against every kind.

Task 2

Reportedmedium · 10 minutes

What does this program print? Read the code above, write your three lines, then open the solution.

MSC Technology: given a polymorphism program, write its output
You are given
Lending.sln · where this file sits
Given.cs · line 1 · 1 of 4Top-level lines run first. The classes are below.
Stuck? Hints
  1. Which constructor runs first when you write new Lease()?
  2. Print is virtual. Whose Print runs inside the base constructor?
  3. Kind uses . What type is the variable c?
Show a solution
Lending.sln · where this file sits
Output · line 1 · 1 of 3Base constructor → virtual Print → Lease's Print, but _name is not set yet.
What the interviewer checks
  • Line 1 is null: the base constructor calls Lease's override before has set _name.
  • Line 2 is : the object is a Lease, so the override runs.
  • Line 3 is base Kind: hides, and c is typed .
  • Senior extra: with string? _name = "Lease"; (a field initializer) line 1 prints , because in C# field initializers run before the base constructor. Analyzer rule CA2214 warns about the virtual call.

Task 3

Question index: Mustmedium · 25 minutes

A picks the amount with a switch on Kind (given above). Turn it into classes, then add Navistar's (adds a mileage fee) without editing .

Question index (Must): model loans in code, extended to the refactor interviewers ask about next
You are given
Lending.sln · where this file sits
  • src/Application/Billing/
PaymentCalculator.cs · line 7 · 1 of 1Every new kind lands here, and in every other switch like it. is one database row with every kind's columns.
Stuck? Hints
  1. What do both branches share? That goes in the base class.
  2. Each branch becomes one override.
  3. Where must the Kind text still be read? Only where a row becomes an object.
Show a solution
Lending.sln · where these 2 files sit
  • src/
    • Domain/Contracts/
    • Infrastructure/Contracts/
Diagram · step 3Fix: one class per kind. holds the shared part; and hold their own.
TruckLease.cs · line 6 · 1 of 2The only new data.
What the interviewer checks
  • An abstract base with , one class per kind.
  • The switch leaves billing; one factory maps Kind to a class when a row is loaded (EF Core can do this for you).
  • is one new file; and its tests are untouched.
  • You can say when a switch is fine: it sits in one place and the set of kinds never grows.
  • Senior extra: the factory is the one switch left, so a new kind is one line there; a test that loads every Kind value guards it.
With AI tools

Interviewers now ask how you check AI-written code. Copilot often writes public List<T> Items { get; set; } and a new subclass for each variation, because both are common in the code it learned from. Its quick fix for warning CS0114 ("this method hides the parent's") can be "add the keyword", which silences the warning and keeps the bug. In review I look for three things: lists and setters that leak, children that throw , and where was meant.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked