354 .NET Developer Interview Questions & Answers

127 top • 51 Amazon • 43 Apple • 54 Google • 33 Meta • 46 Microsoft

.NET Developer icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

61. What is the using statement used for?Language SpecificMedium

Question Details

Describe deterministic disposal, scope boundaries, and how using interacts with IDisposable resources.

Short Interview Answer (30-60 seconds)

The using statement makes sure a disposable resource is cleaned up when execution leaves its scope. C# calls Dispose even if an exception causes execution to leave that scope. I use it for IDisposable resources such as streams so cleanup happens at a predictable point instead of depending on garbage collection.

Detailed Explanation

See the Code while reading this explanation.

The using statement helps a program clean up something as soon as that work is finished. For example, a program may open a file while it reads or writes data. That file should not stay open after the work is done. Using creates a clear boundary around the work. When execution leaves that boundary, cleanup happens automatically. Cleanup also happens if an error causes execution to leave early. This makes the lifetime of the resource easier to understand and reduces the chance of keeping an important resource open longer than needed.

Useful Questions to Ask the Interviewer
  1. Should I also explain the using declaration form?
  2. Should I compare using with await using?
What is the using statement used for? diagram
How to Explain It in an Interview

In C#, a using statement provides deterministic disposal. This means cleanup occurs at a predictable point in program execution.

A traditional using statement places a disposable resource inside a block. When control leaves that block, C# ensures that Dispose is called on the resource. This happens when the block finishes normally and also when an exception causes execution to leave the block. Its cleanup behavior is similar to placing the Dispose call in a finally section, so cleanup is not skipped simply because the protected work fails.

A using declaration is another form. For example, using var stream = ...; does not create a separate block. The resource is disposed when execution leaves the scope that contains the declaration. Because of this, placing the declaration earlier in a large method can keep the resource alive longer than necessary.

If the resource expression evaluates to null, no Dispose call is made on that null value. For the normal reference type case, the resource implements IDisposable and its Dispose method defines the cleanup work.

Using should be used when a disposable resource should be released promptly. Common examples include file streams, readers, writers, and disposable database objects. Dispose does not mean that the managed object is immediately removed from memory. Garbage collection still manages managed memory separately.

If cleanup itself is asynchronous and the resource implements IAsyncDisposable, use await using so DisposeAsync can be awaited. The cost of using itself is small. The meaningful runtime cost comes from whatever cleanup the resource performs in Dispose or DisposeAsync.

Code
using System;
using System.IO;

public static class Program
{
    public static void Main()
    {
        string path = Path.GetTempFileName();

        try
        {
            // Limit the stream lifetime to the exact work that needs the file.
            using (FileStream stream =
                       new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                // Write one byte while this scope owns the open file resource.
                stream.WriteByte(65);
            }

            // The using scope has ended, so Dispose has already released the file handle.
            Console.WriteLine("Stream disposed after leaving its scope.");
        }
        finally
        {
            // Delete the temporary file after the stream has released the file handle.
            File.Delete(path);
        }
    }
}
Why Interviewers Ask This

Interviewers ask this to check whether a candidate understands resource lifetime and deterministic cleanup in C#. They want to see whether the candidate knows when Dispose is called, how scope controls resource lifetime, what happens when an exception occurs, and when asynchronous disposal requires await using.

Common interview mistakes

A common mistake is thinking that using immediately destroys the object or frees all managed memory. Its main guarantee is deterministic disposal when execution leaves the relevant scope. Another mistake is manually calling Dispose on a resource that is already controlled by using without a specific reason. Developers can also place a using declaration too early in a large scope and keep the resource alive longer than needed. Another mistake is using ordinary using when asynchronous cleanup through IAsyncDisposable requires await using.

Interview tip

Start by saying that using provides deterministic disposal. Then explain the scope boundary, the automatic Dispose call, and the guarantee when an exception leaves the scope. Mention using declarations and await using only after the main behavior is clear.

Interviewer may ask next
What happens if an exception is thrown inside a using block?

Dispose is still called when the exception causes execution to leave the using block. The using statement guarantees deterministic disposal with cleanup behavior similar to a finally section. This matters because a resource such as an open stream still needs cleanup when the operation fails. If Dispose itself throws, that new exception can affect which exception is observed, so disposal code should normally avoid throwing.

When should you use await using instead of using?

Use await using when the resource implements IAsyncDisposable and its cleanup should run through DisposeAsync. Await using waits for that asynchronous disposal to complete before execution continues beyond the scope. This matters when cleanup requires asynchronous work. The tradeoff is that the containing code must support await, while ordinary IDisposable resources should normally use regular using.

62. What is the difference between == and .Equals()?Language SpecificMedium

Question Details

Discuss operator equality versus method equality and what can change when a type overloads comparison behavior.

Short Interview Answer (30-60 seconds)

The main difference is that == is an operator whose behavior is determined by the applicable equality operator for the operand types, while Equals is a method used to test equality according to the implementation provided by the type. Their results depend on the type. For an ordinary class with no custom equality, == normally compares references and inherited Object.Equals also uses reference equality. Types such as string define value equality. For null safe generic comparison, EqualityComparer<T>.Default.Equals is often a good choice.

Detailed Explanation

See the Code while reading this explanation.

The practical point is that two values that appear to contain the same information are not always considered equal in the same way. One comparison may check whether two variables refer to the same object. Another may check whether the information represented by two values is considered the same. The exact result depends on the kind of value and the equality rules that kind provides. This matters because choosing the wrong comparison can make conditions fail, allow unwanted duplicates, or make collections behave differently from what the developer expects.

Useful Questions to Ask the Interviewer
  1. Should I explain both reference types and value types?
  2. Would you like an example of a type that defines custom equality?
What is the difference between == and .Equals()? diagram
How to Explain It in an Interview

In C#, == is an operator. The compiler selects the applicable equality operator from the operand types and the available operator definitions. Some types have equality operators defined by the language or their type, and user defined types can overload ==.

Equals is a method. System.Object defines a virtual Equals method. A class can override it to provide logical value equality instead of the inherited reference equality behavior. A type can also implement IEquatable<T> so equality between values of the same type can use a strongly typed method.

For an ordinary class that does not overload ==, == normally checks whether both references refer to the same object. If that class does not override Equals, the inherited Object.Equals implementation also uses reference equality.

Types can change this behavior. String defines == and Equals so both compare string contents. Records also provide generated value equality behavior. A custom class can overload == and override Equals. Those implementations should follow the same logical equality rule so callers receive consistent results.

Null handling is important. Calling left.Equals(right) throws NullReferenceException if left is null. An applicable == operator can compare references with null, but an overloaded operator controls its own behavior. Static Object.Equals(left, right) and EqualityComparer<T>.Default.Equals(left, right) provide null safe comparison patterns.

For value types, Equals is available through ValueType and can be overridden. The == expression is valid only when an applicable equality operator exists. Performance sensitive structs commonly implement IEquatable<T> because strongly typed equality can avoid boxing that may occur through object based equality paths.

Code
using System;
using System.Collections.Generic;

public sealed class Person : IEquatable<Person>
{
    public int Id { get; }

    public Person(int id)
    {
        Id = id;
    }

    public bool Equals(Person? other)
    {
        // Two Person objects are logically equal when their Id values match.
        return other is not null && Id == other.Id;
    }

    public override bool Equals(object? obj)
    {
        // Keep object based equality consistent with the strongly typed equality rule.
        return obj is Person other && Equals(other);
    }

    public override int GetHashCode()
    {
        // Equal Person objects must return the same hash code.
        return Id.GetHashCode();
    }

    public static bool operator ==(Person? left, Person? right)
    {
        // Use the same logical equality rule and handle null values safely.
        return EqualityComparer<Person>.Default.Equals(left, right);
    }

    public static bool operator !=(Person? left, Person? right)
    {
        // Inequality is the logical opposite of equality.
        return !(left == right);
    }
}

public static class Program
{
    public static void Main()
    {
        var first = new Person(42);
        var second = new Person(42);

        // These variables refer to two separate objects.
        Console.WriteLine(ReferenceEquals(first, second));

        // Both comparisons use the same logical equality rule based on Id.
        Console.WriteLine(first == second);
        Console.WriteLine(first.Equals(second));

        // The default generic comparer also uses Person equality semantics.
        Console.WriteLine(EqualityComparer<Person>.Default.Equals(first, second));
    }
}
Why Interviewers Ask This

