Wednesday, June 15, 2005

The Microsoft Interview -Part 2

This is in continuation to the earlier post in which I told a part of my story with the Microsoft interviewing process. Basically the phone interview part.

Part 1 ended with me about to have my first real interview on the 30th of August.
My interviewer was to be an SDET team lead on the avalon team.

I went around 20 minutes earlier, waited for my interview. He then came out (10 minutes earlier then scheduled) to take me to the interview room.


Once there he started things up nice and slow with a quick review of the contents of my resume with me giving very brief description for some of the projects when asked.

He then asked me how I would rate my C++ programming skills, out of ten. And then my .Net programming skills. My general advice for you here not to be too cocky. You will soon afterwards get the chance to demonstrate your skills, so you'd better underestimate your skills and let the interviewer find out you're better than you think you are than to raise the bar for yourself and then struggle to meet the expectations.
For C++ for example, just think for a second about what 10 out of 10 means. In my opinion only Stroustrup can claim a perfect 10 out of 10. Putting that in mind I for one would be very doubtful of anyone giving themselves 9, or even 8 out of 10.

Next question was about reflection in .Net. I was asked to define what it was and then how it was made possible (the basic inner workings of the framework). I had no clue at the time of the interview about how reflection was made possible, but I had a very basic idea about reflection. I tried to state what I knew without making stuff up or trying to look more knowledgeable than I am.
I was then asked about "App domains" in .Net. Which I had no idea about. So I stated that I've never even heard of them.
It's hard to say "I have no idea" in an interview but just remember that the MS interviewers (all 6 I've met) are very smart and knowledgable people. I probably can't stress that enough but don't try to make stuff up or lie about your knowledge. It'll be detected and the interviewer will probably go along with you till you hit a dead end. At least that's what I'd do if I was interviewing someone and they bluffed.

And then for the fun part. A white board was just behind me, my interviewer walks me to it and then begins asking coding questions that I'm required to solve using a marker pen and the white board. Some required just illustrating your algorithm for the solutions with discussions about the efficiency of the solution and some required to go all the way till writing working code for solving the problem. I wrote my solutions in C++ but that was probably because I stated that C++ was the language I'm most comfortable with. Plus it gives the interviewer a pool of pointer related problems that's usually good material for interviews.

My coding questions were:
  • What does this C++ macro accomplish:
    #define func(a, b) a ^= b ^= a ^= b
  • How do I delete an element from a singly linked list given a pointer to the node to delete? What are the restrictions on the nodes that can be deleted using that solution?
  • Given an array with integers sorted in ascending order but with duplicates. Write a function to compact the array so that it contains no duplicated. Ideally, the function would take a pointer to the first element in the array (int*) , and the length of the array. It should do the compaction in place (no extra memory allocation) and return the new length of the array. The optimal solution is of O(n) complexity.
  • Write a function to count the bits whose value is equal to 1 in a byte.
I was asked one more question that I can't recall in detail right now. But you get the idea.
In all questions where I was required to write code, a discussion ensued about the various test cases I could test the function with. General advice for testing questions is to be as exhaustive and creative as possible. Work your way starting with the most obvious cases (boundary checks, abnormal parameter values) and up to the most complex cases you can think of or even potential performance issues.

The interview lasted for a little more than 2 hours. I enjoyed every minute of it even though I was very tense and nervous for most of it.
Writing code on a white board with someone watching is not easy at all, try to get all the practice you can before the interview. A good starting point for a ton of information about the MS interviewing process as well as sample questions can be found here.

A couple of weeks later I was informed that I passed my interviews. Which at the moment I thought was the final step. But as it turns out, I should instead get ready for another (and final) round of interviews that were to come some time in January 2005.

So I find myself preparing all over again, this time determined to fill up the "holes" that I discovered during my first interview. Mainly brush up on my C++ knowledge and try to know as much as I could about the .Net framework. Not the syntax and semantics of various .Net languages, but rather the framework features and how it's made possible. This proved very interesting and I'll be listing the resources I used as well the account of my final round of interviews.

So stay tuned.

9 comments:

Anonymous said...

ok this is weird.
I was thinking of the a^=b^=a^=b thing chris..
and i wanted to see it working so i wrote a tiny c++ program to test it,
then i thought i would share it with my co-workers, and i asked them if they can figure out what it does, and one of them answered that it would always return zero, and i go like that's not correct, then he decides to write tiny C# program to test it, and boom, he gets a zero. ALWAYS.
Actually i didn't believe him, and i wrote my own one, and it turned out that he's correct it always gives you zero in .net.

Any explanation chris...!!!

May be MS need to adjust their .net compiler.

Christian said...

Very interesting indeed. Thanks for bringing that up marco!

Apparently other people have found out about that earlier and had interesting conversations about it too.

The most interesting discussion I got is here

It's pretty long, I've read it all but it's rather hard to sum up. The conclusions you'd get from the discussion are that a)It's apparently intentional semantics of the C# language b)The behavior is in fact undefined in C and C++ standards even though almost all compilers will produce the "expected" output. c)Not sure about why C# does that but a couple of credible theories are that it may be using the original x value when performing the last (rightmost) x ^= y and hence x is in effect being XORed with itself (the new y value) and hence produces the zero, or is that the order of evaluating expressions might be a little different.

Although I'm not sure yet about this, I find it very interesting. I'll try to get an exact answer for it when I'm actually at MS, if I made some friends from the c# compiler team that is :))

Of course it goes without saying that this line of code is purely meant to be a mental exercise and not exactly what you'd call good programming style :)

Mohamed Moshrif said...

It's a very common bug, but this time it happened in the C# compiler: D: D
I'll discuss how this error is happened:

The expression:
func (a,b) a ^= b ^= a ^= b
It can be evaluated in two ways, one of them is wrong (will lead to b = a, and a = 0) and the other will be true which will lead that they will be swapped, I'll tell you the both ways, and the error in the wrong way.

The right way:

Because in every step, a and b are changing, we've must know that the a in the left most side of the expression is not like that at the right most, so we'll rename the expression to be:

a2 ^= b1 ^= a1 ^= b0;

Now we can easily state that:
a1 = a0 ^ b0;
b1 = a1 ^ b0;
a2 = a1 ^ b1;

fo, a-final = a2 = a1 ^ b1 = (a0 ^ b0) ^ ((a0 ^ b0) ^ b0) = (a ^ b) ^ (a ^ b ^ b) = a ^ b ^ a = b;

And the same in b-final = a1 ^ b0 = (a0 ^ b0) ^b0 = a ^ b ^ b = a;
So now the numbers had been swapped.

Now, let's look at the wrong evaluation for the expression:

If we tried to evaluate the expression from right to left:
So a ^= b ^= a ^= b will be in this way:

a = a ^ b;
b = b ^ a;
a = a ^ b;

so in this case, a-final = a ^ (b ^ (a ^b));//here's the error, they hadn't put in mind that the first a had been changed too, so they omit the extra a, I mean the a in the third equation is not equal to a, it's equal to a ^ b, so they omit the extra b which leads to that the expression will be evaluated to a ^ a at the end, not a ^ b ^ a which will lead to the final result to be b

Christian said...

Actually I came up with a way to be able to exactly tell what's happening.

Creating a and b as read/write integer properties for a class and then using them in the expression will allow you to trace the order of evaluation of the expression exactly.

Add these 2 properties to your c# code:
public int a
{
get
{
MessageBox.Show("Getting a=" + _a.ToString());
//OR USE System.Console.WriteLine()
return _a;
}
set
{
MessageBox.Show("Setting a=" + value.ToString());
//OR USE System.Console.WriteLine()
_a = value;
}
}

public int b
{
get
{
MessageBox.Show("Getting b=" + _b.ToString());
//OR USE System.Console.WriteLine()
return _b;
}
set
{
MessageBox.Show("Setting b=" + value.ToString());
//OR USE System.Console.WriteLine()
_b = value;
}
}

and add 2 integer member variables to hold the values of a and b, calling them _a and _b respectively, making sure you initialize them (not through the properties)

write the expression then and take notice of the output. The output will be similar to this, provided that you initialized _a with the value 2 and _b with the value 3:

Getting a=2
Getting b=3
Getting a=2
Getting b=3
Setting a=1
Setting b=2
Setting a=0

So it's obvious that it's using the old value for the last assignment for a resulting in an a^a with a zero value.

Mohamed Moshrif said...

One of my friend (MAAK) had an idea for viewing the assembly code of the generated code, and here you're the results:

