Machine Coding Problem

Survey Monkey (Survey System)

maco60macoAllproductivitydynamic-form-modeling
Commonly Asked By:QualtricsGoogleMicrosoft

Functional Scope (In-Scope)

  • Dynamic Form Schemas: Create surveys composed of diverse question types (Choice, Text, Scale).
  • Skip-Logic Rule Routing: Define skip rules (e.g., if Q1 is 'No', skip to Q5) to navigate forms dynamically.
  • Strict Path Validation: Verify that submitted survey responses follow a valid logical path on form completion.
  • Analytics Aggregation: Aggregate answers by question type (choice percentages, mean rating scores) using the Visitor Design Pattern.

Explicit Boundaries (Out-of-Scope)

  • No Third-Party Email Dispatchers: Excludes direct integration with Mailchimp or transactional SMTP client configurations.
  • No Machine Learning Sentiment Extractors: Bypasses automatic NLP sentiment analysis for text-based answers.

Clean reference designs demonstrating dynamic skip logic in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;

enum QuestionType { CHOICE, SCALE, TEXT }

// Visitor Pattern for dynamic analytics aggregation
interface QuestionVisitor {
    void visit(ChoiceQuestion q);
    void visit(ScaleQuestion q);
    void visit(TextQuestion q);
}

abstract class Question {
    private final String id;
    private final String text;
    private final QuestionType type;

    public Question(String id, String text, QuestionType type) {
        this.id = id;
        this.text = text;
        this.type = type;
    }

    public String getId() { return id; }
    public String getText() { return text; }
    public QuestionType getType() { return type; }

    public abstract boolean validateResponse(Object answer);
    public abstract void accept(QuestionVisitor visitor);
}

class ChoiceQuestion extends Question {
    private final List<String> options;

    public ChoiceQuestion(String id, String text, List<String> options) {
        super(id, text, QuestionType.CHOICE);
        this.options = new ArrayList<>(options);
    }

    public List<String> getOptions() { return options; }

    @Override
    public boolean validateResponse(Object answer) {
        if (!(answer instanceof String)) return false;
        return options.contains(answer);
    }

    @Override
    public void accept(QuestionVisitor visitor) {
        visitor.visit(this);
    }
}

class ScaleQuestion extends Question {
    private final int minVal;
    private final int maxVal;

    public ScaleQuestion(String id, String text, int minVal, int maxVal) {
        super(id, text, QuestionType.SCALE);
        this.minVal = minVal;
        this.maxVal = maxVal;
    }

    public int getMinVal() { return minVal; }
    public int getMaxVal() { return maxVal; }

    @Override
    public boolean validateResponse(Object answer) {
        if (!(answer instanceof Integer)) return false;
        int val = (Integer) answer;
        return val >= minVal && val <= maxVal;
    }

    @Override
    public void accept(QuestionVisitor visitor) {
        visitor.visit(this);
    }
}

class TextQuestion extends Question {
    public TextQuestion(String id, String text) {
        super(id, text, QuestionType.TEXT);
    }

    @Override
    public boolean validateResponse(Object answer) {
        if (!(answer instanceof String)) return false;
        return !((String) answer).trim().isEmpty();
    }

    @Override
    public void accept(QuestionVisitor visitor) {
        visitor.visit(this);
    }
}

class SkipRule {
    private final String expectedAnswer;
    private final String jumpToQuestionId;

    public SkipRule(String expectedAnswer, String jumpToQuestionId) {
        this.expectedAnswer = expectedAnswer;
        this.jumpToQuestionId = jumpToQuestionId;
    }

    public String getExpectedAnswer() { return expectedAnswer; }
    public String getJumpToQuestionId() { return jumpToQuestionId; }
}

class SurveyResponse {
    private final String responseId;
    private final String surveyId;
    private final Map<String, Object> answers = new ConcurrentHashMap<>();

    public SurveyResponse(String responseId, String surveyId) {
        this.responseId = responseId;
        this.surveyId = surveyId;
    }

    public String getResponseId() { return responseId; }
    public String getSurveyId() { return surveyId; }
    public void putAnswer(String qId, Object answer) { answers.put(qId, answer); }
    public Object getAnswer(String qId) { return answers.get(qId); }
    public Map<String, Object> getAnswers() { return answers; }
}

class Survey {
    private final String id;
    private final List<Question> questions = new CopyOnWriteArrayList<>();
    private final Map<String, List<SkipRule>> skipRules = new ConcurrentHashMap<>();

    public Survey(String id) {
        this.id = id;
    }

    public String getId() { return id; }

    public void addQuestion(Question q) {
        questions.add(q);
    }

    public void addSkipRule(String questionId, String answer, String jumpToId) {
        skipRules.computeIfAbsent(questionId, k -> new CopyOnWriteArrayList<>())
                 .add(new SkipRule(answer, jumpToId));
    }

    public Question getQuestion(String qId) {
        for (Question q : questions) {
            if (q.getId().equals(qId)) return q;
        }
        return null;
    }