Interviewers ask this to check whether the candidate understands that C# has operator equality and method equality, that a type can define their behavior, and that reference identity and value equality are different concepts. It also tests null handling, custom equality design, and the judgment needed to keep equality behavior consistent in production code.

Common interview mistakes

A common mistake is saying that == always checks references while Equals always checks values. That rule is incorrect because equality behavior depends on the type. Another mistake is overloading == without keeping Equals and GetHashCode consistent with the same logical equality rule. Developers also sometimes call an instance Equals method on a value that may be null, which causes NullReferenceException. For structs, another mistake is assuming that == is automatically available for every struct. An applicable equality operator must exist for the expression to compile.

Interview tip

Start by saying that == is an operator and Equals is a method, then explain that their exact behavior depends on the type. Use an ordinary class as the reference equality example and string as the value equality example. Mention custom equality and null handling. Avoid the inaccurate shortcut that == always means reference equality.

Interviewer may ask next
What happens if the left value is null when using == versus calling Equals?

Calling left.Equals(right) throws NullReferenceException when left is null because an instance method needs an object to invoke it on. An applicable == operator can compare references with null, although an overloaded operator defines its own behavior. Static Object.Equals(left, right) and EqualityComparer<T>.Default.Equals(left, right) are null safe alternatives. This matters when values may legitimately be null because the comparison choice can determine whether the code returns an equality result or throws an exception.

Why should ==, Equals, and GetHashCode be consistent for a custom value type or class?

They should represent the same logical equality rule so the type behaves predictably. If == reports equality while Equals reports inequality, different parts of the program can disagree about the same two values. If Equals reports that two values are equal, GetHashCode must return the same hash code for those values so hash based collections can locate them correctly. The tradeoff is additional implementation work, but consistent equality behavior prevents subtle production bugs.

63. Even or OddCodingEasy

Question Details

Solve the problem in C#, explain the input and output, edge cases, chosen data structures, time complexity, space complexity, and why the approach is production-suitable.

Short Interview Answer (30-60 seconds)

I would check the integer using the modulo operator. I calculate number % 2 and compare the remainder with zero. If the remainder is zero, I return "Even". Otherwise, I return "Odd". For the example, the input is 14. 14 % 2 is 0, so the condition is true and the result is "Even". I do not need any data structure because I only use scalar values. The algorithm takes O(1) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

This question asks me to decide whether one integer is even or odd. The input is one integer called number. The output is either "Even" or "Odd". The main idea is to divide the number by 2 with the modulo operator and check the remainder. A remainder of zero means the number is even. A non-zero remainder means it is odd. The solution uses no array or collection. It only uses scalar values and one condition. This is simple and suitable for production code because the logic is deterministic and easy to test.

Useful Questions to Ask the Interviewer
  1. Should the method return exactly the strings "Even" and "Odd"?
  2. Should the method accept a standard C# int value?
Even or Odd diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is one integer named number. The output is the string "Even" or "Odd". The diagram uses 14 as the example input and "Even" as the expected output.

2. Choose the algorithm and data structure

I use the modulo operator %. It gives the remainder after division by 2. I do not need a data structure. The main rule is that a number is even exactly when number % 2 == 0.

3. Initialize the state

The initial state is number = 14. There is no extra data structure. The algorithm works directly with the input value.

The central invariant is simple: a number is even exactly when its remainder after division by 2 is zero.

4. Walk through the example

First, I compute 14 % 2. The result is 0.

Next, I compare the remainder with zero. The check is 0 == 0, which is true.

Because the condition is true, I choose "Even" and return it. Processing stops after this result is produced. The "Odd" branch is not executed for the example.

The exact relationship is 14 = 2 × 7 + 0. This confirms that 14 is divisible by 2.

The execution summary is: number = 14 → compute modulo and compare → remainder = 0, decision = true → return "Even".

5. Explain why the result is correct

The algorithm checks the mathematical definition of an even integer. An integer is even exactly when division by 2 leaves remainder zero. Since 14 % 2 is 0, returning "Even" is correct.

6. Explain the C# implementation

The method accepts an int named number. It evaluates number % 2 == 0. The conditional expression returns "Even" when the condition is true. Otherwise, it returns "Odd".

The Main method runs the same example from the diagram with 14 and prints the returned result.

7. Explain complexity and edge cases

The method performs a constant number of operations, so the time complexity is O(1). It uses only scalar values, so the auxiliary space complexity is O(1).

Relevant edge cases include 0, negative even values such as -8, negative odd values such as -5, and large 32-bit integer values. The same modulo check handles these cases correctly.

Key Insight / Why This Solution Works

The key insight is the definition of an even integer. A number is even exactly when dividing it by 2 leaves remainder zero. The algorithm computes number % 2 and compares that remainder with 0. If the comparison is true, it returns "Even". Otherwise, it returns "Odd". The central invariant is number % 2 == 0 exactly when the number is even. No data structure is needed because the algorithm only uses scalar values.

Code
using System;

public static class Program
{
    public static void Main()
    {
        // Use the exact example from the diagram so the executable output matches the walkthrough.
        int number = 14;

        // Run the parity check and store the returned classification.
        string result = EvenOrOdd(number);

        // Print the same result shown in the diagram: Even.
        Console.WriteLine(result);
    }

    public static string EvenOrOdd(int number)
    {
        // A remainder of zero after division by 2 means the number is even.
        // Otherwise, the number is odd. This also handles zero and negative integers correctly.
        return number % 2 == 0 ? "Even" : "Odd";
    }
}
Time & Space Complexity

The time complexity is O(1) because the method performs one modulo calculation, one comparison, and one return. The auxiliary space complexity is O(1) because the method does not create an array, collection, recursion stack, or other memory that grows with the input.

Where it is used

This pattern is useful whenever software needs to check parity. For example, code can use it to decide whether a count is even or odd, alternate behavior based on a number, or validate a value that must be divisible by 2. When only one integer needs to be classified, the modulo check is simple and direct.

Why Interviewers Ask This

This question checks whether I can turn a simple mathematical rule into correct C# code. The interviewer is looking for correct use of the modulo operator, a clear condition, the right return values, and proper handling of basic edge cases. It also checks whether I avoid unnecessary data structures and whether I can explain the solution's correctness and O(1) time and O(1) auxiliary space accurately.

Common interview mistakes
  1. Forgetting to use the remainder and checking ordinary division instead.
  2. Treating 0 as odd even though 0 % 2 is 0.
  3. Assuming the logic works only for positive numbers and overlooking negative values such as -8 and -5.
  4. Adding an array or collection when the problem only needs one integer and one condition.
  5. Claiming that the method needs more than constant time or constant extra space.
Interview tip

State the rule first: an even integer leaves remainder zero when divided by 2. Then trace the example 14 % 2 = 0 and 0 == 0. This directly connects the mathematical rule, the decision, the returned result, and the O(1) complexity.

Interviewer may ask next
What would change if the method had to return `"EVEN"` and `"ODD"` in uppercase?

The algorithm would stay the same. I would still check number % 2 == 0. I would only change the returned strings to "EVEN" and "ODD". The time remains O(1) and the auxiliary space remains O(1).

What would change if the input had to support integers larger than the C# `int` range?

I would use a wider integer type such as long when the input contract requires it. The same modulo check still determines parity. The algorithm remains O(1) time and O(1) auxiliary space.

64. Reverse a linked list.CodingEasy

Question Details

Describe how to reverse the pointers in place, what the head pointer ends up pointing to, and how you would verify the list is fully reversed.

Short Interview Answer (30-60 seconds)

I would reverse the linked list in place with three pointers: prev, current, and next. I start with prev as null and current at the head. For each node, I save current.next, point current.next back to prev, then move prev and current forward. When current becomes null, prev is the new head, so the list is fully reversed. This takes O(n) time and O(1) extra space.

Detailed Explanation

See the Code while reading this explanation.

This problem asks me to take a chain of connected items and turn it around. The last item should become the first one, and every connection should point the other way. I do this in place, so I do not build a new list. I walk through the list once, save the next item before changing anything, reverse one connection at a time, and then return the new first item at the end.

Useful Questions to Ask the Interviewer
  1. Is the list singly linked, and should I reverse it in place?
  2. Should I return the new head after the reversal?
Reverse a linked list. diagram
How to Explain It in an Interview
1. Understand the input and output

The input is the head of a singly linked list. The output is the head of the same list after all next pointers are reversed. The old tail becomes the new head. We do not create new nodes.