In C++:

mov edx,dword ptr [ebp-4] //put the a into register edx
xor edx,dword ptr [ebp-8] //xor the b with edx and store it in edx (a ^= b)
mov dword ptr [ebp-4],edx //save edx to a
mov eax,dword ptr [ebp-8] //put the b into register eax
xor eax,dword ptr [ebp-4] //xor the a with eax and store it in eax (b ^= a[new a])
mov dword ptr [ebp-8],eax //save eax to b
mov ecx,dword ptr [ebp-4] //put the a into register ecx
xor ecx,dword ptr [ebp-8] //xor the b with ecx and store it in ecx (a ^= b [new b and a])
mov dword ptr [ebp-4],ecx //save ecx to a

In C#:

mov edi,dword ptr [ebp-8] //put the a into register edi (this causes the whole mess)
mov ebx,dword ptr [ebp-0Ch] //put the b into register ebx
mov eax,dword ptr [ebp-0Ch] //put the b into register eax
xor dword ptr [ebp-8],eax //xor the a with eax and store it in a (a ^= b)
xor ebx,dword ptr [ebp-8] //xor the ebx with a and store it in ebx (b ^= a[new a])
mov dword ptr [ebp-0Ch],ebx //save ebx to b (now b is correctly evaluated)
xor edi,dword ptr [ebp-0Ch] //xor the edi (CONTAINING OLD a!!) with b and save it to edi
mov dword ptr [ebp-8],edi //save edi to a!!!!

So the error now is obvious, and it's in the compiler itself

Mohamed Moshrif said...

The thing i forgot to mention, the optimization of the C# compiler had removed this line:

mov dword ptr [ebp-4],edx //save edx to a

So the final result had used the old value of a instead of the new one.

Anonymous said...

Well i'm glad u guys solved the vague behaviour.
But i'm really curious to know if that's a Bug, or it's intended for semantic reasons as u chris mentioned.
coz as u know, C# is the "pampered developer favourite language." so i'm thinking may be MS was trying to be so sweet, and make developers avoid mistakes... :D

Mohamed Moshrif said...

I got two comments from 2 of the C# compiler's team members, the first one was from Daigo Hamura:

The current behavior in the C# compiler is correct.

The mistake in you logic is that you assume that, because the __expression is evaluated right-to-left for precedence, the variable locations are also taken from right to left.

The runtime evaluation of the __expression is (correctly) from left to right.

[I am adapting from the C# language specification here]

An operation of the form x op= y
is evaluated as x = x op y, except that x is evaluated only once.

The run-time processing of a simple assignment of the form x = y consists of the following steps:
o x is evaluated to produce the variable.
o y is evaluated
o The value resulting from the evaluation of y is stored into the location given by the evaluation of x.


So, ...
a ^= b ^= a ^= b;

can be re-written as
a ^= b ^= (a = a ^ b);
a ^= (b = b ^ (a = a ^ b));
a = a ^ (b = b ^ (a = a ^ b));

Applying the runtime processing rules above,
The lhs a is evaluated to produce a variable, which is used for the first two occurences of a ("a = a ....."). [The most important thing to remember is that this evaluation happens before everything else to the right.]
Then y, or a ^ (b = b ^ (a = a ^ b)), is evaluated
And finally the result stored in the location given by the evaluation of a.

In order to evaluate the 'y' part of the assignment above,
b = b ^ (a = a ^ b) needs to be evaluated similarly.
b is evaluated to produce a variable, and b ^ (a = a ^ b) is evaluated and the result stored in the location given by the evaluation of b.

a = a ^ b is evaluated similarly.
a is evaluated to produce a variable, and a ^ b is evaluated and the result stored.


Hope this helps


The other comment was from Gus Perez the lead of the C# Compiler QA team:


Hey Mohamed,

Regarding the issue you mentioned I'm 99% sure this is by design and
will try to look into it at some point but I'm swamped right now so I
don't know when I'll be able to. C++ and C# have different rules in
some
cases and I'm pretty sure this is one of those. Actually, I think the
swapping via xor trick takes advantage of some "undefined" behavior of
the language to work. But I can't say for sure without looking into it
a
bit more.

Thanks!

gp.::http://blogs.msdn.com/gusperez

Anonymous said...

it was pretty awesome.