diff --git a/src/ast.rs b/src/ast.rs
index 7c83033f..fa563341 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -289,7 +289,8 @@ impl InputValue {
     /// Resolve all variables to their values.
     pub fn into_const(self, vars: &HashMap<String, InputValue>) -> InputValue {
         match self {
-            InputValue::Variable(v) => vars[&v].clone(),
+            InputValue::Variable(v) => vars.get(&v)
+                .map_or_else(InputValue::null, Clone::clone),
             InputValue::List(l) => InputValue::List(
                 l.into_iter().map(|s| s.map(|v| v.into_const(vars))).collect()
             ),
diff --git a/src/executor_tests/mod.rs b/src/executor_tests/mod.rs
index 481c5bd1..391693c2 100644
--- a/src/executor_tests/mod.rs
+++ b/src/executor_tests/mod.rs
@@ -1 +1,2 @@
 mod introspection;
+mod variables;
diff --git a/src/executor_tests/variables.rs b/src/executor_tests/variables.rs
new file mode 100644
index 00000000..76a930c7
--- /dev/null
+++ b/src/executor_tests/variables.rs
@@ -0,0 +1,737 @@
+use std::collections::HashMap;
+
+use value::Value;
+use ast::InputValue;
+use schema::model::RootNode;
+use ::GraphQLError::ValidationError;
+use validation::RuleError;
+use parser::SourcePosition;
+
+#[derive(Debug)]
+struct TestComplexScalar;
+
+struct TestType;
+
+graphql_scalar!(TestComplexScalar {
+    resolve(&self) -> Value {
+        Value::string("SerializedValue")
+    }
+
+    from_input_value(v: &InputValue) -> Option<TestComplexScalar> {
+        if let Some(s) = v.as_string_value() {
+            if s == "SerializedValue" {
+                return Some(TestComplexScalar);
+            }
+        }
+
+        None
+    }
+});
+
+
+graphql_input_object!(
+    #[derive(Debug)]
+    struct TestInputObject {
+        a: Option<String>,
+        b: Option<Vec<Option<String>>>,
+        c: String,
+        d: Option<TestComplexScalar>,
+    }
+);
+
+graphql_input_object!(
+    #[derive(Debug)]
+    struct TestNestedInputObject {
+        na: TestInputObject,
+        nb: String,
+    }
+);
+
+graphql_object!(TestType: () |&self| {
+    field field_with_object_input(input: Option<TestInputObject>) -> String {
+        format!("{:?}", input)
+    }
+
+    field field_with_nullable_string_input(input: Option<String>) -> String {
+        format!("{:?}", input)
+    }
+
+    field field_with_non_nullable_string_input(input: String) -> String {
+        format!("{:?}", input)
+    }
+
+    field field_with_default_argument_value(input = ("Hello World".to_owned()): String) -> String {
+        format!("{:?}", input)
+    }
+
+    field field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
+        format!("{:?}", input)
+    }
+
+    field list(input: Option<Vec<Option<String>>>) -> String {
+        format!("{:?}", input)
+    }
+
+    field nn_list(input: Vec<Option<String>>) -> String {
+        format!("{:?}", input)
+    }
+
+    field list_nn(input: Option<Vec<String>>) -> String {
+        format!("{:?}", input)
+    }
+
+    field nn_list_nn(input: Vec<String>) -> String {
+        format!("{:?}", input)
+    }
+});
+
+fn run_variable_query<F>(query: &str, vars: HashMap<String, InputValue>, f: F)
+    where F: Fn(&HashMap<String, Value>) -> ()
+{
+    let schema = RootNode::new(TestType, ());
+
+    let (result, errs) = ::execute(query, None, &schema, &vars, &())
+        .expect("Execution failed");
+
+    assert_eq!(errs, []);
+
+    println!("Result: {:?}", result);
+
+    let obj = result.as_object_value().expect("Result is not an object");
+
+    f(obj);
+}
+
+fn run_query<F>(query: &str, f: F)
+    where F: Fn(&HashMap<String, Value>) -> ()
+{
+    run_variable_query(query, HashMap::new(), f);
+}
+
+#[test]
+fn inline_complex_input() {
+    run_query(
+        r#"{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
+        });
+}
+
+#[test]
+fn inline_parse_single_value_to_list() {
+    run_query(
+        r#"{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
+        });
+}
+
+#[test]
+fn inline_runs_from_input_value_on_scalar() {
+    run_query(
+        r#"{ fieldWithObjectInput(input: {c: "baz", d: "SerializedValue"}) }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#)));
+        });
+}
+
+#[test]
+fn variable_complex_input() {
+    run_variable_query(
+        r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::object(vec![
+                ("a", InputValue::string("foo")),
+                ("b", InputValue::list(vec![InputValue::string("bar")])),
+                ("c", InputValue::string("baz")),
+            ].into_iter().collect())),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
+        });
+}
+
+#[test]
+fn variable_parse_single_value_to_list() {
+    run_variable_query(
+        r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::object(vec![
+                ("a", InputValue::string("foo")),
+                ("b", InputValue::string("bar")),
+                ("c", InputValue::string("baz")),
+            ].into_iter().collect())),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
+        });
+}
+
+#[test]
+fn variable_runs_from_input_value_on_scalar() {
+    run_variable_query(
+        r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::object(vec![
+                ("c", InputValue::string("baz")),
+                ("d", InputValue::string("SerializedValue")),
+            ].into_iter().collect())),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithObjectInput"),
+                Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#)));
+        });
+}
+
+#[test]
+fn variable_error_on_nested_non_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::object(vec![
+            ("a", InputValue::string("foo")),
+            ("b", InputValue::string("bar")),
+            ("c", InputValue::null()),
+        ].into_iter().collect()))
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn variable_error_on_incorrect_type() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::string("foo bar")),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. Expected "TestInputObject", found not an object."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn variable_error_on_omit_non_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::object(vec![
+            ("a", InputValue::string("foo")),
+            ("b", InputValue::string("bar")),
+        ].into_iter().collect()))
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn variable_multiple_errors_with_nesting() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestNestedInputObject) { fieldWithNestedObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::object(vec![
+            ("na", InputValue::object(vec![
+                ("a", InputValue::string("foo")),
+            ].into_iter().collect())),
+        ].into_iter().collect()))
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In field "na": In field "c": Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In field "nb": Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn variable_error_on_additional_field() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::object(vec![
+            ("a", InputValue::string("foo")),
+            ("b", InputValue::string("bar")),
+            ("c", InputValue::string("baz")),
+            ("extra", InputValue::string("dog")),
+        ].into_iter().collect()))
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In field "extra": Unknown field."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn allow_nullable_inputs_to_be_omitted() {
+    run_query(
+        r#"{ fieldWithNullableStringInput }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNullableStringInput"),
+                Some(&Value::string(r#"None"#)));
+        });
+}
+
+#[test]
+fn allow_nullable_inputs_to_be_omitted_in_variable() {
+    run_query(
+        r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNullableStringInput"),
+                Some(&Value::string(r#"None"#)));
+        });
+}
+
+#[test]
+fn allow_nullable_inputs_to_be_set_to_null_in_variable() {
+    run_variable_query(
+        r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
+        vec![
+            ("value".to_owned(), InputValue::null()),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNullableStringInput"),
+                Some(&Value::string(r#"None"#)));
+        });
+}
+
+#[test]
+fn allow_nullable_inputs_to_be_set_to_value_in_variable() {
+    run_variable_query(
+        r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
+        vec![
+            ("value".to_owned(), InputValue::string("a")),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNullableStringInput"),
+                Some(&Value::string(r#"Some("a")"#)));
+        });
+}
+
+#[test]
+fn allow_nullable_inputs_to_be_set_to_value_directly() {
+    run_query(
+        r#"{ fieldWithNullableStringInput(input: "a") }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNullableStringInput"),
+                Some(&Value::string(r#"Some("a")"#)));
+        });
+}
+
+#[test]
+fn does_not_allow_non_nullable_input_to_be_omitted_in_variable() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#;
+    let vars = vec![
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$value" of required type "String!" was not provided."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn does_not_allow_non_nullable_input_to_be_set_to_null_in_variable() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#;
+    let vars = vec![
+        ("value".to_owned(), InputValue::null()),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$value" of required type "String!" was not provided."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn allow_non_nullable_inputs_to_be_set_to_value_in_variable() {
+    run_variable_query(
+        r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#,
+        vec![
+            ("value".to_owned(), InputValue::string("a")),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNonNullableStringInput"),
+                Some(&Value::string(r#""a""#)));
+        });
+}
+
+#[test]
+fn allow_non_nullable_inputs_to_be_set_to_value_directly() {
+    run_query(
+        r#"{ fieldWithNonNullableStringInput(input: "a") }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithNonNullableStringInput"),
+                Some(&Value::string(r#""a""#)));
+        });
+}
+
+#[test]
+fn allow_lists_to_be_null() {
+    run_variable_query(
+        r#"query q($input: [String]) { list(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::null()),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("list"),
+                Some(&Value::string(r#"None"#)));
+        });
+}
+
+#[test]
+fn allow_lists_to_contain_values() {
+    run_variable_query(
+        r#"query q($input: [String]) { list(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("list"),
+                Some(&Value::string(r#"Some([Some("A")])"#)));
+        });
+}
+
+#[test]
+fn allow_lists_to_contain_null() {
+    run_variable_query(
+        r#"query q($input: [String]) { list(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+                InputValue::null(),
+                InputValue::string("B"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("list"),
+                Some(&Value::string(r#"Some([Some("A"), None, Some("B")])"#)));
+        });
+}
+
+#[test]
+fn does_not_allow_non_null_lists_to_be_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: [String]!) { nnList(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::null()),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" of required type "[String]!" was not provided."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn allow_non_null_lists_to_contain_values() {
+    run_variable_query(
+        r#"query q($input: [String]!) { nnList(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("nnList"),
+                Some(&Value::string(r#"[Some("A")]"#)));
+        });
+}
+#[test]
+fn allow_non_null_lists_to_contain_null() {
+    run_variable_query(
+        r#"query q($input: [String]!) { nnList(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+                InputValue::null(),
+                InputValue::string("B"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("nnList"),
+                Some(&Value::string(r#"[Some("A"), None, Some("B")]"#)));
+        });
+}
+
+#[test]
+fn allow_lists_of_non_null_to_be_null() {
+    run_variable_query(
+        r#"query q($input: [String!]) { listNn(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::null()),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("listNn"),
+                Some(&Value::string(r#"None"#)));
+        });
+}
+
+#[test]
+fn allow_lists_of_non_null_to_contain_values() {
+    run_variable_query(
+        r#"query q($input: [String!]) { listNn(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("listNn"),
+                Some(&Value::string(r#"Some(["A"])"#)));
+        });
+}
+
+#[test]
+fn does_not_allow_lists_of_non_null_to_contain_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: [String!]) { listNn(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::list(vec![
+            InputValue::string("A"),
+            InputValue::null(),
+            InputValue::string("B"),
+        ])),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn does_not_allow_non_null_lists_of_non_null_to_contain_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#;
+    let vars = vec![
+        ("input".to_owned(), InputValue::list(vec![
+            InputValue::string("A"),
+            InputValue::null(),
+            InputValue::string("B"),
+        ])),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn does_not_allow_non_null_lists_of_non_null_to_be_null() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#;
+    let vars = vec![
+        ("value".to_owned(), InputValue::null()),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" of required type "[String!]!" was not provided."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn allow_non_null_lists_of_non_null_to_contain_values() {
+    run_variable_query(
+        r#"query q($input: [String!]!) { nnListNn(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::list(vec![
+                InputValue::string("A"),
+            ])),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("nnListNn"),
+                Some(&Value::string(r#"["A"]"#)));
+        });
+}
+
+#[test]
+fn does_not_allow_invalid_types_to_be_used_as_values() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: TestType!) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("value".to_owned(), InputValue::list(vec![
+            InputValue::string("A"),
+            InputValue::string("B"),
+        ])),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" expected value of type "TestType!" which cannot be used as an input type."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn does_not_allow_unknown_types_to_be_used_as_values() {
+    let schema = RootNode::new(TestType, ());
+
+    let query = r#"query q($input: UnknownType!) { fieldWithObjectInput(input: $input) }"#;
+    let vars = vec![
+        ("value".to_owned(), InputValue::list(vec![
+            InputValue::string("A"),
+            InputValue::string("B"),
+        ])),
+    ].into_iter().collect();
+
+    let error = ::execute(query, None, &schema, &vars, &())
+        .unwrap_err();
+
+    assert_eq!(error, ValidationError(vec![
+        RuleError::new(
+            r#"Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type."#,
+            &[SourcePosition::new(8, 0, 8)],
+        ),
+    ]));
+}
+
+#[test]
+fn default_argument_when_not_provided() {
+    run_query(
+        r#"{ fieldWithDefaultArgumentValue }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithDefaultArgumentValue"),
+                Some(&Value::string(r#""Hello World""#)));
+        });
+}
+
+#[test]
+fn default_argument_when_nullable_variable_not_provided() {
+    run_query(
+        r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#,
+        |result| {
+            assert_eq!(
+                result.get("fieldWithDefaultArgumentValue"),
+                Some(&Value::string(r#""Hello World""#)));
+        });
+}
+
+#[test]
+fn default_argument_when_nullable_variable_set_to_null() {
+    run_variable_query(
+        r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#,
+        vec![
+            ("input".to_owned(), InputValue::null()),
+        ].into_iter().collect(),
+        |result| {
+            assert_eq!(
+                result.get("fieldWithDefaultArgumentValue"),
+                Some(&Value::string(r#""Hello World""#)));
+        });
+}
diff --git a/src/lib.rs b/src/lib.rs
index 5d647e1c..8facda63 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -204,7 +204,7 @@ use std::collections::HashMap;
 use rustc_serialize::json::{ToJson, Json};
 
 use parser::{parse_document_source, ParseError, Spanning, SourcePosition};
-use validation::{RuleError, ValidatorContext, visit_all_rules};
+use validation::{RuleError, ValidatorContext, visit_all_rules, validate_input_values};
 use executor::execute_validated_query;
 
 pub use ast::{ToInputValue, FromInputValue, InputValue, Type, Selection};
@@ -242,6 +242,14 @@ pub fn execute<'a, CtxT, QueryT, MutationT>(
 {
     let document = try!(parse_document_source(document_source));
 
+    {
+        let errors = validate_input_values(variables, &document, &root_node.schema);
+
+        if !errors.is_empty() {
+            return Err(GraphQLError::ValidationError(errors));
+        }
+    }
+
     {
         let mut ctx = ValidatorContext::new(&root_node.schema, &document);
         visit_all_rules(&mut ctx, &document);
diff --git a/src/macros/args.rs b/src/macros/args.rs
index a5ffcefe..f6326fc1 100644
--- a/src/macros/args.rs
+++ b/src/macros/args.rs
@@ -21,17 +21,6 @@ macro_rules! __graphql__args {
         __graphql__args!(@assign_arg_vars, $args, $executorvar, $($rest)*);
     };
 
-    (
-        @assign_arg_vars,
-        $args:ident, $executorvar:ident,
-        $name:ident : Option<$ty:ty> as $desc:tt $($rest:tt)*
-    ) => {
-        let $name: Option<$ty> = $args
-            .get(&$crate::to_snake_case(stringify!($name)))
-            .unwrap_or(None);
-        __graphql__args!(@assign_arg_vars, $args, $executorvar, $($rest)*);
-    };
-
     (
         @assign_arg_vars,
         $args:ident, $executorvar:ident,
diff --git a/src/macros/input_object.rs b/src/macros/input_object.rs
index b78aeb56..90b04077 100644
--- a/src/macros/input_object.rs
+++ b/src/macros/input_object.rs
@@ -50,8 +50,13 @@ macro_rules! graphql_input_object {
         Some($name {
             $( $field_name: {
                 let n: String = $crate::to_snake_case(stringify!($field_name));
-                let v: &$crate::InputValue = $var[&n[..]];
-                $crate::FromInputValue::from(v).unwrap()
+                let v: Option<&&$crate::InputValue> = $var.get(&n[..]);
+
+                if let Some(v) = v {
+                    $crate::FromInputValue::from(v).unwrap()
+                } else {
+                    $crate::FromInputValue::from(&$crate::InputValue::null()).unwrap()
+                }
             } ),*
         })
     };
diff --git a/src/schema/model.rs b/src/schema/model.rs
index cd37df2a..f1810a35 100644
--- a/src/schema/model.rs
+++ b/src/schema/model.rs
@@ -338,3 +338,13 @@ impl fmt::Display for DirectiveLocation {
         })
     }
 }
+
+impl<'a> fmt::Display for TypeType<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            TypeType::Concrete(ref t) => f.write_str(&t.name().unwrap()),
+            TypeType::List(ref i) => write!(f, "[{}]", i),
+            TypeType::NonNull(ref i) => write!(f, "{}!", i),
+        }
+    }
+}
diff --git a/src/types/base.rs b/src/types/base.rs
index af40e608..11d0cf09 100644
--- a/src/types/base.rs
+++ b/src/types/base.rs
@@ -76,9 +76,11 @@ impl Arguments {
 
         if let (&mut Some(ref mut args), &Some(ref meta_args)) = (&mut args, meta_args) {
             for arg in meta_args {
-                if !args.contains_key(&arg.name) {
+                if !args.contains_key(&arg.name) || args[&arg.name].is_null() {
                     if let Some(ref default_value) = arg.default_value {
                         args.insert(arg.name.clone(), default_value.clone());
+                    } else {
+                        args.insert(arg.name.clone(), InputValue::null());
                     }
                 }
             }
diff --git a/src/types/containers.rs b/src/types/containers.rs
index 11501524..45c23efe 100644
--- a/src/types/containers.rs
+++ b/src/types/containers.rs
@@ -25,7 +25,7 @@ impl<T, CtxT> GraphQLType<CtxT> for Option<T> where T: GraphQLType<CtxT> {
 impl<T> FromInputValue for Option<T> where T: FromInputValue {
     fn from(v: &InputValue) -> Option<Option<T>> {
         match v {
-            &InputValue::Null => None,
+            &InputValue::Null => Some(None),
             v => match v.convert() {
                 Some(x) => Some(Some(x)),
                 None => None,
@@ -79,7 +79,12 @@ impl<T> FromInputValue for Vec<T> where T: FromInputValue {
                     None
                 }
             },
-            _ => None,
+            ref other =>
+                if let Some(e) = other.convert() {
+                    Some(vec![ e ])
+                } else {
+                    None
+                }
         }
     }
 }
diff --git a/src/validation/input_value.rs b/src/validation/input_value.rs
new file mode 100644
index 00000000..5413a9ab
--- /dev/null
+++ b/src/validation/input_value.rs
@@ -0,0 +1,268 @@
+use std::collections::{HashMap, HashSet};
+use std::fmt;
+
+use parser::SourcePosition;
+use ast::{InputValue, Document, Definition, VariableDefinitions};
+use validation::RuleError;
+use schema::model::{SchemaType, TypeType};
+use schema::meta::{MetaType, ScalarMeta, InputObjectMeta, EnumMeta};
+
+#[derive(Debug)]
+enum Path<'a> {
+    Root,
+    ArrayElement(usize, &'a Path<'a>),
+    ObjectField(&'a str, &'a Path<'a>),
+}
+
+pub fn validate_input_values(
+    values: &HashMap<String, InputValue>,
+    document: &Document,
+    schema: &SchemaType,
+)
+    -> Vec<RuleError>
+{
+    let mut errs = vec![];
+
+    for def in document {
+        if let &Definition::Operation(ref op) = def {
+            if let Some(ref vars) = op.item.variable_definitions {
+                validate_var_defs(values, &vars.item, schema, &mut errs);
+            }
+        }
+    }
+
+    errs.sort();
+    errs
+}
+
+fn validate_var_defs(
+    values: &HashMap<String, InputValue>,
+    var_defs: &VariableDefinitions,
+    schema: &SchemaType,
+    errors: &mut Vec<RuleError>,
+) {
+    for &(ref name, ref def) in var_defs.iter() {
+        let raw_type_name = def.var_type.item.innermost_name();
+        match schema.concrete_type_by_name(raw_type_name) {
+            Some(t) if t.is_input() => {
+                let ct = schema.make_type(&def.var_type.item);
+
+                if def.var_type.item.is_non_null() && is_absent_or_null(values.get(&name.item)) {
+                    errors.push(RuleError::new(
+                        &format!(
+                            r#"Variable "${}" of required type "{}" was not provided."#,
+                            name.item, def.var_type.item,
+                        ),
+                        &[ name.start.clone() ],
+                    ));
+                } else if let Some(ref v) = values.get(&name.item) {
+                    unify_value(&name.item, &name.start, v, &ct, schema, errors, Path::Root);
+                }
+            },
+            _ => errors.push(RuleError::new(
+                &format!(
+                    r#"Variable "${}" expected value of type "{}" which cannot be used as an input type."#,
+                    name.item, def.var_type.item,
+                ),
+                &[ name.start.clone() ],
+            ))
+        }
+    }
+}
+
+fn unify_value<'a>(
+    var_name: &str,
+    var_pos: &SourcePosition,
+    value: &InputValue,
+    meta_type: &TypeType<'a>,
+    schema: &SchemaType,
+    errors: &mut Vec<RuleError>,
+    path: Path<'a>,
+) {
+    match *meta_type {
+        TypeType::NonNull(ref inner) => {
+            if value.is_null() {
+                push_unification_error(
+                    errors, var_name, var_pos, &path,
+                    &format!(r#"Expected "{}", found null"#, meta_type)
+                );
+            }
+            else {
+                unify_value(var_name, var_pos, value, &inner, schema, errors, path);
+            }
+        }
+
+        TypeType::List(ref inner) => {
+            if value.is_null() {
+                return;
+            }
+
+            match value.to_list_value() {
+                Some(l) =>
+                    for (i, v) in l.iter().enumerate() {
+                        unify_value(var_name, var_pos, v, &inner, schema, errors, Path::ArrayElement(i, &path));
+                    },
+                _ => unify_value(var_name, var_pos, value, &inner, schema, errors, path)
+            }
+        }
+
+        TypeType::Concrete(mt) => {
+            if value.is_null() {
+                return;
+            }
+
+            match mt {
+                &MetaType::Scalar(ref sm) =>
+                    unify_scalar(var_name, var_pos, value, sm, errors, &path),
+                &MetaType::Enum(ref em) =>
+                    unify_enum(var_name, var_pos, value, em, errors, &path),
+                &MetaType::InputObject(ref iom) =>
+                    unify_input_object(var_name, var_pos, value, iom, schema, errors, &path),
+                _ => panic!("Can't unify non-input concrete type"),
+            }
+        }
+    }
+}
+
+fn unify_scalar<'a>(
+    var_name: &str,
+    var_pos: &SourcePosition,
+    value: &InputValue,
+    meta: &ScalarMeta,
+    errors: &mut Vec<RuleError>,
+    path: &Path<'a>,
+) {
+    match value {
+        &InputValue::List(_) =>
+            push_unification_error(
+                errors,
+                var_name,
+                var_pos,
+                path,
+                &format!(r#"Expected "{}", found list"#, meta.name),
+            ),
+        &InputValue::Object(_) =>
+            push_unification_error(
+                errors,
+                var_name,
+                var_pos,
+                path,
+                &format!(r#"Expected "{}", found object"#, meta.name),
+            ),
+        _ => (),
+    }
+}
+
+fn unify_enum<'a>(
+    var_name: &str,
+    var_pos: &SourcePosition,
+    value: &InputValue,
+    meta: &EnumMeta,
+    errors: &mut Vec<RuleError>,
+    path: &Path<'a>,
+) {
+    match value {
+        &InputValue::String(_) | &InputValue::Enum(_) => (),
+        _ => push_unification_error(
+            errors,
+            var_name,
+            var_pos,
+            path,
+            &format!(r#"Expected "{}", found not a string or enum"#, meta.name),
+        )
+    }
+}
+
+fn unify_input_object<'a>(
+    var_name: &str,
+    var_pos: &SourcePosition,
+    value: &InputValue,
+    meta: &InputObjectMeta,
+    schema: &SchemaType,
+    errors: &mut Vec<RuleError>,
+    path: &Path<'a>,
+) {
+    if let Some(ref obj) = value.to_object_value() {
+        let mut keys = obj.keys().collect::<HashSet<&&str>>();
+
+        for input_field in &meta.input_fields {
+            let mut has_value = false;
+            keys.remove(&input_field.name.as_str());
+
+            if let Some(ref value) = obj.get(input_field.name.as_str()) {
+                if !value.is_null() {
+                    has_value = true;
+
+                    unify_value(
+                        var_name,
+                        var_pos,
+                        value,
+                        &schema.make_type(&input_field.arg_type),
+                        schema,
+                        errors,
+                        Path::ObjectField(&input_field.name, path),
+                    );
+                }
+            }
+
+            if !has_value && input_field.arg_type.is_non_null() {
+                push_unification_error(
+                    errors,
+                    var_name,
+                    var_pos,
+                    &Path::ObjectField(&input_field.name, path),
+                    &format!(r#"Expected "{}", found null"#, input_field.arg_type),
+                );
+            }
+        }
+
+        for key in keys {
+            push_unification_error(
+                errors,
+                var_name,
+                var_pos,
+                &Path::ObjectField(&key, path),
+                &format!("Unknown field"),
+            );
+        }
+    }
+    else {
+        push_unification_error(
+            errors,
+            var_name,
+            var_pos,
+            path,
+            &format!(r#"Expected "{}", found not an object"#, meta.name),
+        );
+    }
+}
+
+fn is_absent_or_null(v: Option<&InputValue>) -> bool {
+    v.map_or(true, InputValue::is_null)
+}
+
+fn push_unification_error<'a>(
+    errors: &mut Vec<RuleError>,
+    var_name: &str,
+    var_pos: &SourcePosition,
+    path: &Path<'a>,
+    message: &str,
+) {
+    errors.push(RuleError::new(
+        &format!(
+            r#"Variable "${}" got invalid value. {}{}."#,
+            var_name, path, message,
+        ),
+        &[ var_pos.clone() ],
+    ));
+}
+
+impl<'a> fmt::Display for Path<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Path::Root => write!(f, ""),
+            Path::ArrayElement(idx, prev) => write!(f, "{}In element #{}: ", prev, idx),
+            Path::ObjectField(name, prev) => write!(f, r#"{}In field "{}": "#, prev, name),
+        }
+    }
+}
diff --git a/src/validation/mod.rs b/src/validation/mod.rs
index 7f7b58b3..ea2c98f3 100644
--- a/src/validation/mod.rs
+++ b/src/validation/mod.rs
@@ -5,6 +5,7 @@ mod traits;
 mod context;
 mod multi_visitor;
 mod rules;
+mod input_value;
 
 #[cfg(test)]
 mod test_harness;
@@ -14,6 +15,7 @@ pub use self::visitor::visit;
 pub use self::context::{RuleError, ValidatorContext};
 pub use self::rules::visit_all_rules;
 pub use self::multi_visitor::MultiVisitor;
+pub use self::input_value::validate_input_values;
 
 #[cfg(test)]
 pub use self::test_harness::{
diff --git a/src/validation/rules/variables_in_allowed_position.rs b/src/validation/rules/variables_in_allowed_position.rs
index e21f3b62..007448c8 100644
--- a/src/validation/rules/variables_in_allowed_position.rs
+++ b/src/validation/rules/variables_in_allowed_position.rs
@@ -54,8 +54,6 @@ impl<'a> VariableInAllowedPosition<'a> {
                         (_, t) => t.clone(),
                     };
 
-                    println!("Variable {} of type {} used in position expecting {}", var_name.item, expected_type, var_type);
-
                     if !ctx.schema.is_subtype(&expected_type, var_type) {
                         ctx.report_error(
                             &error_message(&var_name.item, &format!("{}", expected_type), &format!("{}", var_type)),