2. Choose the algorithm and data structure

I use three pointers: prev, current, and next. prev keeps the already reversed part. current walks through the list. next saves the rest of the list before I change current.next. This works because each node is rewired once.

3. Initialize the state

Start with prev = null, current = head, and next = null. Before any change, the reversed part is empty. The invariant is: everything before current is already reversed, and current is still the first node not processed.

4. Walk through the example

Use the exact example 1 -> 2 -> 3 -> 4 -> 5 -> null. Step 1: save 2, point 1.next to null, move prev to 1, and move current to

  1. Step 2: save 3, point 2.next to 1, move prev to 2, and move current to
  2. Step 3: save 4, point 3.next to 2, move prev to 3, and move current to
  3. Step 4: save 5, point 4.next to 3, move prev to 4, and move current to
  4. Step 5: save null, point 5.next to 4, move prev to 5, and move current to null.
5. Explain why the result is correct

Each step removes one node from the front of the unprocessed part and adds it to the front of the reversed part. Because next is saved first, the rest of the list is never lost. When current becomes null, every node has been reversed exactly once, so prev points to the new head.

6. Explain the C# implementation

The ReverseList method uses a while loop that continues until current is null. Inside the loop, it stores current.next, flips current.next to prev, advances prev, and then advances current. The method returns prev, and that is the new head of the reversed list.

7. Explain complexity and edge cases

The loop visits each node once. So the time is O(n). The method uses only three pointers, so the extra space is O(1). An empty list returns null. A one-node list stays the same. A two-node list reverses correctly because the links are flipped one by one.

Key Insight / Why This Solution Works

The key idea is to reverse the links one node at a time while keeping the rest of the list safe in next. The invariant is simple: prev always points to the reversed prefix, current points to the next node to process, and next protects the remaining list. This is better than building a new list because it uses constant extra space. When current becomes null, all nodes are in the reversed prefix, so prev is the new head.

Code
using System;
using System.Text;

public sealed class ListNode
{
    public int val;
    public ListNode? next;

    public ListNode(int val)
    {
        this.val = val;
    }
}

public static class Program
{
    public static void Main()
    {
        // Build the exact example from the diagram: 1 -> 2 -> 3 -> 4 -> 5 -> null.
        ListNode? head = BuildList(new[] { 1, 2, 3, 4, 5 });

        Console.WriteLine("Original List:");
        Console.WriteLine(PrintList(head));

        // Reverse the list in place and return the new head.
        head = ReverseList(head);

        Console.WriteLine("Reversed List:");
        Console.WriteLine(PrintList(head));
    }

    public static ListNode? ReverseList(ListNode? head)
    {
        // prev is the reversed part. current is the node we are processing now.
        ListNode? prev = null;
        ListNode? current = head;

        while (current != null)
        {
            // Save the next node before changing current.next.
            ListNode? next = current.next;

            // Reverse the pointer for the current node.
            current.next = prev;

            // Move prev forward. It is now the first node in the reversed part.
            prev = current;

            // Move current forward to the next unprocessed node.
            current = next;
        }

        // prev is the new head after all links are reversed.
        return prev;
    }

    private static ListNode? BuildList(int[] values)
    {
        if (values.Length == 0)
        {
            return null;
        }

        ListNode head = new ListNode(values[0]);
        ListNode tail = head;

        for (int i = 1; i < values.Length; i++)
        {
            // Create the next node and attach it to the end of the list.
            ListNode newNode = new ListNode(values[i]);
            tail.next = newNode;
            tail = newNode;
        }

        return head;
    }

    private static string PrintList(ListNode? head)
    {
        if (head == null)
        {
            return "null";
        }

        StringBuilder sb = new StringBuilder();
        ListNode? current = head;

        while (current != null)
        {
            sb.Append(current.val);
            sb.Append(" -> ");
            current = current.next;
        }

        sb.Append("null");
        return sb.ToString();
    }
}
Time & Space Complexity

I look at each node once. That makes the time O(n). I only keep three pointers, so the extra memory is O(1). This is the standard in-place way to reverse a singly linked list.

Where it is used

This is useful when you need to reverse the order of connected items in place, such as a chain of tasks or records stored one after another. It is also a common interview pattern for pointer updates and careful state changes.

Why Interviewers Ask This

Interviewers want to see if you can reason about pointers without losing nodes. They also want to see a clear invariant, correct return value, and correct complexity. This question checks whether you know how to mutate a linked structure in place and explain each step in simple terms.

Common interview mistakes

One common mistake is changing current.next before saving the next node. That loses the rest of the list. Another mistake is returning head instead of prev. At the end, prev is the new head. A third mistake is using extra storage when the question asks for an in-place reversal. Another is forgetting that an empty list should return null, while a one-node list should stay the same.

Interview tip

Say the loop out loud as four actions: save next, reverse the link, move prev, move current. If you can explain that flow clearly, the pointer logic is easy to follow.

Interviewer may ask next
How would you reverse the list recursively?

You would use the call stack instead of the loop. The base case is an empty list or one node. The recursive call reverses the rest of the list, then you point the next node back to the current node and set current.next = null. The time stays O(n), but the extra space becomes O(n) because of recursion depth.

How do you verify that the list is fully reversed?

Start from the new head and traverse forward. For the example, you should see 5 -> 4 -> 3 -> 2 -> 1 -> null. You can also check that the old head is now the last node and its next is null. That confirms every pointer was flipped in the correct direction.

65. Middle of a linked list.CodingEasy

Question Details

Explain how to find the middle node with one pass and what to return when the list has an even number of nodes.

Short Interview Answer (30-60 seconds)

I would use two node references called slow and fast. Both start at the head. While fast and fast.next are not null, I move slow one node and fast two nodes. Because fast moves twice as quickly, slow reaches the middle when fast reaches the end. For an even-length list, this setup makes slow land on the second middle node. I return slow. The time complexity is O(n), and the auxiliary space complexity is O(1).

Detailed Explanation

See the Code while reading this explanation.

The input is the head of a singly linked list. We need to return the node in the middle. We should find it in one pass instead of first counting all the nodes. We use two references that start at the first node. One moves one node at a time. The other moves two nodes at a time. When the faster reference reaches the end, the slower reference is at the middle. For an even number of nodes, we return the second of the two middle nodes.

Useful Questions to Ask the Interviewer
  1. Can the head be null, and should I return null for an empty list?
  2. For an even-length list, should I return the second of the two middle nodes?
Middle of a linked list. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a reference to the first node of a singly linked list. The output is a node reference, not only the value stored in that node. If the list is empty, the diagram returns null. If the list has an even number of nodes, the required result is the second middle node.

2. Choose the slow and fast pointer method

I keep two node references named slow and fast. slow moves one node per loop. fast moves two nodes per loop. The important relationship is that fast advances twice as quickly as slow. This lets us find the middle without making a separate pass to count the nodes.

3. Initialize the state

Set slow = head and fast = head. Before the loop starts, both references point to the first node. Continue only while fast is not null and fast.next is not null. This condition makes it safe to move fast by two nodes.

4. Walk through the example

The diagram uses 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null. At the start, slow and fast both point to node 1. After the first loop, slow points to 2 and fast points to 3. After the second loop, slow points to 3 and fast points to 5. During the third loop, slow moves to 4 and fast moves two links to null. The next loop condition is false because fast is null. We stop and return slow. slow points to the node with value 4, which is the second middle node of this six-node list.

5. Explain why the result is correct

Each completed loop moves slow one link and fast two links. Therefore, fast advances through the list twice as quickly as slow. When fast reaches the end or cannot make another two-link move, slow has reached the middle position. With both references starting at head and this loop condition, an even-length list leaves slow on the second middle node.

6. Explain the C# implementation

The method first checks whether head is null. It then assigns head to both slow and fast. The while condition checks that fast can safely advance two links. Inside the loop, slow moves with slow.Next and fast moves with fast.Next.Next. When the condition becomes false, the method returns the node referenced by slow.

7. Explain complexity and edge cases

The algorithm takes O(n) time because the references only move forward through the list. It uses O(1) auxiliary space because it keeps only two additional node references. An empty list returns null. A one-node list returns its head. A two-node list returns the second node. The same pointer rule works for both odd and even list lengths.

Key Insight / Why This Solution Works

