DEV Community

Renato Silva
Renato Silva

Posted on

I Failed a Technical Interview After 10 Years. Here's the Feedback.

I have more than 10 years in software engineering. I've migrated monoliths to .NET Core, designed APIs, tuned SQL Server queries in production, and mentored other developers.

And I just failed a technical interview on the fundamentals.

Not on distributed systems. Not on system design. On Dependency Injection, SOLID, interfaces versus abstract classes, variable scope, and database indexes. The things we expect a mid-level developer to explain.

This post is about that rejection, what I think actually went wrong, and the refresher I wrote for myself afterwards. If you're a senior developer preparing for interviews, this one is for you. 👇

📩 The feedback

A few days after the interview, the email arrived. To their credit, it was specific:

During the technical discussion, we identified some areas where your knowledge could be further strengthened, particularly around a number of fundamental software engineering concepts. These included Dependency Injection, object-oriented programming (OOP) and SOLID principles, the differences between abstract classes and interfaces, variable scope, and database indexing concepts such as clustered and non-clustered indexes.

My first reaction was defensive. I use DI every day. I've built entire systems on these principles.

My second reaction, a day later, was more honest: they weren't wrong about what they saw in that room.

🧠 Doing is not the same as explaining

After 10 years, most fundamentals stop being concepts and become reflexes. I register services in Program.cs without thinking about why constructor injection beats a service locator. I add an index when a query is slow, without reciting the difference between a clustered and a non-clustered one.

That's fine on the job. In an interview, it's a problem. The interviewer can't see your reflexes. They only see what you can put into words, under pressure, in a few minutes.

A few things I noticed when I replayed the interview in my head:

  • I answered with experience instead of definitions. "In my last project we..." is a great follow-up, but a weak opening. Lead with the concept, then prove it with a story.
  • I skipped the vocabulary. Saying "we just inject it" isn't the same as naming the Dependency Inversion Principle and the lifetimes involved.
  • I underestimated the basics. I prepared for system design and architecture. I didn't revise what I assumed I already knew.

Seniority doesn't exempt you from fundamentals. If anything, it raises the bar: a senior is expected to teach them.

📚 The refresher I wrote for myself

Here is how I'd answer each topic now: definition first, then the detail that shows you've actually used it.

1. Dependency Injection

Definition: a class receives its dependencies from the outside instead of creating them itself. It's the practical way to apply the Dependency Inversion Principle: depend on abstractions, not concrete implementations.

public class OrderService
{
    private readonly IPaymentGateway _payments;

    // The dependency is injected, not created with 'new'
    public OrderService(IPaymentGateway payments) => _payments = payments;
}

// Program.cs
builder.Services.AddScoped<IPaymentGateway, StripeGateway>();
builder.Services.AddScoped<OrderService>();
Enter fullscreen mode Exit fullscreen mode

The senior-level detail: service lifetimes in .NET.

  • Transient: a new instance every time it's requested.
  • Scoped: one instance per request (or per scope).
  • Singleton: one instance for the whole application.

The classic trap is a captive dependency: injecting a scoped service (like a DbContext) into a singleton. The scoped service then lives as long as the singleton, which breaks its assumptions.

2. OOP and SOLID

The four pillars of OOP:

  • Encapsulation: hide internal state, expose behaviour.
  • Abstraction: expose what an object does, not how.
  • Inheritance: reuse and specialise behaviour from a base type.
  • Polymorphism: treat different types through a common contract.

SOLID, in one line each:

  • Single Responsibility: a class should have one reason to change.
  • Open/Closed: open for extension, closed for modification.
  • Liskov Substitution: a subtype must be usable wherever its base type is expected, without surprises.
  • Interface Segregation: many small, focused interfaces beat one large one.
  • Dependency Inversion: high-level modules depend on abstractions, not on low-level details.

The senior-level detail: have one real example per principle ready. For Liskov, the classic is the Square that inherits from Rectangle and breaks when you set width and height independently.

3. Abstract classes vs interfaces

Abstract class Interface
Inheritance A class can inherit only one A class can implement many
State (fields) Yes No instance fields
Constructors Yes No
Implementation Abstract and concrete members Contract only (plus default methods since C# 8)
Models An "is-a" relationship with shared code A "can-do" capability
public abstract class Notification
{
    public DateTime CreatedAt { get; } = DateTime.UtcNow; // shared state
    public abstract Task SendAsync();                     // must be implemented
}

public interface IRetryable
{
    int MaxRetries { get; }
}

public class EmailNotification : Notification, IRetryable
{
    public int MaxRetries => 3;
    public override Task SendAsync() => Task.CompletedTask;
}
Enter fullscreen mode Exit fullscreen mode

The senior-level detail: default interface methods blurred the line in C# 8, but the rule of thumb still holds. Use an abstract class to share code and state within a family of types. Use an interface to define a capability that unrelated types can share.

4. Variable scope

Definition: scope is the region of code where a variable can be accessed. In C#, scope is defined by the block ({ }) where the variable is declared.

The interesting part is closures. A lambda captures the variable, not its value at that moment. In JavaScript, this is the classic var vs let question:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i)); // 3, 3, 3 — 'var' is function-scoped
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j)); // 0, 1, 2 — 'let' is block-scoped
}
Enter fullscreen mode Exit fullscreen mode

