diff --git a/editions/free/src/css/start.css b/editions/free/src/css/start.css
index f8989f0..22d3030 100644
--- a/editions/free/src/css/start.css
+++ b/editions/free/src/css/start.css
@@ -344,3 +344,43 @@ div.frame {
     text-align: right;
 }
 
+.optionsInstruction {
+    position: absolute;
+    width: 96%;
+    top: 0;
+    padding: 2%;
+    color: #ffdd44;
+    font-size: 2.75rem;
+    font-weight: 500;
+    text-align: center;
+    text-shadow: 1px 1px 4px #333333;
+    list-style-type: none;
+}
+
+.optionsInstruction.hide {display: none;}
+.optionsInstruction.show {display: block;}
+
+.optionsList {
+    position: absolute;
+    width: 92%;
+    height: 86%;
+    padding: 4%;
+    top: 6%;
+    column-count: 3;
+    column-fill: auto; /* use first column up first */
+    color: white;
+    font-size: 2.25rem;
+    font-weight: 500;
+    text-align: left;
+    text-shadow: 1px 1px 4px #333333;
+    list-style-type: none;
+    z-index: 1000;
+}
+
+.optionsList.hide {display: none;}
+.optionsList.show {display: block;}
+
+.optionsList > div {
+    display: block;
+    padding: .25em;
+}
diff --git a/editions/free/src/index.html b/editions/free/src/index.html
index d2ba348..40b2dd1 100644
--- a/editions/free/src/index.html
+++ b/editions/free/src/index.html
@@ -55,6 +55,11 @@
         <div class="usageNoanswer hide" id="usageNoanswer">
             <div class="usageNoanswerText" id="usageNoanswerText"></div>
         </div>
+
+        <div class="optionsInstruction hide" id="optionsInstruction">
+        </div>
+        <div class="optionsList hide" id="optionsList">
+        </div>
     </div>
 </body>
 </html>
diff --git a/src/entry/app.js b/src/entry/app.js
index 136b7d6..2b4dabd 100644
--- a/src/entry/app.js
+++ b/src/entry/app.js
@@ -1,6 +1,6 @@
 import {preprocessAndLoadCss} from '../utils/lib';
 import Localization from '../utils/Localization';
-import AppUsage from '../utils/AppUsage';
+import InitialOptions from '../utils/InitialOptions';
 import OS from '../tablet/OS';
 import IO from '../tablet/IO';
 import MediaLib from '../tablet/MediaLib';
@@ -110,6 +110,6 @@ window.onload = () => {
             });
         });
         // Initialize currentUsage data
-        AppUsage.initUsage();
+        InitialOptions.initWithSettings(window.Settings.initialOptions);
     });
 };
diff --git a/src/entry/index.js b/src/entry/index.js
index baa849c..145c076 100644
--- a/src/entry/index.js
+++ b/src/entry/index.js
@@ -3,17 +3,27 @@ import {gn, getUrlVars, isAndroid, isiOS} from '../utils/lib';
 import OS from '../tablet/OS';
 import UI from '../editor/ui/UI';
 import Localization from '../utils/Localization';