The key insight is to move one node reference at half the speed of another. slow and fast both start at head. On each completed loop, slow advances one link and fast advances two links. The central invariant is that fast has advanced twice as many links as slow after the same number of loop iterations. Therefore, when fast reaches the end or cannot make another two-link move, slow is at the middle. Starting both references at head and using fast != null && fast.Next != null makes slow land on the second middle node when the list length is even.

Code
using System;

public sealed class ListNode
{
    public int Value;
    public ListNode? Next;

    public ListNode(int value, ListNode? next = null)
    {
        // Store this node's value and the reference to the next node.
        Value = value;
        Next = next;
    }
}

public static class Program
{
    public static void Main()
    {
        // Build the exact diagram example: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null.
        ListNode head = new ListNode(
            1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6))))));

        // Run the slow-fast pointer algorithm on the example list.
        ListNode? middle = MiddleNode(head);

        // The list has six nodes, so the required second middle node has value 4.
        Console.WriteLine(middle?.Value);
    }

    public static ListNode? MiddleNode(ListNode? head)
    {
        // An empty list has no middle node.
        if (head == null)
        {
            return null;
        }

        // Both references start at the head.
        // slow will move one node per iteration and fast will move two.
        ListNode? slow = head;
        ListNode? fast = head;

        // Continue only while fast can safely move two links.
        // This stopping rule makes slow land on the second middle for even lengths.
        while (fast != null && fast.Next != null)
        {
            // Move slow one node toward the middle.
            slow = slow!.Next;

            // Move fast two nodes toward the end.
            fast = fast.Next.Next;
        }

        // At this point slow references the required middle node.
        return slow;
    }
}
Time & Space Complexity

Let n be the number of nodes. The time complexity is O(n). The references only move forward, and the loop performs about n / 2 iterations because fast advances two nodes at a time. O(n / 2) simplifies to O(n). The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. The amount of extra memory does not grow with n because the algorithm keeps only the slow and fast node references.

Where it is used

The slow-and-fast pointer pattern is useful when we need information about relative positions in a linked structure without first measuring its length. Finding the middle of a linked list is a common example because it avoids a separate counting pass. The same general different-speed pointer idea can also be useful in other linked-list problems where references need to progress through the structure at different rates.

Why Interviewers Ask This

This problem checks whether a candidate recognizes the slow-and-fast pointer pattern and can apply it safely to a linked list. It tests understanding of node references, pointer movement, null-safe loop conditions, and the invariant created by moving one reference twice as quickly as the other. The even-length case also shows whether the candidate understands how initialization and stopping conditions affect the returned node. Interviewers can also evaluate C# correctness, edge-case handling, and accurate O(n) time and O(1) auxiliary-space analysis.

Common interview mistakes

A common mistake is moving slow or fast by the wrong number of nodes. Another is checking only fast != null and then reading fast.Next.Next, which can cause a null-reference error. A candidate may also return the first middle node when the requirement is to return the second middle for an even-length list. Another mistake is returning only the integer value instead of the node reference. It is also incorrect to claim O(n) auxiliary space because this algorithm keeps only two extra references.

Interview tip

State the even-length rule before writing the loop. Say that both references start at head, slow moves one node, fast moves two nodes, and the condition fast != null && fast.Next != null makes slow finish on the second middle node.

Interviewer may ask next
How would you change the solution if the interviewer wanted the first middle node for an even-length list?

I would keep the same slow-and-fast pointer pattern but change the initial fast reference. Set slow = head and fast = head.Next. Then use the same movement rule while fast != null && fast.Next != null. For 1 -> 2 -> 3 -> 4 -> 5 -> 6, slow finishes at node 3 instead of node 4. The invariant still uses different pointer speeds, but the one-node head start for fast changes the even-length stopping position. Time remains O(n), auxiliary space remains O(1), and the tradeoff is that the initialization now implements the first-middle rule instead of the second-middle rule.

What happens for lists with zero, one, or two nodes?

For zero nodes, head is null, so the method returns null immediately. For one node, fast.Next is null before the loop starts, so the loop does not run and the method returns head. For two nodes, the loop runs once. slow moves to the second node and fast moves to null. The method then returns the second node, which matches the required second-middle rule. The complexity remains O(n) time and O(1) auxiliary space.

66. Detect Loop in a linked list.CodingEasy

Question Details

Focus on identifying a cycle without extra storage and explain how the fast and slow pointers reveal the loop.

Short Interview Answer (30-60 seconds)

I would use Floyd’s Tortoise and Hare algorithm with two node references. I start both slow and fast at the head. On each iteration, slow moves one node and fast moves two nodes. If fast reaches the end, there is no cycle. If slow and fast point to the same node, a cycle exists, so I return true immediately. This takes O(n) time and O(1) auxiliary space because I only store the two pointers.

Detailed Explanation

See the Code while reading this explanation.

The problem gives us the head of a singly linked list and asks whether following its next links can eventually bring us back to a node that was already reached. We only need to return true or false. The diagram solves this without storing visited nodes. It uses two moving references called slow and fast. Slow moves one node at a time, while fast moves two. If a loop exists, fast eventually catches slow inside that loop. This gives a simple solution with constant extra memory.

Useful Questions to Ask the Interviewer
  1. Should I return only whether a cycle exists, or also return the node where the cycle starts?
  2. Can the head be null or contain only one node?
  3. Should cycle detection be based on node identity rather than comparing node values?
Detect Loop in a linked list. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is the head node of a singly linked list. The output is true when the list contains a cycle and false when it does not. We must compare node references, not only their stored values. Two different nodes can contain the same value, so matching values alone do not prove that the pointers reached the same node.

2. Choose Floyd’s two-pointer algorithm

I use two node references called slow and fast. Slow moves one node on each iteration. Fast moves two nodes on each iteration. No visited-node collection is needed, so the extra memory stays constant.

The key idea is that once both pointers are inside a finite cycle, fast gains one position on slow during each iteration. Because the cycle contains only a finite number of nodes, fast must eventually catch slow.

3. Initialize the pointers

The diagram example is 1 -> 2 -> 3 -> 4 -> 5, with node 5 pointing back to node 3. Both slow and fast start at node 1.

Before moving fast two nodes, the loop checks that fast is not null and fast.Next is not null. This prevents an invalid Next access when the list has no cycle and fast reaches the end.

4. Walk through the diagram example

Initial state: slow = 1 and fast = 1.

Step 1: slow moves from node 1 to node

  1. Fast moves from node 1 through node 2 to node
  2. They do not point to the same node, so processing continues.

Step 2: slow moves from node 2 to node 3. Fast moves from node 3 through node 4 to node 5. They still do not point to the same node.

Step 3: slow moves from node 3 to node 4. Fast moves from node 5 to node 3 and then to node 4 because node 5 points back to node 3. Slow and fast now reference the same node 4. The method stops immediately and returns true.

The diagram also shows an optional step after cycle detection. To find the loop entry, one pointer is moved back to the head while the other remains at the meeting node. Both then move one node at a time. In this example they meet at node 3, which is the start of the cycle. The requested HasCycle method only needs to return true or false.

5. Explain why the result is correct

If there is no cycle, fast eventually reaches the end of the list. Then fast becomes null or fast.Next becomes null, the loop stops, and the method returns false.

If there is a cycle, both pointers eventually enter it. Fast moves one extra position relative to slow on each iteration. The relative distance therefore changes around the finite cycle until both references point to the same node. Their meeting proves that a cycle exists.

6. Explain the C# implementation

The method first handles an empty list and a one-node list whose Next reference is null. It then initializes slow and fast to the head. The while condition checks fast and fast.Next before fast moves two links. Inside the loop, slow advances once and fast advances twice. If slow and fast reference the same ListNode object, the method returns true immediately. If fast reaches the end instead, the loop finishes and the method returns false.

7. Explain complexity and edge cases

The time complexity is O(n). Floyd’s algorithm performs only a constant amount of work on each pointer movement and reaches either the end of the list or a meeting point after a linear number of movements. The auxiliary space complexity is O(1) because it stores only slow and fast.

Relevant edge cases are an empty list, a single node with no loop, a single node whose Next points to itself, and a cycle that starts at the head. The same pointer rules handle all of these cases.

Key Insight / Why This Solution Works

The key insight is that we can detect repeated traversal without storing every node we have visited. Floyd’s algorithm uses two references that move at different speeds. Slow advances one Next link per iteration, while fast advances two. If no cycle exists, fast eventually reaches null. If a cycle exists, both pointers eventually enter that finite cycle and fast gains one position on slow during each iteration until they meet. The central invariant is that slow advances exactly one link and fast advances exactly two whenever another loop iteration is valid. This detects a cycle with constant extra memory.

Code
using System;

public sealed class ListNode
{
    public int Val;
    public ListNode? Next;

    public ListNode(int val)
    {
        // Store the node value and start with no next node.
        Val = val;
        Next = null;
    }
}

public static class Program
{
    public static bool HasCycle(ListNode? head)
    {
        // An empty list or one terminating node cannot contain a cycle.
        if (head is null || head.Next is null)
        {
            return false;
        }

        // Both pointers start at the head, matching the diagram's initial state.
        ListNode? slow = head;
        ListNode? fast = head;

        // fast and fast.Next must exist before fast can safely move two links.
        while (fast is not null && fast.Next is not null)
        {
            // Slow advances by exactly one node.
            slow = slow!.Next;

            // Fast advances by exactly two nodes.
            fast = fast.Next.Next;

            // Meeting at the same node reference proves that a cycle exists.
            if (slow == fast)
            {
                return true;
            }
        }

        // If fast reaches the end, the list is not cyclic.
        return false;
    }

    public static void Main()
    {
        // Build the diagram's five nodes: 1 -> 2 -> 3 -> 4 -> 5.
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);

        // Connect the normal forward part of the list.
        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        // Create the cycle shown in the diagram: node 5 points back to node 3.
        node5.Next = node3;

        // The slow and fast pointers meet inside the cycle, so this prints True.
        bool result = HasCycle(node1);
        Console.WriteLine(result);
    }
}
Time & Space Complexity

The time complexity is O(n), where n represents the number of reachable nodes involved before the algorithm either reaches the end or detects the cycle. Each loop iteration performs only a constant amount of pointer work, and Floyd’s algorithm needs only a linear number of movements. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. Only the slow and fast node references are stored, so the extra memory does not grow with the linked list.

Where it is used

Floyd’s cycle-detection pattern is useful when objects or states form a chain where each item points to one next item. It can detect accidental circular references in linked structures and cycles in state-transition processes. It is especially useful when cycle detection is required but storing every previously visited item would use too much extra memory.

Why Interviewers Ask This

This problem tests whether you recognize Floyd’s cycle-detection pattern and understand node identity in a linked list. It also checks whether you can move pointers safely, handle null references, maintain the slow-and-fast invariant, and explain why different pointer speeds guarantee a meeting inside a cycle. The interviewer is also evaluating whether you can write correct C# and justify the O(n) time and O(1) auxiliary-space complexity.

Common interview mistakes

One common mistake is comparing node values instead of node references. Equal values can belong to different nodes. Another mistake is moving fast two nodes without first checking both fast and fast.Next for null. A candidate may also move slow and fast at the same speed, which removes the catching behavior that makes Floyd’s algorithm work. Using a visited HashSet also breaks the requirement to detect the cycle without extra storage. Finally, once slow and fast meet, the method should return true immediately instead of continuing to process the list.

Interview tip

Before writing code, state the two stopping cases clearly: if fast reaches the end, return false; if slow and fast meet at the same node reference, return true. Then write the one-step and two-step pointer movements exactly in that order.

Interviewer may ask next
How would you return the node where the cycle starts instead of only returning true or false?

Run the same slow and fast phase until the pointers meet. If fast reaches the end, there is no cycle. After a meeting, move one pointer back to the head and leave the other at the meeting node. Then move both pointers one node at a time. Their next meeting point is the cycle entry. In the diagram example, the detection phase meets at node 4. Resetting one pointer to node 1 and moving both one step at a time makes them meet at node 3. The time complexity remains O(n), the auxiliary space remains O(1), and the tradeoff is that we perform an additional pointer phase after detection.

What changes if extra storage is allowed?

We could traverse the list while storing each visited node reference in a HashSet<ListNode>. Before adding the current node, we check whether that exact reference is already present. Seeing it again means there is a cycle. HashSet lookup and insertion are O(1) on average, so the overall expected time is O(n), but the auxiliary space becomes O(n). This approach is straightforward, but Floyd’s method is better for the original requirement because it keeps auxiliary space at O(1).

67. Nth node from End.CodingEasy

Question Details

Solve the problem by reasoning about a fixed-distance pointer gap and explain how you handle short lists.

Short Interview Answer (30-60 seconds)

I would use two pointers, fast and slow, starting at the head. First, I move fast forward by n nodes. If fast becomes null before I complete those n moves, the list is too short, so I return null. Then I move both pointers together until fast reaches null. Because fast stays exactly n nodes ahead, slow finishes at the nth node from the end. I return slow's value. This takes O(L) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is the head of a singly linked list and a positive integer n. We need the value stored in the nth node when counting backward from the end. For example, with 1 → 2 → 3 → 4 → 5 → 6 and n = 2, the answer is 5. We do not need to store the nodes or calculate the list length first. Instead, we keep two positions a fixed distance apart. This also lets us detect when the list contains fewer than n nodes.

Useful Questions to Ask the Interviewer
  1. Should n be treated as 1-based, so n = 1 means the last node?
  2. If n is greater than the list length, should I return null?
  3. Should the function return the node value rather than the node reference?
Nth node from End. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is the head of a singly linked list and an integer n. The diagram treats n as 1-based. We return the value of the nth node from the end. If the list contains fewer than n nodes, we return null. In the shown example, the list is 1 → 2 → 3 → 4 → 5 → 6 and n = 2. The required value is 5.

2. Create a fixed gap between two pointers

I start both fast and slow at the head. I move fast forward exactly n times. If fast is already null before one of those required moves, the list contains fewer than n nodes, so I return null. After the n moves, fast is n nodes ahead of slow. If fast is now null, n equals the list length, so slow is still at the head and the head value is the answer.

3. Walk through the example

Both pointers start at node 1. Because n = 2, fast first moves from node 1 to node 2. Fast then moves from node 2 to node 3. Slow stays at node 1 during these two moves. The fixed gap is now two nodes. Next, both pointers move together. They become fast = 4 and slow = 2, then fast = 5 and slow = 3, then fast = 6 and slow = 4, and finally fast = null and slow = 5. Processing stops when fast becomes null.

4. Explain why the result is correct

After the first n moves, fast is exactly n nodes ahead of slow. This is the central invariant. When both pointers move forward one node at the same time, that distance stays unchanged. Therefore, when fast moves past the final node and becomes null, slow must be at the nth node from the end. In the example, fast becomes null while slow is at node 5. Therefore, the returned value is 5.

5. Explain the C# implementation

The method first handles an empty list or a non-positive n. It initializes fast and slow to head. A for loop advances fast exactly n times and checks before every move whether the list has already ended. If fast becomes null before all n moves are completed, the method returns null. If fast becomes null exactly after the nth move, slow is still at the head and its value is returned. Otherwise, a while loop moves fast and slow together until fast becomes null. The method then returns slow's value.

6. Explain complexity and edge cases

Let L be the number of nodes. Each pointer only moves forward, so the running time is O(L). The algorithm stores only two node references and a loop counter, so auxiliary space is O(1). If n equals the list length, it returns the head value. If n is greater than the list length, it returns null. If the list contains one node and n = 1, it returns that node's value.

Key Insight / Why This Solution Works

The key insight is to create a fixed distance of n nodes between two pointers. Fast and slow both start at the head. Fast moves forward n times while slow stays in place. After that, fast is exactly n nodes ahead of slow. Both pointers then move one node at a time, so this gap stays constant. When fast reaches null, slow is exactly at the nth node from the end. For the diagram's example 1 → 2 → 3 → 4 → 5 → 6 with n = 2, slow finishes at node 5. The initial fast-pointer movement also detects a list that is shorter than n.

Code
using System;

public sealed class ListNode
{
    public int Value;
    public ListNode? Next;

    public ListNode(int value, ListNode? next = null)
    {
        Value = value;
        Next = next;
    }
}

public static class Program
{
    public static int? NthFromEnd(ListNode? head, int n)
    {
        // The diagram uses a positive, 1-based n. An empty list has no valid answer.
        if (head is null || n <= 0)
        {
            return null;
        }

        // Both pointers start at the head. Fast will be moved n nodes ahead of slow.
        ListNode? fast = head;
        ListNode? slow = head;

        // Advance fast exactly n nodes to create the fixed-distance gap.
        for (int i = 0; i < n; i++)
        {
            // If fast is already null before all n moves are completed,
            // the list contains fewer than n nodes.
            if (fast is null)
            {
                return null;
            }

            fast = fast.Next;
        }

        // If fast is null exactly after n moves, n equals the list length.
        // Slow is still at the head, which is the nth node from the end.
        if (fast is null)
        {
            return slow!.Value;
        }

        // Move both pointers together so the n-node gap stays unchanged.
        while (fast is not null)
        {
            fast = fast.Next;
            slow = slow!.Next;
        }

        // Fast is now past the end, so slow is exactly the nth node from the end.
        return slow!.Value;
    }

    public static void Main()
    {
        // Build the exact diagram example: 1 -> 2 -> 3 -> 4 -> 5 -> 6.
        ListNode head = new ListNode(
            1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6))))));

        int n = 2;

        // Find the value stored in the second node from the end.
        int? result = NthFromEnd(head, n);

        // The diagram's expected result is 5.
        Console.WriteLine(result?.ToString() ?? "null");
    }
}
Time & Space Complexity

Let L be the number of nodes in the linked list. The time complexity is O(L). The fast pointer moves only forward, and the slow pointer also moves only forward. Neither pointer revisits an earlier node. The algorithm may stop early when the list is shorter than n. The auxiliary space complexity is O(1) because it stores only the fast pointer, the slow pointer, and a loop counter. It does not create an array, stack, map, or copy of the list.

Where it is used

This fixed-gap two-pointer pattern is useful with singly linked lists when we need a position relative to the end but cannot move backward. A common example is finding the kth or nth node from the end without first storing every node or making a separate pass only to calculate the list length.

Why Interviewers Ask This

This problem checks whether you recognize the fixed-gap two-pointer pattern for a singly linked list. It tests whether you can reason about node references without moving backward, maintain a simple invariant, and handle boundary cases such as n equal to or greater than the list length. It also checks whether your C# code matches your explanation and whether you can state the O(L) time and O(1) auxiliary space accurately.

Common interview mistakes

A common mistake is moving fast only n - 1 steps, which breaks the fixed n-node gap used by this solution. Another mistake is moving slow during the initial n fast-pointer moves. Candidates may also forget to check whether fast becomes null before all n moves are completed, which is how a short list is detected. Another error is mishandling the case where n equals the list length. Finally, returning the node reference instead of its value would not match the output shown in the diagram.

Interview tip

State the invariant clearly while drawing a small example: after the first n moves, fast stays exactly n nodes ahead of slow. Once that is clear, it is easy to explain why slow is at the nth node from the end when fast reaches null.

Interviewer may ask next
What if the method must return the actual node instead of its value?

The pointer movement stays the same. I would change the return type from int? to ListNode? and return slow instead of slow.Value. If the list is shorter than n, the method still returns null. The fixed-gap invariant is unchanged. The time complexity remains O(L), and the auxiliary space remains O(1). The tradeoff is that callers receive a node reference and can access or modify the list starting from that node.

What happens if n is greater than the number of nodes in the list?

The current algorithm already handles that case. While moving fast forward n times, it checks fast before every move. If fast is null before all n moves are completed, the list contains fewer than n nodes, so the method returns null immediately. No different algorithm is needed. The worst-case time remains O(L), and the auxiliary space remains O(1).

68. Merge two sorted linked lists.CodingEasy

Question Details

Describe how to merge by rewiring nodes, preserving order, and avoiding unnecessary allocations.

Short Interview Answer (30-60 seconds)

I would merge the two sorted lists by rewiring their existing nodes. I keep pointers p and q at the current nodes and tail at the end of the merged list. A dummy helper node makes the first link simple. I compare p.val and q.val, attach the smaller node, advance only that list pointer, and then advance tail. When one list ends, I attach the remaining suffix. This takes O(n + m) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We have two linked lists that are already sorted from small to large. We need to combine them into one sorted list. We should reuse the existing input nodes instead of creating a replacement node for every value. We compare the first unused node from each list and connect the smaller one to the result. A dummy helper node gives us an easy starting point. When one list becomes empty, the rest of the other list can be attached directly because it is already sorted.

Useful Questions to Ask the Interviewer
  1. May I modify the input lists by changing their next pointers?
  2. Should duplicate values remain in the merged list?
  3. Is using one dummy helper node acceptable?
Merge two sorted linked lists. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is two sorted singly linked lists. The diagram uses List 1 = 1 → 2 → 4 → 7 → null and List 2 = 1 → 3 → 4 → 6 → null. We must return the head of one sorted linked list that reuses the input nodes. The final result is 1 → 1 → 2 → 3 → 4 → 4 → 6 → 7 → null.

2. Choose the merge approach

Keep p at the first unmerged node of List 1 and q at the first unmerged node of List 2. Keep tail at the last node already connected to the merged list. Start tail at one dummy helper node. While p and q are both non-null, compare p.val and q.val. Link the smaller node after tail. The code uses p.val <= q.val, so if the values are equal, the node from List 1 is taken first.

3. Initialize the state

Create dummy and set tail = dummy. Set p = list1 and q = list2. The dummy node is only a helper. It is not part of the returned list. The real result will start at dummy.next.

4. Walk through the example

Step 1: Compare 1 and 1. Because the condition is p.val <= q.val, take the List 1 node 1. The merged prefix is 1. Move p to 2. Step 2: Compare 2 and 1. Take the List 2 node 1. The merged prefix is 1 → 1. Move q to 3. Step 3: Compare 2 and 3. Take the List 1 node 2. The merged prefix is 1 → 1 → 2. Move p to 4. Step 4: Compare 4 and 3. Take the List 2 node 3. The merged prefix is 1 → 1 → 2 → 3. Move q to 4. Step 5: Compare 4 and 4. Take the List 1 node 4 because the code uses <=. The merged prefix is 1 → 1 → 2 → 3 → 4. Move p to 7. Step 6: Compare 7 and 4. Take the List 2 node 4. The merged prefix is 1 → 1 → 2 → 3 → 4 → 4. Move q to 6. Step 7: Compare 7 and 6. Take the List 2 node 6. The merged prefix is 1 → 1 → 2 → 3 → 4 → 4 → 6. Move q to null. Step 8: The loop stops because q is null. Attach the remaining List 1 suffix, which is node 7. The final list is 1 → 1 → 2 → 3 → 4 → 4 → 6 → 7 → null.

5. Explain why the result is correct

The important invariant is that the nodes from dummy.next through tail are always sorted, and p and q point to the first nodes that have not yet been merged. Because each input list is sorted, choosing the smaller current node cannot skip a smaller value later in that same list. When one list ends, every remaining node in the other list is already in sorted order, so that entire suffix can be connected directly.

6. Explain the C# implementation

The loop runs while both p and q are non-null. If p.val <= q.val, tail.next is set to p and p advances. Otherwise, tail.next is set to q and q advances. After either branch, tail moves to tail.next. When the loop ends, tail.next is assigned to whichever pointer is still non-null. Finally, return dummy.next so the dummy helper itself is excluded from the answer.

7. Explain complexity and edge cases

If List 1 has n nodes and List 2 has m nodes, the time complexity is O(n + m) because each input node is processed at most once. Auxiliary space is O(1) because only one dummy node and a constant number of references are used. If one list is empty, the other list becomes the result. If both are empty, the result is null. If all nodes in one list are smaller, that list is consumed first and the other suffix is attached directly.

Key Insight / Why This Solution Works

The key insight is that both input lists are already sorted, so only their current first unmerged nodes need to be compared. Keep p and q at those nodes and tail at the end of the merged prefix. The invariant is that everything from dummy.next through tail is already sorted, while p and q begin the two remaining sorted suffixes. Linking the smaller current node preserves this invariant. When one suffix becomes empty, the other suffix is already sorted and can be attached in one operation.

Code
using System;

public sealed class ListNode
{
    public int val;
    public ListNode? next;

    public ListNode(int val = 0, ListNode? next = null)
    {
        this.val = val;
        this.next = next;
    }
}

