1. Conditional types solution
type Config = {
  name: boolean;
  lastname: boolean;
};
type User = {
  name?: string;
  lastname?: string;
};
declare function getUser<
  C extends Config,
  _NamePart = C['name'] extends true ? Pick<Required<User>, 'name'> : {},
  _LastNamePart = C['lastname'] extends true ? Pick<Required<User>, 'lastname'> : {}
  >(
    config: C
): _NamePart & _LastNamePart;
Check the solution in the playground.
Explanation
- declaration have generic type C extends Configin order to be able to work with narrowed variable type
- we have created two local variables _NamePartand_LastNamePart, the purpose is readability
- 
_NamePart = C['name'] extends true ? Pick<Required<User>, 'name'> : {}- if type variableChasnameproperty set attruewe assign type which has required fieldnamefrom the original typeUser
- 
_LastNamePart = C['lastname'] extends true ? Pick<Required<User>, 'lastname'> : {}- in the same way as before we ask aboutlastname
- 
_NamePart & _LastNamePartwe return intersection type, it means depends on what we get in particular parts we join those parts.
Take a look that I have used monoid property as in both conditional types I just fallback to {} as neutral element of & operation.
2. Overloads solution
type Config = {
  name: boolean;
  lastname: boolean;
};
type User = {
  name?: string;
  lastname?: string;
};
declare function getUser(
  config: { name: true; lastname: false}
): Pick<Required<User>,'name'>;
declare function getUser(
  config: { name: false; lastname: true}
): Pick<Required<User>,'lastname'>;
declare function getUser(
  config: { name: false; lastname: false}
): {};
declare function getUser(
  config: { name: true; lastname: true}
): Required<User>;
Check the solution in the playground
The solution with overloads is less sophisticated but also longer, in order to achieve the result we need to create overload for every possible correlation of both fields in Config, so exactly 4 versions.
- 
Pick<Required<User>,'name'>we pick onlynamefield fromUserand say its required.
- 
Required<User>- we say we getUserbut with all fields non-optional
Utility types used in both solutions:
- Pick - allows for picking only wanted set of properties
- Required - produce a type with all properties required
Also want to mention more sophisticated solution by Rahul Kashyap left in the comment - Solution from the comments section. Good job Rahul!
This series is will continue. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.
 

 
    
Top comments (3)
Here is a generic solution that will support future interface changes:
Here is an even more generic solution to any type (not just User):
Another solution: