Senior · Trace · Parsing

Trace the complete program for parse quoted fields with an explicit state machine. State the exact stdout, then justify the state transitions and relevant edge conditions.

Think through your answer before opening the explanation.

Coding interviews

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

Console.WriteLine(string.Join("|",Parse("a,\"b,c\",\"d\"\"e\",",100,10)));
bool rejected=false; try { Parse("\"unfinished",100,10); } catch(FormatException) { rejected=true; } Console.WriteLine("unclosed rejected:"+rejected.ToString().ToLowerInvariant());
static string[] Parse(string text,int maxLength,int maxFields) {
    if(text.Length>maxLength || maxFields<1) throw new ArgumentOutOfRangeException();
    var fields=new List<string>(); var value=new StringBuilder(); int state=0;
    void End() { if(fields.Count>=maxFields) throw new FormatException("too many fields"); fields.Add(value.ToString()); value.Clear(); }
    for(int i=0;i<text.Length;i++) { char c=text[i];
        if(state==1) { if(c=='"') { if(i+1<text.Length && text[i+1]=='"') { value.Append('"'); i++; } else state=2; } else value.Append(c); }
        else if(c==',') { End(); state=0; }
        else if(state==2) throw new FormatException("after quote");
        else if(c=='"') { if(value.Length!=0) throw new FormatException("quote position"); state=1; }
        else value.Append(c);
    }
    if(state==1) throw new FormatException("unclosed quote"); End(); return fields.ToArray();
}
Reveal answer and reasoning

The output lines, in order, are “a|b,c|d"e|”; “unclosed rejected:true”. The unquoted a becomes the first field. The comma inside quoted b,c remains data, and doubled quotes inside d double-quote e decode into one literal quote. The final delimiter contributes an empty fourth field. A separate unclosed opening quote fails instead of silently accepting partial data. State zero is unquoted, one quoted and two after a closing quote.

End materializes the current field, clears its builder and enforces the field-count limit. The final End call is necessary even after a trailing delimiter because the grammar preserves a final empty field. Doubling a quote consumes the second quote immediately, ensuring it is data rather than a second closing transition.

Each input code unit is inspected a bounded number of times, giving O(n) time and O(n) output/buffer storage within the accepted input limit. The maxFields check bounds the number of fields. StringBuilder and final strings still allocate; this is not a zero-allocation parser. Splitting on commas first destroys the information needed to recognize quoted delimiters. Accepting arbitrary characters after a closing quote creates ambiguous recovery behavior.

Limits must apply before unbounded accumulation, and the parser should not return a partially accepted record after a grammar error.

What a strong answer covers

  • Exact output: a|b,c|d"e| | unclosed rejected:true
  • The unquoted a becomes the first field.
  • Splitting on commas first destroys the information needed to recognize quoted delimiters.

Common pitfall

Splitting on commas first destroys the information needed to recognize quoted delimiters. Accepting arbitrary characters after a closing quote creates ambiguous recovery behavior. Limits must apply before unbounded accumulation, and the parser should not return a partially accepted record after a grammar error.

Follow-up questions

  1. Test an empty record, an empty quoted field, an embedded quote in an unquoted field and characters after a closing quote. Hint: specify the expected field count for an empty input before deciding whether it is an error.
  2. Which state must survive a chunk boundary immediately after the first quote of a doubled quote?

Self-review

Choose what to revisit. These marks are your own assessment and are separate from lesson completion.

Primary references

Open lesson and code ↗

← All interview questions