public static class Program
{
    public static void Main()
    {
        // Build List 1 from the diagram: 1 -> 2 -> 4 -> 7 -> null.
        ListNode list1 = new ListNode(1, new ListNode(2, new ListNode(4, new ListNode(7))));

        // Build List 2 from the diagram: 1 -> 3 -> 4 -> 6 -> null.
        ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4, new ListNode(6))));

        // Merge by rewiring the existing input nodes.
        ListNode? merged = MergeTwoLists(list1, list2);

        // Print the exact merged result from the diagram.
        PrintList(merged);
    }

    public static ListNode? MergeTwoLists(ListNode? list1, ListNode? list2)
    {
        // The dummy helper gives tail a fixed starting node.
        // It is not included in the returned list.
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        // p and q point to the first unmerged nodes in the two lists.
        ListNode? p = list1;
        ListNode? q = list2;

        // Continue while both lists still have an unmerged node.
        while (p != null && q != null)
        {
            if (p.val <= q.val)
            {
                // Rewire tail to the current List 1 node.
                tail.next = p;

                // Advance only p because its node was consumed.
                p = p.next;
            }
            else
            {
                // Rewire tail to the current List 2 node.
                tail.next = q;

                // Advance only q because its node was consumed.
                q = q.next;
            }

            // Move tail to the node that was just attached.
            tail = tail.next;
        }

        // At most one list still has nodes.
        // Its remaining suffix is already sorted, so attach it directly.
        tail.next = p != null ? p : q;

        // Skip the dummy helper and return the real merged head.
        return dummy.next;
    }

    private static void PrintList(ListNode? head)
    {
        // Traverse the merged list without changing it.
        ListNode? current = head;

        if (current == null)
        {
            // This is the correct result when both input lists are empty.
            Console.WriteLine("null");
            return;
        }

        while (current != null)
        {
            // Print the current value before advancing.
            Console.Write(current.val);
            current = current.next;

            // Show the next link or the final null marker.
            Console.Write(current != null ? " -> " : " -> null");
        }

        Console.WriteLine();
    }
}
Time & Space Complexity

Let n be the number of nodes in List 1 and m be the number of nodes in List 2. The time complexity is O(n + m). Each input node is processed at most once. The auxiliary space complexity is O(1). Extra memory does not grow with n or m. The merge uses one dummy helper node and a constant number of references such as p, q, and tail.

Where it is used

This pattern is useful when two already sorted linked sequences must be combined while reusing their existing nodes. It is also the merge operation used by merge sort on linked lists. Rewiring nodes avoids allocating a replacement node for every input value.

Why Interviewers Ask This

This problem tests whether you recognize the standard merge pattern for sorted data and can manipulate linked-list references safely. The interviewer can see whether you preserve the remaining nodes, advance the correct pointer, handle equal values consistently, attach the final suffix, and maintain a useful invariant. It also checks whether you can write clear C# code and correctly explain O(n + m) time and O(1) auxiliary space.

Common interview mistakes

A common mistake is advancing both p and q after one comparison. Only the pointer whose node was attached should move. Another mistake is forgetting to move tail after linking a node. Candidates may also forget to attach the remaining suffix after the loop, create replacement nodes for every value instead of rewiring existing nodes, lose part of a list by changing references in the wrong order, or claim the auxiliary space is O(n + m) even though the merge itself uses only constant extra memory.

Interview tip

Say the invariant before you code: the chain through tail is already sorted, and p and q point to the first unmerged nodes. Then show how every comparison preserves that invariant.

Interviewer may ask next
Can you merge the lists without allocating a dummy helper node?

Yes. Choose the smaller first node as the merged head and set tail to it. Advance the pointer for that list, then continue with the same comparison and rewiring process. The invariant stays the same after initialization. Time remains O(n + m) and auxiliary space remains O(1). The tradeoff is extra special-case logic for choosing the first node and handling empty inputs.

What changes if the original input lists must remain unchanged?

Then their next pointers cannot be rewired. Keep the same two-pointer comparison order, but create a new output node for each selected value. The merged values stay sorted for the same reason as before. Time remains O(n + m), but auxiliary space becomes O(n + m) because the result requires newly allocated nodes. The tradeoff is extra memory in exchange for leaving both input lists unchanged.

69. Palindrome Linked List.CodingEasy

Question Details

Explain how to compare the first half with the reversed second half and what to restore afterward if needed.

Short Interview Answer (30-60 seconds)

I would use slow and fast pointers to find the middle, reverse the back half, and then compare the two sides node by node. If any values differ, I stop and return false. If they all match, the list is a palindrome. I then reverse that half again so the original list is restored. This runs in O(n) time and uses O(1) extra space.

Detailed Explanation

See the Code while reading this explanation.

This problem asks whether a chain of numbers reads the same from left to right and from right to left. I need to compare the front part with the back part without losing the original chain. The diagram shows a careful order. It finds the middle, turns the back part around, compares matching items one by one, and then puts the chain back the way it was. That fits well because each item is visited only a small number of times, and the original structure is restored before I return.

Useful Questions to Ask the Interviewer
  1. Is it okay to change the list temporarily if I restore it before returning?
  2. Do you want me to explain the restore step in the same way the diagram shows it?
Palindrome Linked List. diagram
How to Explain It in an Interview
1. Understand the input and output

The input is the head of a singly linked list. The output is true or false. I return true when the list reads the same in both directions.

2. Find the middle and reverse the back part

I use slow and fast pointers. Slow moves one step. Fast moves two steps. When fast reaches the end, slow is at the middle area. Then I reverse the part that starts at slow. This gives me the back side in reverse order.

3. Compare both sides

I set one pointer at the head and one pointer at the head of the reversed part. Then I compare the node values one by one. If one pair is different, I stop and return false. If all pairs match, the list is a palindrome.

4. Restore the original list

After the check, I reverse the same part again. That puts the nodes back in their original order. This matters because the question asks what to restore afterward if needed.

5. Explain why the result is correct

The key idea is symmetry. The left side of a palindrome must match the right side in reverse order. By reversing the back part, I turn that comparison into a simple front-to-front check. If every mirrored pair matches, the answer is true.

6. Explain the C# implementation

The IsPalindrome method handles the whole check. It first returns true for an empty list or a single node. Then it finds the middle with slow and fast pointers. Next it reverses the back part, compares the two sides, restores the list, and returns the boolean result. The ReverseList helper changes the next links one by one.

7. Explain complexity and edge cases

The time is O(n) because I pass through the list a constant number of times. The extra space is O(1) because I only use a few pointers. Important edge cases are an empty list, a single node, two equal nodes, and odd-length lists.

Key Insight / Why This Solution Works

The key idea is to use slow and fast pointers to find the middle, reverse the back part of the list, and compare mirrored nodes from the front and the reversed part. The invariant is simple: p1 walks from the head, and p2 walks from the reversed back part, so each pair must match for the list to be a palindrome. After the compare step, I reverse that same part again so the original list structure is restored.

Code
using System;

public static class Program
{
    public static void Main()
    {
        // Build the exact example from the diagram: 1 -> 2 -> 3 -> 2 -> 1.
        ListNode head =
            new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(2, new ListNode(1)))));

        Solution solution = new Solution();
        bool isPalindrome = solution.IsPalindrome(head);

        // Print the same result shown in the diagram.
        Console.WriteLine(isPalindrome ? "true" : "false");
    }
}

public class Solution
{
    public bool IsPalindrome(ListNode head)
    {
        // Empty list and single-node list are already palindromes.
        if (head == null || head.next == null)
        {
            return true;
        }

        // Find the middle area with slow and fast pointers.
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null)
        {
            slow = slow.next;
            fast = fast.next.next;
        }

        // Reverse the back part starting at slow, just like the diagram shows.
        ListNode second = ReverseList(slow);

        // Compare the front half with the reversed back part node by node.
        ListNode p1 = head;
        ListNode p2 = second;
        bool isPal = true;
        while (p2 != null)
        {
            // A single mismatch means the list is not a palindrome.
            if (p1.val != p2.val)
            {
                isPal = false;
                break;
            }

            p1 = p1.next;
            p2 = p2.next;
        }

        // Restore the original list by reversing the same part again.
        ReverseList(second);

        return isPal;
    }

    private ListNode ReverseList(ListNode head)
    {
        // prev holds the reversed part. curr walks the remaining nodes.
        ListNode prev = null;
        ListNode curr = head;

        while (curr != null)
        {
            // Save the next node before changing the link.
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }

        // prev is the new head of the reversed list.
        return prev;
    }
}

public class ListNode
{
    public int val;
    public ListNode next;

    public ListNode(int val = 0, ListNode next = null)
    {
        this.val = val;
        this.next = next;
    }
}
Time & Space Complexity

The list is visited only a constant number of times, so the time is O(n). The first pass finds the middle. The second pass reverses the back part. The third pass compares the values. The last pass restores the list. I only use a few node pointers, so the extra memory is O(1).