-import AppUsage from '../utils/AppUsage';
+import InitialOptions from '../utils/InitialOptions';
+
+/*
+When this code starts up, there are several scenarios:
+* the app was already running: "AlreadyRunning"
+* the app was not already running, but has been opened before: "NewSession"
+* the app has never been opened before: "FirstTimeEver"
+
+*/
+
+let alreadyStartedQuestions = false;
 
 export function indexMain () {
     gn('gettings').ontouchend = indexGettingstarted;
     gn('startcode').ontouchend = indexGohome;
     ScratchAudio.init();
     var urlvars = getUrlVars();
-    if (urlvars.back) {
-        indexLoadOptions();
+    if (urlvars.back && InitialOptions.allQuestionsAnswered()) {
+        indexLoadStart();
     } else {
-        indexFirstTime();
+        indexNewSession();
     }
 
     if (window.Settings.edition == 'PBS') {
@@ -36,7 +46,28 @@ export function indexMain () {
     }, 250);
 }
 
-function indexFirstTime () {
+function startQuestionsIfNotAlreadyStarted () {
+    if (!alreadyStartedQuestions) {
+        alreadyStartedQuestions = true;
+        indexAskRemainingQuestions();
+    }
+    window.removeEventListener('touchend', startQuestionsIfNotAlreadyStarted, false);
+}
+
+function indexNewSession () {
+    showSplash();
+    OS.askpermission(); // ask for sound recording
+    setTimeout(function () {
+        OS.hidesplash(addTouchListener);
+    }, 500);
+    // may be necessary to wait for a touch in some environments
+    function addTouchListener () {
+        window.addEventListener('touchend', startQuestionsIfNotAlreadyStarted, false);
+    }
+    setTimeout(startQuestionsIfNotAlreadyStarted, 2000);
+}
+
+function showSplash () {
     gn('authors').className = 'credits show';
     gn('authorsText').className = 'creditsText show';
     if (window.Settings.edition == 'PBS') {
@@ -49,29 +80,9 @@ function indexFirstTime () {
         gn('blueguy').className = 'blue show';
         gn('redguy').className = 'red show';
     }
-    OS.askpermission(); // ask for sound recording
-    setTimeout(function () {
-        OS.hidesplash(doit);
-    }, 500);
-    function doit () {
-        window.ontouchend = function () {
-            indexLoadOptions();
-        };
-    }
-    setTimeout(function () {
-        indexLoadOptions();
-    }, 2000);
 }
 
-function indexLoadOptions () {
-    if (window.Settings.edition != 'PBS' && AppUsage.askForUsage()) {
-        indexLoadUsage();
-    } else {
-        indexLoadStart();
-    }
-}
-
-function indexLoadStart (afterUsage) {
+function indexHideSplash () {
     gn('authors').className = 'credits hide';
     gn('authorsText').className = 'creditsText hide';
 
@@ -84,18 +95,11 @@ function indexLoadStart (afterUsage) {
         gn('blueguy').className = 'blue hide';
         gn('redguy').className = 'red hide';
         gn('gear').className = 'gear show';
-
-        if (afterUsage) {
-            gn('catface').className = 'catface show';
-            gn('jrlogo').className = 'jrlogo show';
-            gn('usageQuestion').className = 'usageQuestion hide';
-            gn('usageSchool').className = 'usageSchool hide';
-            gn('usageHome').className = 'usageHome hide';
-            gn('usageOther').className = 'usageOther hide';
-            gn('usageNoanswer').className = 'usageNoanswer hide';
-        }
-        OS.setAnalyticsPlacePref(AppUsage.currentUsage);
     }
+}
+
+function indexLoadStart () {
+    indexHideSplash();
     gn('gettings').className = 'gettings show';
     gn('startcode').className = 'startcode show';
     document.ontouchmove = function (e) {
@@ -106,7 +110,17 @@ function indexLoadStart (afterUsage) {
     }
 }
 
-function indexLoadUsage () {
+function indexAskRemainingQuestions () {
+    indexHideSplash();
+    var nextQuestionKey = InitialOptions.nextUnansweredQuestion();
+    if (nextQuestionKey) {
+        indexShowQuestion(nextQuestionKey);
+    } else { // done with questions
+        indexLoadStart();
+    }
+}
+
+function indexAskPlace () {
     gn('authors').className = 'credits show';
     gn('authorsText').className = 'creditsText hide';
     gn('purpleguy').className = 'purple hide';
@@ -126,10 +140,137 @@ function indexLoadUsage () {
     gn('usageHome').className = 'usageHome show';
     gn('usageOther').className = 'usageOther show';
     gn('usageNoanswer').className = 'usageNoanswer show';
-    gn('usageSchool').ontouchend = indexSetUsage;
-    gn('usageHome').ontouchend = indexSetUsage;
-    gn('usageOther').ontouchend = indexSetUsage;
-    gn('usageNoanswer').ontouchend = indexSetUsage;
+    gn('usageSchool').ontouchend = indexSetPlace;
+    gn('usageHome').ontouchend = indexSetPlace;
+    gn('usageOther').ontouchend = indexSetPlace;
+    gn('usageNoanswer').ontouchend = indexSetPlace;
+}
+
+function indexSetPlace (e) {
+    var usageText = '';
+
+    switch (e.target.parentElement.id) {
+    case 'usageSchool':
+        usageText = 'school';
+        break;
+    case 'usageHome':
+        usageText = 'home';
+        break;
+    case 'usageOther':
+        usageText = 'other';
+        break;
+    case 'usageNoanswer':
+    default:
+        usageText = 'noanswer';
+        break;
+    }
+    // Send one-time analytics event about usage
+    OS.analyticsEvent('lobby', 'scratchjr_usage', usageText);
+    InitialOptions.setValue('place', usageText);
+    // we use 'place_preference' for this particular Firebase pref
+    OS.setAnalyticsPref('place_preference', usageText);
+    ScratchAudio.sndFX('tap.wav');
+    indexHidePlaceQuestion();
+    indexAskRemainingQuestions();
+}
+
+function indexHidePlaceQuestion () {
+    gn('catface').className = 'catface show';
+    gn('jrlogo').className = 'jrlogo show';
+    gn('usageQuestion').className = 'usageQuestion hide';
+    gn('usageSchool').className = 'usageSchool hide';
+    gn('usageHome').className = 'usageHome hide';
+    gn('usageOther').className = 'usageOther hide';
+    gn('usageNoanswer').className = 'usageNoanswer hide';
+}
+
+function optionTouched (elem) {
+    var key = elem.target.getAttribute('data-key');
+    var value = elem.target.getAttribute('data-value');
+    // sometimes a touch is registered by a child of the relevant parent
+    if (!key && !value) {
+        var parent = elem.target.parentNode;
+        key = parent.getAttribute('data-key');
+        value = parent.getAttribute('data-value');
+    }
+    // if we still don't have a key and value, something is wrong -- just go
+    // to lobby
+    if (!key && !value) {
+        indexLoadStart();
+        return;
+    }
+    // elem.target.style.backgroundColor = 'purple';
+    // if everything is good, register the selection and advance to next screen
+    indexSelectOption(key, value);
+}
+
+// show the question for a given settings option key
+function indexShowQuestion (key) {
+    var optionType = InitialOptions.optionTypeForKey(key);
+    if (optionType === 'place') {
+        indexAskPlace();
+    } else { // custom question
+        var options = InitialOptions.optionsForKey(key);
+        // if we could not find any options, choose 'n/a'
+        if (!options || !options.length) {
+            indexSelectOption(key, 'n/a');
+            return;
+        }
+        // if there's only one option, don't bother asking, just choose it!
+        if (options.length === 1) {
+            indexSelectOption(key, options[0]);
+            return;
+        }
+        // if we got here, there is more than one option...
+        var instructionText = InitialOptions.instructionForKey(key);
+        var instructionElem = document.getElementById('optionsInstruction');
+        instructionElem.appendChild(document.createTextNode(instructionText));
+        gn('optionsInstruction').className = 'optionsInstruction show';
+
+        var optionsListElem = document.getElementById('optionsList');
+        var optionNum = 0;
+        options.forEach(function (option) {
+            var optionElem = document.createElement('div');
+            optionElem.setAttribute('data-key', key);
+            optionElem.setAttribute('data-value', option);
+            optionElem.setAttribute('id', 'option-' + key + '-' + optionNum);
+            optionElem.ontouchend = optionTouched;
+            optionsListElem.appendChild(optionElem);
+
+            switch (optionType) {
+            case 'image':
+                var imgElem = document.createElement('img');
+                imgElem.setAttribute('src', 'svglibrary/' + option);
+                optionElem.appendChild(imgElem);
+                break;
+            case 'text':
+            default:
+                optionElem.appendChild(document.createTextNode(option));
+                break;
+            }
+            optionNum = optionNum + 1;
+        });
+        gn('optionsList').className = 'optionsList show';
+    }
+}
+
+// store user selection, and show next question
+function indexSelectOption (key, val) {
+    InitialOptions.setValue(key, val);
+    OS.setAnalyticsPref(key, val);
+    ScratchAudio.sndFX('tap.wav');
+
+    // clear out old options instruction
+    var instructionElem = document.getElementById('optionsInstruction');
+    instructionElem.innerHTML = '';
+    gn('optionsInstruction').className = 'optionsInstruction hide';
+    // clear out old options content
+    var optionsListElem = document.getElementById('optionsList');
+    optionsListElem.innerHTML = '';
+    gn('optionsList').className = 'optionsList hide';
+
+    // show next question, or advance to start screen
+    indexAskRemainingQuestions();
 }
 
 function indexGohome () {
@@ -153,29 +294,6 @@ function indexGettingstarted () {
     window.location.href = 'gettingstarted.html?place=home';
 }
 
-function indexSetUsage (e) {
-    var usageText = '';
-
-    switch (e.target.parentElement.id) {
-    case 'usageSchool':
-        usageText = 'school';
-        break;
-    case 'usageHome':
-        usageText = 'home';
-        break;
-    case 'usageOther':
-        usageText = 'other';
-        break;
-    case 'usageNoanswer':
-        usageText = 'noanswer';
-        break;
-    }
-    // Send one-time analytics event about usage
-    OS.analyticsEvent('lobby', 'scratchjr_usage', usageText);
-    AppUsage.setUsage(usageText);
-    ScratchAudio.sndFX('tap.wav');
-    indexLoadStart(true);
-}
 // For PBS KIDS edition only
 function indexInfo () {
     ScratchAudio.sndFX('tap.wav');
diff --git a/src/utils/AppUsage.js b/src/utils/AppUsage.js
deleted file mode 100644
index 441e284..0000000
--- a/src/utils/AppUsage.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Cookie from './Cookie';
-
-let currentUsage;
-
-export default class AppUsage {
-    static get currentUsage () {
-        return currentUsage;
-    }
-
-    /**
-     * Initialize currentUsage for attaching to Analytics events from
-     * the usage cookie if it is set. currentUsage is blank if the cookie is
-     * not set.
-     */
-    static initUsage () {
-        const usageCookie = Cookie.get('usage');
-        currentUsage =  (usageCookie) ? usageCookie : '';
-    }
-
-    /**
-     * Check whether the App should ask for the usage data (first time launched)
-     * @return {boolean} True if the usage cookie has never been set
-     */
-    static askForUsage () {
-        var usageCookie = Cookie.get('usage');
-        return usageCookie === null;
-    }
-
-    /**
-     * Set the usage cookie for tracking Analytics Events
-     * @param {string} kind answer from user to the usage survey (home, school, other, noanswer)
-     */
-    static setUsage (kind) {
-        currentUsage = (kind === '') ? 'noanswer' : kind;
-        Cookie.set('usage', currentUsage);
-    }
-}
diff --git a/src/utils/InitialOptions.js b/src/utils/InitialOptions.js
new file mode 100644
index 0000000..c24e875
--- /dev/null
+++ b/src/utils/InitialOptions.js
@@ -0,0 +1,185 @@
+import Cookie from './Cookie';
+
+let settingsSection = null; // reference to settings.json's "initialOptions" section
+let currentVals = {}; // locally cached copy of the same key-value settings in cookie
+// tracks keys for options that have been set during the current session
+// (useful for questions we want to ask every time the app starts up)
+let answeredThisSession = {};
+
+export default class InitialOptions {
+
+    // ****************************************
+    // functions only used from within InitialOptions
+    // ****************************************
+
+    /**
+     * Checks if a cookie is set for a given question
+     * @param {string} key indicates which options question to use
+     */
+    static hasCookieSet (key) {
+        var usageCookie = Cookie.get(key);
+        return usageCookie !== null;
+    }
+
+    /**
+     * Checks if a given question has been answered. If the question needs to be
+     * asked every time the app starts, check that it has been asked this session.
+     * @param {object} question options question object from settings
+     */
+    static isAnswered (question) {
+        if (!question) return false;
+        if (InitialOptions.hasCookieSet(question.key)) {
+            // if the cookie is set, check if the question needs to be asked
+            // every time (and hasn't been asked yet)
+            if (question.everyTime && !answeredThisSession[question.key]) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Initialize currentVals for attaching to Analytics events from
+     * the usage cookie if it is set. currentVals is blank if the cookie is
+     * not set.
+     * @param {string} key indicates which options question to use
+     */
+    static initKeyFromCookie (key) {
+        const usageCookie = Cookie.get(key);
+        currentVals[key] = (usageCookie) ? usageCookie : '';
+    }
+
+    /**
+     * Get value for a given question and a given field for that question
+     * @param {string} questionKey indicates which options question to use
+     * @param {string} fieldKey indicates which field of that question to use
+     */
+    static valForKeyAndField (questionKey, fieldKey) {
+        if (!settingsSection || !settingsSection.length) return null;
+        var question = settingsSection.find(function (question) {
+            return (question.key === questionKey);
+        });
+        if (!question || !question[fieldKey]) return null;
+        return question[fieldKey];
+    }
+
+    // ****************************************
+    // functions called from outside InitialOptions
+    // ****************************************
+
+    /**
+     * Process settings object from settings.json, and initialize values
+     * using cookie values
+     * @param {object} settingsSectionParam JSON-derived object with entire
+     * initialOptions section from settings
+     */
+    static initWithSettings (settingsSectionParam) {
+        settingsSection = settingsSectionParam;
+        if (!settingsSection) return;
+        settingsSection.forEach(function (question) {
+            // question is like {key: OPTION_NAME, options: [...]}
+            if (question.firstTime && !question.everyTime) {
+                InitialOptions.initKeyFromCookie(question.key);
+            }
+        });
+        settingsSection.forEach(function (question) {
+            // question is like {key: OPTION_NAME, options: [...]}
+            if (question.everyTime) {
+                InitialOptions.initKeyFromCookie(question.key);
+            }
+        });
+    }
+
+    /**
+     * Get instruction for given question
+     * @param {string} key indicates which options question to use
+     */
+    static instructionForKey (key) {
+        return InitialOptions.valForKeyAndField(key, 'instruction');
+    }
+
+    /**
+     * Get question type for given question
+     * @param {string} key indicates which options question to use
+     */
+    static optionTypeForKey (key) {
+        var type = InitialOptions.valForKeyAndField(key, 'type');
+        if (!type) {
+            type = 'text'; // default
+        }
+        return type;
+    }
+
+    /**
+     * Get array of posible value choices for given question.
+     * If question depends on a previous response, use that response to
+     * determine which value choices to show.
+     * @param {string} key indicates which options question to use
+     */
+    static optionsForKey (key) {
+        if (!settingsSection || !settingsSection.length) return null;
+        var question = settingsSection.find(function (question) {
+            return (question.key === key);
+        });
+        if (!question || !question.values) return null;
+        var valuesKey = 'default';
+        if (question.dependsOn) {
+            var prevSelection = currentVals[question.dependsOn];
+            if (question.values[prevSelection]) valuesKey = prevSelection;
+        }
+        if (!question.values[valuesKey] || !question.values[valuesKey].length) return null;
+        return question.values[valuesKey];
+    }
+
+    /**
+     * Checks if all of the questions have options set. If any question doesn't
+     * have a cookie value set, or if it needs to be asked every time the app
+     * starts and hasn't yet, return false.
+     */
+    static allQuestionsAnswered () {
+        if (!settingsSection || !settingsSection.length) return true;
+        settingsSection.forEach(function (question) {
+            if (!InitialOptions.isAnswered(question)) {
+                return false;
+            }
+        });
+        return true;
+    }
+
+    /**
+     * Gets array of keys of all questions that still need to be answered
+     * in this app session
+     */
+    // static unansweredQuestions () {
+    //     if (!settingsSection || !settingsSection.length) return [];
+    //     return settingsSection.filter(function (question) {
+    //         return !InitialOptions.isAnswered(question);
+    //     }).map(function (question) {
+    //         return question.key;
+    //     });
+    // }
+
+    /**
+     * Gets next question that needs to be asked
+     */
+    static nextUnansweredQuestion () {
+        if (!settingsSection || !settingsSection.length) return [];
+        var nextUnansweredQuestion = settingsSection.find(function (question) {
+            return !InitialOptions.isAnswered(question);
+        });
+        if (nextUnansweredQuestion) return nextUnansweredQuestion.key;
+        return null;
+    }
+
+    /**
+     * Set an options value in both cookie, and local object.
+     * @param {string} key indicates which options question this value is for
+     * @param {string} value option chosen by user
+     */
+    static setValue (key, value) {
+        currentVals[key] = value;
+        Cookie.set(key, currentVals[key]);
+        answeredThisSession[key] = true;
+    }
+}