The senior-level detail: explain why. With var, there is one variable shared by every iteration, and it's hoisted to the function. With let, each iteration gets a new binding. C# had the same issue with foreach until C# 5, which changed it so each iteration gets a fresh variable.

5. Clustered vs non-clustered indexes

Clustered index: defines the physical order of the rows in the table. The table is the index. That's why a table can have only one. In SQL Server, the primary key creates a clustered index by default.

Non-clustered index: a separate structure that stores the indexed columns plus a pointer back to the row. A table can have many.

-- Clustered: rows are stored ordered by OrderId
CREATE CLUSTERED INDEX IX_Orders_OrderId ON dbo.Orders (OrderId);

-- Non-clustered, covering the query with INCLUDE
CREATE NONCLUSTERED INDEX IX_Orders_CustomerId
    ON dbo.Orders (CustomerId)
    INCLUDE (OrderDate, Total);
Enter fullscreen mode Exit fullscreen mode

Think of a book: the clustered index is the order of the pages, the non-clustered index is the index at the back that tells you which page to go to.

The senior-level detail: when a non-clustered index doesn't have every column the query needs, SQL Server does a key lookup back to the clustered index for each row. With many rows, that gets expensive. INCLUDE columns make the index covering and remove the lookup. The trade-off: every index speeds up reads and slows down writes.

🛠️ What I'm doing about it

Feeling bad about it for a week wouldn't change anything. So I turned the feedback into a plan:

  1. Explain out loud. For each topic, I say the definition in under 30 seconds, then add one real example from my work. If I can't do it without notes, I'm not ready.
  2. Use the structure "concept → detail → story". Definition first, the senior-level nuance second, and a production story last.
  3. Write about it. Writing this post was part of the plan. If you can teach it, you know it.
  4. Revise the basics before every interview. Not just system design. One evening for fundamentals, every time.

💡 Final thoughts

Failing an interview after 10 years hurts more than failing one after two. But clear, specific feedback is rare, and it's a gift. Most rejections just say "we decided to move forward with other candidates."

If you take one thing from this post: experience doesn't replace fundamentals, it should make you better at explaining them.

Now I'm curious 👇

  • Have you ever failed an interview on something you use every day?
  • Which "basic" question do you think catches senior developers the most?

Let me know in the comments. And if this helped, share it with someone who's preparing for interviews right now.

Top comments (6)

Collapse
 
christopher_jakob_acda8fa profile image
Christopher Jakob •

"Doing is not the same as explaining" is so true, and I think there's another layer to it. In an interview your body is already in a higher alert state, and when that happens your brain grabs whatever it trusts most, which for you is the stories from real work, not the textbook words. That's not you lacking the knowledge, it's the alarm going off and pulling you toward what feels safe. Saying the definitions out loud before interviews is a great fix because it gives your brain a new path it already knows is safe, so it stops reaching for the old one under pressure. Respect for sharing the email and owning it after the first defensive reaction. Most people stay stuck in that first day.

Collapse
 
renato_silva_71eef0fc385f profile image
Renato Silva •

Hi @christopher_jakob_acda8fa, it's exactly as you said, during a interview you have average 20min to convince someone that you are good at what you do. I wanted to share this because in a current situation of the IT industry people are afraid of what is coming and at same time trying to improve themselves. Thank you for the comment.

Collapse
 
christopher_jakob_acda8fa profile image
Christopher Jakob •

Yeah 20 minutes is nothing, and it's even less when half of it goes to your nerves settling down. And you're right about the timing, a lot of people are scared right now, so posts like yours help more than you think. It shows someone with 10 years in still gets rattled and can still learn from it. Good luck on the next one, you'll crush it.

Collapse
 
waccha21 profile image
Federico •

"The interviewer can't see your reflexes" is spot on. Ten years of doing turns concepts into muscle memory, and interviews ask you to turn them back into words on the spot. Going from defensive to honest in a day, then writing it up publicly, is more senior than any definition of SOLID. Good luck with the next one.

Collapse
 
renato_silva_71eef0fc385f profile image
Renato Silva •

Hi @waccha21, thank you for the comment. I just saw an opportunity to share something that almost sure happens more than it seems. All of us tend to be our own bubble, I know that I'm not the prime software engineer but at same time I know that I am able to do something. Thank you again.

Collapse
 
waccha21 profile image
Federico •

Ha, the bubble thing is real. Knowing you can do the work without needing to be the best engineer in the room is a pretty good place to interview from. Hope the next one goes your way.