Where it is used

This pattern is useful when I need to check symmetry in a singly linked list and keep the original list usable afterward. It is common in interview problems and in code that validates linked data without copying it into an array.

Why Interviewers Ask This

They want to see whether I can work with linked list pointers without breaking the structure. This problem checks if I can find the middle, reverse part of a list, compare mirrored values, and then put the list back. It also shows whether I can explain why the method works, keep the code simple in C#, and state time and space complexity correctly.

Common interview mistakes

One common mistake is moving the fast pointer by one step instead of two, which breaks the middle search. Another is comparing the wrong nodes after the reverse step. Candidates also forget to restore the list before returning, even though the prompt asks about that. It is also easy to mix up node values and node references, or to keep checking after the first mismatch instead of stopping early.

Interview tip

Say the invariant out loud: slow finds the middle, the back part is reversed, mirrored nodes are compared, and then the list is restored.

Interviewer may ask next
What changes if the list must stay unchanged after the check?

This version already restores the list before returning. If the interviewer does not want any mutation at all, I would switch to a stack or copy the values first. That keeps the list untouched, but the extra space becomes O(n).

What changes if I want the code to return false and still restore the list?

Nothing changes in the main idea. I still stop on the first mismatch, then reverse the same part again before I return false. That keeps the original list structure and keeps the time and space bounds the same.

70. Rotate a linked list.CodingEasy

Question Details

Show how to shift the list by k positions, including how you handle k values larger than the list length.

Short Interview Answer (30-60 seconds)

I rotate the linked list to the right by k positions. First I count the nodes and find the tail. Then I reduce k with k % n so large values do not add extra work. I connect the tail to the head to make one circle, move to the new tail, and cut the circle there. This gives O(n) time and O(1) extra space.

Detailed Explanation

See the Code while reading this explanation.

This question asks me to move the last k nodes of a linked list to the front. If k is larger than the list length, I first shrink it with k % n, so the move stays the same. The main idea is simple. I count the list, make it circular once, find the new tail, and then break the circle at the right place. That fits this problem well because I only need pointer changes.

Useful Questions to Ask the Interviewer
  1. Can I change the list in place?
  2. Should I treat k as always non-negative?
Rotate a linked list. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a singly linked list and an integer k. The output is the same list rotated to the right by k positions. The node order changes, but the node values stay the same. The diagram shows that k can be larger than the list length, so I must reduce it first.

2. Choose the linked-list rotation trick

The clean solution is to count the nodes, connect the tail to the head, and treat the list as a circle. Then I move to the new tail and break the circle. This is better than moving one step at a time k times, because that would be slower.

3. Initialize the state

I start with a length counter, a tail pointer at the head, and the original head. I walk to the end once to find the tail and the length n. Then I compute k = k % n. If k becomes 0, the list does not change.

4. Walk through the example

The diagram uses the list 1 -> 2 -> 3 -> 4 -> 5 and k = 7. After I count the list, n = 5. Then I reduce k: 7 % 5 = 2. So the real rotation is by 2. I connect tail.next = head to make one circle. Then I move (n - k) - 1 = 2 steps from the head to find the new tail. That puts newTail on node 3. The new head is newTail.next, which is node 4. Finally, I set newTail.next = null to break the circle. The result is 4 -> 5 -> 1 -> 2 -> 3 -> null.

5. Explain why the result is correct

The circular link keeps every node connected while I move to the new tail. The node after the new tail is exactly the first node of the rotated list. Cutting the circle there gives the correct rotated order and keeps every node in the list.

6. Explain the C# implementation

The code first handles easy cases: null head, one node, or k = 0. Then it counts the list and remembers the tail. Next it reduces k with modulo so large values wrap around. If the reduced k is 0, it returns the original head. Otherwise it connects tail.Next to head, walks to the new tail, sets newHead to newTail.Next, breaks the circle, and returns newHead.

7. Explain complexity and edge cases

The time is O(n) because I count the list once and then move through part of it once more. The extra space is O(1) because I only use a few pointers and counters. The main edge cases are an empty list, one node, k = 0, and k larger than the list length.

Key Insight / Why This Solution Works

The key insight is to turn the linked list into a circle first. Then the rotation becomes a pointer problem, not a value-copy problem. I count the nodes, reduce k with modulo, connect the tail to the head, and move to the node just before the new head. The central invariant is that every node stays connected until I break the circle at the exact new tail. That gives the right rotated order in place.

Code
#nullable enable
using System;
using System.Text;

public sealed class ListNode
{
    public int val;
    public ListNode? next;

    public ListNode(int x)
    {
        val = x;
    }
}

public static class Program
{
    public static void Main()
    {
        // Build the exact example from the diagram.
        ListNode? head = BuildList(new[] { 1, 2, 3, 4, 5 });
        int k = 7;

        // Rotate the list to the right by k positions.
        ListNode? rotated = RotateRight(head, k);

        // Print the final linked list in the same order shown in the diagram.
        Console.WriteLine(ToArrowString(rotated));
    }

    public static ListNode? RotateRight(ListNode? head, int k)
    {
        // Empty list, one node, or no rotation means the list stays the same.
        if (head == null || head.next == null || k == 0)
        {
            return head;
        }

        // Walk to the tail once so we can count the length and remember the end node.
        int n = 1;
        ListNode tail = head;
        while (tail.next != null)
        {
            tail = tail.next;
            n++;
        }

        // Reduce large k values so the rotation stays within the list length.
        k %= n;

        // If k becomes 0 after modulo, the list does not change.
        if (k == 0)
        {
            return head;
        }

        // Make the list circular so we can find the new head with pointer moves only.
        tail.next = head;

        // The new tail is (n - k - 1) steps from the old head.
        int stepsToNewTail = n - k - 1;
        ListNode newTail = head;
        for (int i = 0; i < stepsToNewTail; i++)
        {
            newTail = newTail.next!;
        }

        // The node after the new tail becomes the new head.
        ListNode newHead = newTail.next!;

        // Break the circle so the result is a normal singly linked list again.
        newTail.next = null;

        return newHead;
    }

    private static ListNode? BuildList(int[] values)
    {
        // Build the list node by node in the same order as the array.
        if (values.Length == 0)
        {
            return null;
        }

        ListNode head = new ListNode(values[0]);
        ListNode current = head;

        for (int i = 1; i < values.Length; i++)
        {
            current.next = new ListNode(values[i]);
            current = current.next;
        }

        return head;
    }

    private static string ToArrowString(ListNode? head)
    {
        // Convert the list to the visual arrow format used in the diagram.
        StringBuilder sb = new StringBuilder();
        ListNode? current = head;

        while (current != null)
        {
            sb.Append(current.val);
            sb.Append(" -> ");
            current = current.next;
        }

        sb.Append("null");
        return sb.ToString();
    }
}
Time & Space Complexity

I count the nodes, then I walk to the right place for the new tail. That takes linear time, so the time is O(n). I do not build a new list. I only keep a few pointers and counters, so the extra memory is O(1). The modulo step makes sure a very large k does not add more work.

Where it is used

This pattern is useful when you need to move the end of a sequence to the front without copying data. It fits linked lists, circular schedules, rotating queues, and other in-place reordering tasks.

Why Interviewers Ask This

The interviewer wants to see if I can reason about linked-list pointers without losing nodes. They also want to see if I know how modulo reduces a large rotation, how to handle edge cases, and how to explain an in-place solution clearly. This question checks careful thinking, not just memorized code.

Common interview mistakes

A common mistake is to forget k % n. That makes the code do extra work for large k. Another mistake is to stop at the wrong node before breaking the circle. That shifts the list by the wrong amount. A third mistake is to forget the empty-list and single-node checks. Another is to cut the list before saving the new head, which loses part of the list. The last common mistake is an off-by-one error when moving to the new tail.

Interview tip

Say the pointer order out loud: count, connect, move, cut. That makes the off-by-one step much easier to check.

Interviewer may ask next
How would you rotate the list to the left instead of the right?

I would still count the nodes and use modulo. A left rotation by k is the same as a right rotation by n - k. So I would reuse the same in-place circular-list idea. The time stays O(n) and the extra space stays O(1).

How would you do this without changing the original list?

I would need extra memory. I could copy the node values into a new list in rotated order, or rebuild a new linked list from the rotated sequence. That keeps the original list unchanged, but the extra space becomes O(n).

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.