    public String getNextQuestionId(String currentId, Object answer) {
        // Evaluate skip-rules
        List<SkipRule> rules = skipRules.get(currentId);
        if (rules != null && answer != null) {
            for (SkipRule r : rules) {
                if (r.getExpectedAnswer().equalsIgnoreCase(answer.toString())) {
                    return r.getJumpToQuestionId();
                }
            }
        }

        // Sequential fallback
        for (int i = 0; i < questions.size() - 1; i++) {
            if (questions.get(i).getId().equals(currentId)) {
                return questions.get(i + 1).getId();
            }
        }
        return null; // Terminate survey
    }

    public List<Question> getQuestions() { return questions; }
}

class SurveyResponseValidator {
    public boolean validate(Survey survey, SurveyResponse response) {
        if (survey.getQuestions().isEmpty()) return true;

        String currentId = survey.getQuestions().get(0).getId();
        Set<String> expectedPath = new HashSet<>();

        while (currentId != null) {
            expectedPath.add(currentId);
            Question q = survey.getQuestion(currentId);
            Object ans = response.getAnswer(currentId);

            if (ans == null) {
                System.out.println("Validation fail: Missing answer on active logical path for question " + currentId);
                return false;
            }

            if (!q.validateResponse(ans)) {
                System.out.println("Validation fail: Invalid format value for question " + currentId + ": " + ans);
                return false;
            }

            currentId = survey.getNextQuestionId(currentId, ans);
        }

        // Check if any extra answers exist that were supposed to be skipped
        for (String qId : response.getAnswers().keySet()) {
            if (!expectedPath.contains(qId)) {
                System.out.println("Validation warning: Extra answer registered for skipped question " + qId);
                return false;
            }
        }

        return true;
    }
}

// Analytical Visitor implementation
class AnalyticsAggregator implements QuestionVisitor {
    private final List<SurveyResponse> responses;
    
    public AnalyticsAggregator(List<SurveyResponse> responses) {
        this.responses = responses;
    }

    @Override
    public void visit(ChoiceQuestion q) {
        Map<String, Integer> counts = new HashMap<>();
        for (SurveyResponse res : responses) {
            Object ans = res.getAnswer(q.getId());
            if (ans != null) {
                counts.put(ans.toString(), counts.getOrDefault(ans.toString(), 0) + 1);
            }
        }
        System.out.println("Choice Question [" + q.getText() + "] Results: " + counts);
    }

    @Override
    public void visit(ScaleQuestion q) {
        double total = 0.0;
        int count = 0;
        for (SurveyResponse res : responses) {
            Object ans = res.getAnswer(q.getId());
            if (ans instanceof Integer) {
                total += (Integer) ans;
                count++;
            }
        }
        double avg = count == 0 ? 0.0 : total / count;
        System.out.println("Scale Question [" + q.getText() + "] Average Score: " + String.format("%.2f", avg));
    }

    @Override
    public void visit(TextQuestion q) {
        List<String> entries = new ArrayList<>();
        for (SurveyResponse res : responses) {
            Object ans = res.getAnswer(q.getId());
            if (ans != null) {
                entries.add(ans.toString());
            }
        }
        System.out.println("Text Question [" + q.getText() + "] Responses: " + entries);
    }
}

// Complete Simulation
public class Main {
    public static void main(String[] args) {
        Survey survey = new Survey("S-101");

        // Add Questions
        survey.addQuestion(new ChoiceQuestion("Q-1", "Do you own a car?", Arrays.asList("Yes", "No")));
        survey.addQuestion(new TextQuestion("Q-2", "What brand of car is it?"));
        survey.addQuestion(new ScaleQuestion("Q-3", "Rate your car satisfaction", 1, 5));

        // Skip logic: If Q-1 answer is "No", jump straight to Q-3 (skip Q-2)
        survey.addSkipRule("Q-1", "No", "Q-3");

        // 1. Create a valid "Yes" path response
        SurveyResponse res1 = new SurveyResponse("R-1", "S-101");
        res1.putAnswer("Q-1", "Yes");
        res1.putAnswer("Q-2", "Tesla Model 3");
        res1.putAnswer("Q-3", 5);

        // 2. Create a valid "No" path response (skipped Q-2)
        SurveyResponse res2 = new SurveyResponse("R-2", "S-101");
        res2.putAnswer("Q-1", "No");
        res2.putAnswer("Q-3", 4);

        // 3. Create an invalid response (submitted Q-2 answer but chose "No")
        SurveyResponse res3 = new SurveyResponse("R-3", "S-101");
        res3.putAnswer("Q-1", "No");
        res3.putAnswer("Q-2", "Toyota");
        res3.putAnswer("Q-3", 3);

        SurveyResponseValidator validator = new SurveyResponseValidator();
        System.out.println("Is response 1 valid? " + validator.validate(survey, res1));
        System.out.println("Is response 2 valid? " + validator.validate(survey, res2));
        System.out.println("Is response 3 valid? " + validator.validate(survey, res3));

        // 4. Run visitor analytics
        System.out.println("\n--- Aggregating Analytics ---");
        List<SurveyResponse> allResponses = Arrays.asList(res1, res2);
        AnalyticsAggregator aggregator = new AnalyticsAggregator(allResponses);
        
        for (Question q : survey.getQuestions()) {
            q.accept(aggregator);
        }
    }
}